diff --git a/README.MD b/README.MD index 4b0a2a02590..cd67968d54a 100644 --- a/README.MD +++ b/README.MD @@ -72,7 +72,7 @@ yarn start --locale en 创建新版本 ```bash -yarn run docusaurus docs:version v2.7.x +yarn run docusaurus docs:version 2.8.x ``` ## 评论设置 diff --git a/docs/release/v2.9.md b/docs/release/v2.9.md new file mode 100644 index 00000000000..a5a767aa6d6 --- /dev/null +++ b/docs/release/v2.9.md @@ -0,0 +1,122 @@ +--- +slug: '/release/v2.9' +title: 'v2.9' +sidebar_position: -2 +hide_title: true +keywords: [] +description: '' +unlist: true +--- + + + + +# 主要内容 + +本次版本变更内容较多,以下为本次版本的主要内容,详细的`Change Log`请参考: +[https://github.com/gogf/gf/releases/tag/v2.9.0](https://github.com/gogf/gf/releases/tag/v2.9.0) + +完整的代码变更请参考:[https://github.com/gogf/gf/compare/v2.8.0...v2.9.0](https://github.com/gogf/gf/compare/v2.8.0...v2.9.0) + + +## 兼容提示 + +## 组件改进 +2. `net/ghttp` + - 新增常用中间件`MiddlewareGzip`支持,用于使用`gzip`算法压缩返回结果:https:// 应当整理提供的中间件文档。 + - 去掉规范路由注册时对`api`定义时输入输出数据结构的`*Req/*Res`命名规则限制。 + - 修复跨域返回`Header`中自动从`Referer`生成`Origin`逻辑不严谨的问题。 + - 修复`BuildParams`方法构建请求参数时针对`json omitempty`标签失效问题。 + - 修复`Logger`设置为`nil`时会引发的空指针`panic`问题。 + +2. `net/client` + - 在全局的服务注册发现组件开启时,该组件创建的`HTTP Client`对象不再默认启用服务发现功能,需要手动开启。 + +1. `database/gdb` + - 新增分库分表特性:https:// + - 新增事务传播特性以及隔离级别、只读模式支持:https:// + - 新增`WhereExists/WhereNotExists`方法支持:https:// + - 新增对`unix socket`数据库链接配置的支持。 + - 修复在`OnDuplicate`方法中使用`gdb.Counter`类型失效问题:https:// + - 修复当用户增加`Select Hook`并修改返回结构后,部分场景下导致`Count/Value/Array`查询结构异常问题。 + - 修复在使用`with`特性时,数据结构中`embedded struct`的元数据标签中的`orm`标签失效问题。 + +3. `net/goai` + - 支持`OpenAPIv3.1`协议。 + - 新增返回数据结构扩展能力:https:// + - 新增自动识别数据校验规则`min/max/length/min-length/max-length/between`为`OpenAPIv3`中对应的校验数据结构。 + - 修复参数数据结构中`embedded struct`数据结构无法扩展为参数结构的问题。 + +4. `errors/gerror` + - 新增`As`方法支持:https:// + +4. `utils/gvalid` + - 修复`enums`参数使用指针时`enums`校验失效的问题。 + +4. `os/glog` + - 日志默认时间打印格式调整为更严谨的`2006-01-02T15:04:05.000Z07:00`。 + +4. `os/gsession` + - 新增`RegenerateId/MustRegenerateId`方法支持,用以手动重新生成`Session ID`:https:// + + +4. `os/gview` + - 修复个别场景下从资源管理器中读取模板文件失败的问题。 + +5. `os/gcmd` + - 命令行参数默认值支持输出到命令帮助界面:https:// + +5. `net/gipv4` + - 改进`ip`地址和`uint32`相互转换实现逻辑。 + +5. `util/gconv` + - 改进`Scan`方法,增加`Scan`到基础数据类型的转换支持:https:// + +6. `test/gtest` + - 改进`AssertNI/AssertIN`方法,增加`map`类型支持:https:// + +## 社区组件 +1. `drivers/pgsql` + - 增加对`pgsql`数组字段类型`varchar[]/text[]`的转换支持,自动转换为Go`[]string`类型。 + - 改进数组类型转换性能。 + +2. `contrib/config/polaris` + - 新增更多的数据格式内容支持,支持`yaml/toml/json/xml/ini/properties`等格式。 + +2. `contrib/registry/consul` + - 新增`consul`服务注册发现组件支持:https:// + +3. `contrib/registry/etcd` + - 新增重试机制,解决`lease`租约过期时的重续问题。 + - 删除默认的`AutoSyncInterval`配置项,解决个别场景下由该默认配置引发的`etcd`阻塞问题。 + +4. `contrib/config/nacos` + - 新增`OnChange`回调配置支持。 + +5. `contrib/metric/otelmetric` + - 新增`WithExemplarFilter`选项支持。 + +## 开发工具 +1. 改进`gf init`命令,初始化项目的模板内容发生了改变。 +2. 改进`gf up`命令,在终端中展示更优雅的`cli`文件下载界面效果。 +3. 改进`gf gen ctrl`命令,`api`中定义的注释将会自动同步到生成的控制器代码中。 +4. 改进`gf gen dao`命令: + - 新增分表选项参数`shardingPattern/sp`,用于定义分表规则,生成支持分库的`dao`代码:https:// + - 改进代码生成模板,支持`Golang Template`语法。 +5. 改进`gf gen enums`命令: + - 修改默认读取的接口数据结构定义目录为`api` + - 修改默认生成代码文件路径为`internal/packed/packed_enums.go` +3. 改进`gf gen genpbentity`命令: + - 增加`TypeMapping`和`FieldMapping`特性支持:https:// + - 增加`TablesEx`支持,用于生成`proto`文件时可以忽略指定的表。 + - 增加`GoPackage`选项用于指定生成`proto`文件中的`go_package`配置项。 +3. 修复`gf build`命令在打印环境变量时不准确的问题,应当打印编译文件时,修改环境变量后,真实使用的环境变量。 +3. 修复`gf gen pb`命令在部分场景下使用`-a`选项时,多次执行命令后,自定义标签重复添加的问题。 + + +## 功能修复 +1. 修复`os/gcache`组件中`Remove`方法返回的数据类型与旧版本不兼容的问题。 +2. 修复`database/gdb`组件中`tinyint(1)/int(1)`类型识别为`bool`类型与旧版本不兼容的问题。 +3. 修复`database/gdb`组件中`Order("id", "dasc")`语句与旧版本不兼容的问题。 +4. 修复`registry/zookeeper`组件中带有额外后缀`-`的服务名称路径监听错误。 +5. 修复`cmd/gf`组件中`make`命令在项目模板中生成的`Makefile`文件中的命令不正确的问题。 diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 1700b0db61a..f8c00f179d0 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -1,4 +1,4 @@ -const LATEST_VERSION_LABEL = '2.8.x(Latest)'; +const LATEST_VERSION_LABEL = '2.9.x(Latest)'; import type { Options as IdealImageOptions } from '@docusaurus/plugin-ideal-image'; import type * as Preset from '@docusaurus/preset-classic'; diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" new file mode 100755 index 00000000000..72ac9644a39 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" new file mode 100644 index 00000000000..73d975c150b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" @@ -0,0 +1,55 @@ +--- +slug: '/supportus/pr' +title: 'Become a Contributor' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, open source, code contribution, documentation contribution, GitHub, gitee, open source project, PR process, framework development] +description: 'GoFrame is an open-source, free framework, welcoming all developers to contribute to its code and documentation. Through the main GitHub repository, you can take part in code contribution and be part of our development history. Additionally, you can help promote the GoFrame framework by improving official documentation, writing blogs, or creating videos.' +--- + +`GoFrame` is open-source, free software, which means anyone can contribute to its development and progress. The project's source code is currently hosted on both `GitHub` (primary repository) and `gitee` (domestic) platforms, with both repositories kept in real-time synchronization. Code contributions are unified through the primary GitHub repository. We warmly welcome more friends to join in the development of the `GoFrame` framework, and any contributions you make will be recorded in the annals of `GoFrame`. + +Contributions can generally be divided into two categories: **code contribution** and **documentation contribution**. + +## 1. Code Contribution + +### 1. Where to Find Tasks + +In the main repository’s `issues`, there are many `issues` labeled with `help wanted`, eagerly awaiting contributions from community members: [Click here for the address](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) + +![](/markdown/3138f105d604376eec1a9ec583359ec3.png) + +### 2. Code Contribution Process + +1. First, `fork` the repository code to your own repository. +2. Create a new development branch in your repository, make changes to the code, and submit the changes to your repository. +3. Create a `pull request` in your repository, selecting your development branch as the source branch and the primary repository's `master` branch as the target branch: [https://help.github.com/en/articles/creating-a-pull-request](https://help.github.com/en/articles/creating-a-pull-request) +4. Submit the `pull request`, then wait for the project developers to do a `code review` on the submission. If the `pull request` is not reviewed for a long time, you can actively follow up and request a `code review` from team members. Once approved, you will become a member of the `GoFrame` framework. +5. Congratulations, your name will be permanently recorded in the list of contributors to the `GoFrame` framework source code. + +### 3. Code Collaboration Conventions + +1. All source code files, types, and methods should have detailed comments. +2. Explanation of implementation ideas is needed for sections with complex logic. +3. All comments should be written in English, not in Chinese. +4. New features/modules must have unit tests, with coverage exceeding `80%`. + +## 2. Documentation Contribution + +### 1. Improving the Official Website + +The documentation of the `GoFrame` framework is mainly concentrated in the `gf-site` repository, address: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site) + +While reading the documentation, if you find areas that are not quite complete, you can click to edit the page to help improve it. This is especially true for examples on how to use components and functions, which can often be supplemented. + +### 2. Blogs and Video Tutorials + +Community members can write articles or record videos to share, making it easier for other community members to find your share through search engines, facilitating mutual learning, and promoting open-source projects. + +Please keep the framework name keyword unified as `GoFrame` in the articles, and do not use abbreviations. + +## 3. Other Assistance + +Any help can be requested by contacting WeChat `389961817`. I will provide the necessary guidance and assistance. + +![img.png](img.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" new file mode 100644 index 00000000000..3c9cfbfe01e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" @@ -0,0 +1,74 @@ +--- +slug: '/supportus/team' +title: 'Join the Community Team' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Open Source Project, Community Team, Technical Sharing, Project Development, Open Source Contribution, Career Competitiveness, Technical Growth, Technical Exchange] +description: 'As the GoFrame framework continues to grow, we are committed to further improvement, enhancing releases to attract more attention. Joining the community team allows you to solve real-world problems, stay updated with the latest developments, participate in internal design and technical discussions, and enjoy a sense of achievement and technical enhancement through occasional sharing. Additionally, you can expand your network with like-minded tech enthusiasts, enhancing your resume competitiveness. Actively participate in project development and promotion, handle community issues together, and share your experiences. Strict criteria ensure the quality of our members, and mature enterprises are welcome to contribute.' +--- + +## 1. Why Recruit for the Community? + +- As the `GoFrame` open source project becomes more robust and comprehensive, we aim to make it even better, more complete, and more powerful! We need more strength! +- With the release of the new `v2` version, `GoFrame` has reached a new milestone, and we want more people to recognize the value created by our relentless efforts! +- Propel `GoFrame` into a community-driven project! + +## 2. What Can You Gain by Joining the Community Team? + +### 1. Solve Real-World Issues in Your Work +By using `GoFrame` and becoming part of its team, you'll gain access to more principles and details than others, helping you solve challenging real-world project issues. + +### 2. Stay Updated with the Latest Development Dynamics of Open Source Projects +In the future, we will distribute first-hand project information within the community team, positioning it as the backbone for `GoFrame`'s external promotion and evangelism. + +### 3. Participate in Internal Project Design and Technical Discussions +We aim to integrate more ideas and experiences into the project's outcomes, collectively setting the direction for the project's future development. + +### 4. Participate Occasionally in Internal Technical Sharing +We hope every team member can share their learning insights and research results both internally and externally. + +### 5. Sense of Achievement and Recognition +Years of dedication and community recognition make us believe that this persistence is valuable. + +### 6. Technical Growth and Enhancement +Enhance your understanding of framework design and underlying principles. You'll also encounter various business requirement scenarios, industry-leading or practical solutions. + +### 7. Resume Enhancement +Increase your competitiveness in the job market. Technical leaders appreciate open-source contributors but focus more on your role in the community, your efforts, and the results of your contributions. + +### 8. Expand Technical Networking +Meet more like-minded experts. + +## 3. We Also Have a Gentleman's Agreement! + +### 1. Actively Participate in Project Development +Any `feature, enhancement, bug fix` is highly welcomed. The `issue` section has an endless flow of community requests, and we hope you'll actively contribute. + +### 2. Actively Promote and Evangelize this Project +Be it videos, articles, or discussions, as the backbone of the community, we expect you to actively promote and share the outstanding content, designs, and ideas of the `GoFrame` community across platforms. + +### 3. Actively Participate in Project `issue` Handling and Community Problem Solving +Actively engage in resolving queries to establish and maintain a positive `GoFrame` community image. You're not fighting alone; when encountering difficulties or tricky issues, you can collaborate with team members to resolve challenges. + +### 4. Actively Participate in Internal Team Communication and Technical Sharing +Whether in the community, workplace, or life, we hope you are proactive and willing to share. We can engage with you during your shares, learning, and growing together. + +## 4. Criteria for Becoming a Community Team Member + +To ensure the quality of the team and project, we have cautiously set some basic criteria for joining. + +- First, you must have already used `GoFrame` in real projects and are eager to deepen your understanding. +- Second, you should be interested in participating in `GoFrame` community building with community members during your spare time. +- Last, recognize that open source is valuable, but like life, it's filled with ups and downs. + +:::tip +Furthermore, coming from and giving back to the community, we warmly welcome contributions from enterprises to jointly build the community, forming a positive cooperation loop between enterprises and the community. Interested enterprises are encouraged to contact us; we will reserve a spot for you. +::: + +## 5. How to Become a Community Team Member + +If you're interested and meet the conditions, please contact the project initiator via WeChat and send a brief self-introduction. + +If you are an open-source veteran, just send your `GitHub` profile link. + +![img.png](img.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" new file mode 100644 index 00000000000..116158e8d2c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" @@ -0,0 +1,10 @@ +--- +slug: '/supportus' +title: 'Support Us' +sidebar_position: 1 +hide_title: true +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" new file mode 100644 index 00000000000..a1baa8c7884 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" @@ -0,0 +1,36 @@ +--- +slug: '/share/group' +title: 'Online Chat Group' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Technical Exchange Group, QQ Group, WeChat Group, Practical Group, Programming Community, Software Development, Developer Communication, GoFrame Official Account] +description: "Join the technical exchange group of the GoFrame framework, participate in the GoFrame practical group and WeChat group, and get the latest updates and technical exchanges on the framework's development. We offer multiple QQ groups and WeChat groups for enthusiasts to share experiences and exchange development insights, while continuously following the latest news about GoFrame." + +--- + +## Join the QQ Group + +Scan or search the group number to add. + +### GoFrame Practical Group 1 +Full + +### GoFrame Practical Group 2 +Full + +### GoFrame Practical Group 3 + + +Click the link to join the group chat "GoFrame Practical Group 3": [https://qm.qq.com/q/K7UMKSrVq8](https://qm.qq.com/q/K7UMKSrVq8) + +## Join the WeChat Group + +Scan or add WeChat `389961817`, note `join` to join the group + + + +## WeChat Official Account + +Follow the development updates of `GoFrame` + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" new file mode 100644 index 00000000000..bf8639a14c0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" @@ -0,0 +1,66 @@ +--- +slug: '/share/2021-03-12' +title: '2021-03-12 First Meeting' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, technical sharing, online meetup, framework introduction, framework design, modular design, unified framework design, code layer design, community-driven development] +description: 'This GoFrame technical sharing introduces its framework design concepts and modular design, including unified framework design and code layer design, without involving specific skills. The event includes an interaction session between developers and the author using Tencent Meeting. We welcome everyone to prepare questions and join the discussion.' +--- + +Hello everyone! + +To fulfill our promise to the community, we plan to hold the first `GoFrame` technical sharing (online meetup)! The theme of this sharing is: **Introduction and Design of the GoFrame Framework**. + +At the same time, we will also have a face-to-face interaction session between developers and the author, with ample time reserved for discussion. Everyone is encouraged to prepare questions they wish to ask. + +To ensure a good discussion experience, the Q&A session will use Tencent Meeting. Please join the meeting in advance if you intend to ask questions. + +## I. Content Outline + +This session will mainly share some design concepts of the framework, without discussing specific usage skills. + +1. [Framework Introduction (Latest Version)](https://goframe.org/) +2. Initial Goals +3. [Framework Design](../../../docs/框架设计/框架设计.md) + - [Modular Design](../../../docs/框架设计/模块化设计.md) + - [Unified Framework Design](../../../docs/框架设计/统一框架设计.md) + - [Code Layering](../../../docs/框架设计/工程开发设计/代码分层设计.md) +4. Other Miscellaneous Items +5. Community-Driven Development Plans +6. Interaction Session + +## II. How to Join + +### Bilibili Live + +[http://live.bilibili.com/22901270](https://live.bilibili.com/22901270) + +### Tencent Meeting + +Guo Qiang invites you to join a Tencent Meeting + +Meeting topic: GoFrame First Meeting + +Meeting time: 2021/03/12 19:00-20:00 (GMT+08:00) China Standard Time - Beijing + +Click the link to join the meeting or add it to your meeting list: + +[https://meeting.tencent.com/s/yNMOX0LDlY8N](https://meeting.tencent.com/s/yNMOX0LDlY8N) + +Meeting ID: **135 616 293** + +Mobile one-tap dialing to join + ++8675536550000,,135616293# (Mainland China) + ++85230018898,,,2,135616293# (Hong Kong, China) + +Dial according to your location + ++8675536550000 (Mainland China) + ++85230018898 (Hong Kong, China) + +PS: + +You can also comment on this article with your questions to allow us to prepare in advance and improve communication efficiency during the event. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" new file mode 100644 index 00000000000..eee18e675fd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" @@ -0,0 +1,34 @@ +--- +slug: '/share/2021-04-17' +title: '2021-04-17 GoCN Gopher Meetup' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame framework,Golang,Tech sharing,Framework design,Microservices development,Event registration,GoCN,Tap4Fun,Chengdu] +description: 'A technical sharing event in the Golang field held by GoCN and Tap4Fun in Chengdu on April 17, 2021, mainly focused on the introduction of GoFrame framework design and microservices development. As one of the speakers, I participated in this successful event, which was informative and had a wealth of gifts. More introductions on GoFrame design can be found in the relevant documentation sections.' +--- + +Hello everyone, a new round of `GoFrame` framework technical sharing is here! + +This time, it was a technical sharing event in the `Golang` field held by `GoCN` and `Tap4Fun` in Chengdu, and I was honored to be invited as one of the speakers. + +The theme of this technical sharing remains focused on introducing framework design, with the following being the reference directory: + +![](/markdown/0940acf2b1d7c695310da3d8316e88fc.png)![](/markdown/ec7498b17b29822f11262708edfba681.png) + +For more introductions related to `GoFrame` design, you can refer to the chapter on [Framework Design](../../../docs/框架设计/框架设计.md). + +Due to time constraints, the introduction on microservices development, which everyone is more interested in, will be shared in subsequent events. + +Event registration address: [https://www.bagevent.com/event/7198261](https://www.bagevent.com/event/7198261?code=001hWnFa1BenRA0Q3lGa1V6Sci0hWnFn&state=STATE) + +* * * + +Elegant dividing line + +* * * + +Thanks to this `Golang` technical sharing event held by `GoCN` and `Tap4Fun`, the whole event was very successful, and the organizers were very attentive, with great gifts ❤️. + +Here is the PPT manuscript of this `GoFrame` framework technical sharing, feel free to use it as needed. + +[gocn-GoFrame Framework Introduction and Design.pdf](/attachments/gocn-GoFrame-intro.pdf) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..d7ab904b0fa --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" @@ -0,0 +1,20 @@ +--- +slug: '/share/2021-06-24' +title: '2021-06-24 GoFrame ORM Component Design' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Component, Component Design, GOCN Open Source Sharing, Core Features, Development Documentation, Live Broadcast, Project Source Code, PPT Download] +description: 'The theme of this sharing session "GoFrame ORM Component Design" mainly introduces the design and related features of the core component ORM in the GoFrame framework. Through this sharing, you will understand the functions and outstanding features of the GoFrame ORM component. For more details, please refer to the development documentation and project source code. This event also includes related PPT file downloads and recorded video viewing.' +--- + +Hi, everyone, this Thursday at 8 PM, the GOCN Open Source Sharing session will cover the topic "GoFrame ORM Component Design," mainly introducing the design and related features of the core component ORM in the GoFrame framework. + +The GoFrame ORM component is quite powerful. Due to limited preparation and sharing time, this session will mainly introduce some of its outstanding features. More details are available in the [development documentation](../../../docs/核心组件/核心组件.md) and [project source code](https://github.com/gogf/gf/tree/master/database/gdb). + +Live broadcast address: [https://live.bilibili.com/21878276](https://live.bilibili.com/21878276) + +![](/markdown/2a110b53cb90f2b0ea1af07ed17c4925.png)![](/markdown/823bc3dcb047efeda12e132de48456e4.png) + +PPT file download: [GoFrame ORM Component Design.pptx](/attachments/GoFrameORM-design.pptx) + +Recorded video viewing: [https://www.bilibili.com/video/BV1yw411o7NE](https://www.bilibili.com/video/BV1yw411o7NE) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" new file mode 100644 index 00000000000..42a13cc01e7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" @@ -0,0 +1,22 @@ +--- +slug: '/share/2022-01-27' +title: "2022-01-27 Let's GoFrame" +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Golang,Open Source,High Performance,Enterprise Level,Development Framework,Out-of-the-Box,OpenTelemetry,Error Stack] +description: 'The core design, functional features, and practical applications of the GoFrame framework. GoFrame is a modular, high-performance enterprise-level Go foundational development framework suitable for business projects of all sizes. With a rich set of basic components, development tools, and practical standards, developers can focus on business logic, improving development and maintenance efficiency, and creating greater value.' +--- + +Hello friends, after a year of hard work, it's been a while since we've had a technical sharing! + +![](/markdown/4690d7d19d1de647c100d48a4389dedf.png)![](/markdown/806eac487471281a18dc07dd07d9f000.png) + +This Thursday at 8 PM `GOCN` Open Source Talk will feature the topic `Let's GoFrame`, mainly introducing some core designs, functional features, and practical demonstrations of the `GoFrame` framework. + +`GoFrame` is a modular, high-performance, enterprise-level `Go` foundational development framework. If you want to use `Golang` to develop a business project, whether it's small or medium to large scale, `GoFrame` is your top choice. If you're looking to develop a `Golang` component library, `GoFrame` provides an out-of-the-box, rich and powerful basic component library that can make your work more efficient. + +`GoFrame` offers a unified and practical engineering development standard and supporting tools, automatic data model and database operation code generation, automatic `OpenAPIv3` document generation, support for `OpenTelemetry` observability standards, full error stack features, support for error codes, full API design of core components, and more. Using `GoFrame`'s out-of-the-box basic components, rich development documentation, practical tools, and standards can help us focus our energy on the business itself, write more standardized, secure, and observable code, improve project development and maintenance efficiency, and enable teams and individuals to create more value. + +PPT file download: [gocn-Let's GoFrame.pptx](/attachments/gocn-LetsGoFrame.pptx) + +`bilibili` video link: [https://www.bilibili.com/video/BV1U34y117w7/](https://www.bilibili.com/video/BV1U34y117w7/) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" new file mode 100644 index 00000000000..88a0b8b7061 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" @@ -0,0 +1,48 @@ +--- +slug: '/share/2022-05-17' +title: '2022-05-17 GoFrame Contribution Guide & Q&A' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Contribution Guide, Open Source Project, Community Contribution, Open Source Star-Snatching Event, Q&A, GitHub, Developer, Tencent Meeting] +description: "GoFrame framework contribution guide and usage Q&A, sharing how to contribute to the GoFrame open source project and explaining the Open Source Star-Snatching Event organized by Tengsource, aiming to enhance the project's stability and vitality through community efforts and provide points rewards for participants. The event will be held on May 17, 2022, and can be joined via Tencent Meeting." +--- + +## Introduction + +Hello, community friends! + +As the main features of the GoFrame framework become more complete and stable, future efforts will focus on promoting community contributions. + +As of 2022-05-17, there are over 1K open source projects using the GoFrame framework on GitHub, and 89 people have contributed to the GoFrame project. Some have become continuous contributors. As the team of contributors grows, our open source project becomes more powerful, stable, open, and vibrant. + +![](/markdown/09b9ca341360819fe4a46e1fb6e62fe0.png) + +## Sharing Topics + +### GoFrame Contribution Guide + +First, in this sharing session, we will mainly introduce how to contribute to GoFrame and what GoFrame needs. + +### Introduction to the Open Source Star-Snatching Event + +Secondly, we will introduce the **Open Source Star-Snatching Event** organized by Tengsource, to show everyone how to earn points rewards in the event while providing urgently needed contributions to GoFrame. + +### Usage Q&A Session + +Finally, we also reserve ample Q&A time, during which you can ask questions you have encountered during use in the meeting. + +## Meeting Link + +Guo Qiang invites you to join the Tencent Meeting + +Meeting Topic: GoFrame Contribution Guide & Q&A + +Meeting Time: 2022/05/17 20:00-22:00 (GMT+08:00) China Standard Time - Beijing + +Click the link to join the meeting or add it to the meeting list: + +[https://meeting.tencent.com/dm/k7o72Mh8TN0G](https://meeting.tencent.com/dm/k7o72Mh8TN0G) + +#TencentMeeting: 718-825-328 + +Copy this information and open the Tencent Meeting app to participate. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" new file mode 100644 index 00000000000..ebc5d010a78 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" @@ -0,0 +1,38 @@ +--- +slug: '/share/2022-06-22' +title: '2022-06-22 GoFrame v2.1 Features & Q&A' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame framework, v2.1 features, Q&A, online questions, meeting link, Tencent Meeting, video review, Guo Qiang, developer sharing] +description: 'The GoFrame v2.1 feature sharing session held on June 22, 2022, introduced new version features and provided usage Q&A online. Hosted by Guo Qiang using Tencent Meeting, participants could ask questions online to resolve any confusion during the use of the GoFrame framework, and a video review link is provided to help users better understand and apply the updates of GoFrame v2.1.' +--- + +## Sharing Session Topics + +### Introduction to `v2.1` Features + +Introduce the main contents and considerations of the feature updates online. + +### Q&A Session + +Participants can ask questions online during the meeting about any doubts they have in the usage process, and we will do our best to help. + +## Meeting Link + +Guo Qiang invites you to join a Tencent Meeting + +Meeting theme: GoFrame v2.1 Features & Q&A + +Meeting time: 2022/06/22 20:00-20:30 (GMT+08:00) China Standard Time - Beijing + +Click the link to join the meeting or add it to your meeting list: + +[https://meeting.tencent.com/dm/hYdbmxIBHt6l](https://meeting.tencent.com/dm/hYdbmxIBHt6l) + +#Tencent Meeting: 329-799-787 + +Copy this information and open the Tencent Meeting app to join + +## Video Review + +[https://www.bilibili.com/video/BV1YS4y1v7BT/](https://www.bilibili.com/video/BV1YS4y1v7BT/) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..2ed241684f6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" @@ -0,0 +1,42 @@ +--- +slug: '/share/2022-09-14' +title: '2022-09-14 ORM Driven Development' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Driven Development, ORM Interface, Technical Sharing, Unit Testing, Framework Usage, Community Contribution, Interface Development Example, Development Considerations] +description: 'Detailed content about ORM driven development with the GoFrame framework, including interface design, interface introduction and their relationships, actual examples of interface development; in addition, it covers development considerations, unit test writing, and the process of contributing the driver to the community, as well as answering common questions about framework usage. Readers can enhance their understanding of GoFrame ORM driven development through this document.' +--- + +Hello everyone, recently, we have collected a lot of interest in ORM driven development from the community, so we are preparing a technical sharing session that mainly introduces some details of GoFrame ORM driven development. Those interested can join on time. + +## Meeting Information + +Guo Qiang invites you to attend a Tencent meeting + +Meeting Topic: GoFrame ORM Driven Development + +Meeting Time: 2022/09/14 19:30-20:00 (GMT+08:00) China Standard Time - Beijing + +Click the link to join the meeting, or add it to the meeting list: + +[https://meeting.tencent.com/dm/lBhnKKnhik0g](https://meeting.tencent.com/dm/lBhnKKnhik0g) + +# Tencent Meeting: 413-297-318 + +Copy this information, open the Tencent Meeting app on your phone to participate + +## Video Replay + +[https://www.bilibili.com/video/BV1rT411M7Sp/](https://www.bilibili.com/video/BV1rT411M7Sp/) + +## Main Content + +- Introduction to GoFrame ORM +- ORM Interface Design +- ORM Interface Introduction +- ORM Interface Relationships +- Interface Development Example +- Development Considerations +- Unit Test Writing +- Contributing the Driver to the Community +- Framework Usage Q&A \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" new file mode 100644 index 00000000000..d1db765428b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" @@ -0,0 +1,11 @@ +--- +slug: '/share' +title: 'Community Exchange' +sidebar_position: 0 +hide_title: true +--- + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" new file mode 100644 index 00000000000..b58a109df15 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" @@ -0,0 +1,589 @@ +--- +slug: '/articles/framework-comparison-goframe-beego-iris-gin' +title: 'Golang Framework Comparison: GoFrame, Beego, Iris and Gin' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Beego, Iris, Gin, Framework Comparison, Go Web Framework, Web Development, Performance Comparison] +description: 'A comprehensive comparison of popular Golang frameworks including GoFrame, Beego, Iris and Gin, analyzing their features, performance, and use cases' +--- + +Migrated from the old official website community contribution article: https://wiki.goframe.org/pages/viewpage.action?pageId=3673375 + + + +![Solve problems? Bring problems?](golang-framework-choose.jpeg) +Solve problems? Bring problems? + +Due to work requirements, I have encountered many development frameworks over the years. While there are numerous Golang development frameworks, they are primarily Web "frameworks". The quotation marks are used here because most "frameworks", from their design and functional positioning, can at best be considered as components. When projects need to use them, developers often have to search for other components elsewhere or build their own. For Web development, these "frameworks" all have complete Web development capabilities with little difference, and they are all secondary encapsulations of the standard library net/http.Server. Due to the large number of frameworks available, I have selected only a few that I have previously evaluated for technical selection, am familiar with, and are currently popular and typical Golang "frameworks". I will make a simple horizontal comparison from the perspective of business project development frameworks, to provide a reference for everyone when selecting a project framework." + +Evaluation Criteria +==== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CriteriaDescription
Basic IntroductionInformation sourced from official websites.
Modular DesignSupport for modular plug-and-play design, low coupling between modules, and ability to use components independently.
Module CompletenessRichness of framework modules and their ability to cover common development needs.
Ease of UseBeyond basic usability, focuses on team's ability to adopt quickly and maintain with low cost in the long term.
Documentation QualityReference materials including documentation, videos, examples, and case studies. Local language support is also considered.
Engineering CompletenessRapid project integration capability, project standards, design patterns, development toolchain, documentation quality, code readability, and maintainability.
Development ModeSupported or officially recommended development patterns.
Engineering StandardsDevelopment standards including directory structure, design patterns, coding standards, and naming conventions.
Community ActivityEase of communication with official team, speed of problem resolution, and bug fix response time.
Development ToolchainCLI development tools for project initialization, cross-compilation, code generation, swagger support, hot reloading, etc.
Web: Performance Tests +

Third-party benchmarks from https://github.com/the-benchmarker/web-frameworks.

+
Web: Route Conflict HandlingSolutions for handling route registration conflicts, which are common in business projects.
Web: Domain SupportSupport for domain binding, including multiple domain capabilities.
Web: File ServiceStatic resource serving capabilities.
Web: Graceful Restart/ShutdownAbility to restart without affecting ongoing requests and gracefully shut down by completing active requests while refusing new ones.
ORMBuilt-in or third-party ORM support, which is crucial for business projects.
SessionSession management support, either general-purpose or web-specific.
I18N SupportInternationalization component support (common but non-core component).
Configuration ManagementCore framework capability for configuration management.
Logging ComponentCore framework capability for logging.
Data ValidationCore framework capability for data validation.
Cache ManagementCore framework capability for cache management, including memory and Redis support, either built-in or via third-party components.
Resource BundlingSupport for bundling static resources, configuration files, and other assets into the executable. Framework components automatically support resource discovery.
Distributed TracingFramework capability for distributed tracing, essential for microservice architectures.
Testing FrameworkUnit testing support and standards, whether using standard library or third-party testing frameworks.
Key AdvantagesNotable strengths of the framework.
Key DisadvantagesNotable limitations or weaknesses.
+ +Horizontal Comparison +==== + +* For the comparison parameters below that involve scoring, the maximum score is based on a 10-point scale. +* Items marked with "-" indicate that the feature is not supported or requires third-party plugin support. +* For the features below, if official documentation is available, direct links will be provided. If documentation cannot be found but the author knows the feature exists, it will be briefly noted. +* Different "frameworks" implement features differently, with significant variations in documentation, functionality, and usability. Readers can refer to the links for more details. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

GoFrameBeegoIrisGin
Comparison Versionv1.15.2v1.12.3v12.0.2v1.6.3
Project TypeOpen Source (China)Open Source (China)Open Source (International)Open Source (International)
LicenseMITApache-2BSD-3-ClauseMIT
Framework TypeModular FrameworkWeb FrameworkWeb "Framework"Web "Framework"
+

Basic Introduction

+
A comprehensive, easy-to-use, modular, high-quality, high-performance, + enterprise-level development framework.The most user-friendly enterprise-level Go application development + framework.The fastest growing Go Web framework. Provides complete MVC + functionality and is future-oriented.An HTTP Web framework written in Go. It provides Martini-style APIs + with better performance.
Project URL +

github.com/gogf/gf

+
github.com/beego/beegojackfan.us.kg/kataras/irisjackfan.us.kg/gin-gonic/gin
Official Websitegoframe.orgbeego.meiris-go.comgin-gonic.com
+

Modular Design

+
Yes---
Module Completeness10642
+

Ease of Use

+
99910
+

Documentation Completeness

+
10864
Engineering Completeness10851
Community Activity910910
Development ModeModule ImportThree-tier Architecture、MVCMVCMVC-
Engineering StandardsLayered Design, + Object DesignProject Structure--
Development Toolchaingf工具链bee工具链--
Web: Performance Test8889
Web: HTTPSHTTPS & TLSSupportedCustomHttpConfigurationSupported
Web: HTTP2--SupportedSupported
Web: WebSocketWebSocket服务YesYes-
Web: Group RoutingGroup Route RegistrationNamespaceGroupingRoutesYes
Web: Route Conflict HandlingYes-Yes-
Web: Domain SupportDomain Binding---
Web: File ServiceStatic File ServiceStatic File HandlingServingStaticFilesYes
Web: Multi-port/InstanceMulti-port ListeningMulti-instance Support-RunMultipleServiceUsingIris-
Web: Graceful Restart/ShutdownGraceful Restart FeatureHot UpgradeGracefulShutdownOrRestartGracefulRestartOrStop
ORMORM DocumentationORM Documentation--
SessionSessionSessionYes-
I18NSupportedI18NI18NLocalization-
Template EngineTemplate EngineView DesignTemplateRenderingHtmlRendering
Configuration ManagementConfiguration ManagementParameter Configuration-CustomHttpConfig
Logging ComponentLogging ComponentLogging--
Data ValidationData ValidationForm Data Validation-CustomValidators
Cache ManagementCache ManagementCache--
Resource PackagingResource Managementbee tool bale command--
Service TracingService Tracing---
Testing FrameworkUnit Testing-TestingTesting
Key Advantages + GoFrame primarily focuses on engineering and enterprise-level development, with excellent modular design and engineering design principles. For business projects, it provides development standards, project specifications, naming conventions, design patterns, development toolchain, rich modules, high-quality code and documentation, and has an active community. The author is also an experienced PHP developer, making PHP developers transitioning to Go feel at home. + + Beego was one of the earliest open-source frameworks, being the first comprehensive Golang development framework. It has maintained significant influence in the Golang ecosystem, with its author Xiemengjun organizing the influential GopherCN community events for many years. Beego offers rich development modules, is ready to use out of the box, provides MVC-based project structure, development toolchain, and primarily focuses on Web development, though it can also be used for non-Web projects. + Iris focuses mainly on Web development, providing a series of functional components for Web development based on the MVC pattern. Iris has developed rapidly this year, evolving from a Web Server component and gradually moving towards Beego's design direction.Gin focuses on being a lightweight Web Server that is simple and easy to understand. It features well-designed routing and middleware systems and can be considered an enhanced version of the standard net/http.Server with improved routing capabilities. It's a great choice for those who love building their own components.
Key DisadvantagesRelatively late to open source, takes a passive approach to promotion, currently mainly targets domestic users, and hasn't expanded internationally.Started early but development has slowed in recent years after the author's entrepreneurial ventures. Non-modular design with heavy reliance on third-party modules. +

Claims to have the best performance but results are average. Non-modular design. Has started moving towards Beego's direction in the last two years, but the overall framework capabilities are still incomplete and need improvement.

+
Simple and easy to use functionality, which is both an advantage and a disadvantage.
+ +Experience Sharing +==== + +Different scenarios require different choices. Choose the right tool to solve the right problem. + +In open source, there's no absolute better or worse. Open source authors sharing their technical achievements in the spirit of open source for learning and use is itself a commendable and valuable contribution. + +Finally, I'd like to share my team's experience and the challenges we faced during our transition to the `Golang` technology stack, hoping it can provide some reference for others in their framework selection process. + +Initial Pain Points +------ + +Here's some background on our team's transition to the `Golang` technology stack. The main points are: + +1. Initially, our backend team's primary technology stack was `PHP`. Due to business growth requirements, we underwent a microservices transformation. The first version of microservices used `PHP` + `JsonRpc` for communication. +2. As projects multiplied, the company formed its own `DevOps` team, shifting the underlying deployment to `Docker` + `Kubernetes` container architecture, and introduced the `Golang` technology stack. +3. Due to several pain points, after comparing `PHP` and `Golang` for a period, the team decided to quickly transition to the `Golang` stack. The main pain points were: + 1. `PHP` projects had higher overall development and maintenance costs in later stages when business logic became complex. This was mainly due to its excessive flexibility, unstructured variable design, and varying developer skill levels. + 2. After cloud containerization deployment, `PHP`'s `DevOps` efficiency was too low. Complex `Composer` version management and oversized `Docker` images affected `DevOps` efficiency. In comparison, `Golang` is extremely efficient. + 3. While the `JsonRpc` communication protocol design offered high interface extensibility and flexibility, it was difficult to quickly determine interface input and output definitions between services, relying solely on documentation and examples for integration and maintenance. Due to the separation of code and documentation, interface documentation often lagged behind interface changes. As services increased, the unstructured communication protocol management further raised the development and maintenance costs of service interfaces. + 4. The `JsonRpc` communication protocol, essentially based on `HTTP1.x` + `Json`, had low execution efficiency and couldn't be considered a true microservice communication protocol, making it difficult to integrate with mainstream service governance frameworks. In contrast, the `HTTP2.x`-based `gRPC` protocol comes with mature microservice development frameworks and service governance solutions. + 5. From a business perspective, the migration from `PHP` to `Golang` stack presented an opportunity for technical reconstruction, allowing us to review business system design and pay off technical debt during the process. + +Further Pain Points +------ + +`Golang` is indeed simple enough, and compared to other interpreted languages, it doesn't have excessive syntactic sugar and language features, which allowed our team to quickly get started and complete the technical reconstruction of some business systems. However, this was followed by more serious pain points. Here are the main ones: + +1. **Too Many Reinvented Wheels:** `Golang` is so simple that our team members unleashed their long-suppressed creative energy, fully embracing the "build and forget" mentality, developing/encapsulating numerous wheels of various sizes. These wheels could meet basic functionalities, such as logging, configuration, caching, etc. However, building a wheel is not just about implementing basic functionality as a half-finished product - it needs to ensure functionality, stability, extensibility, and maintainability. It should be validated through more production practices and requires long-term maintenance and continuous iterative improvements. Otherwise, they are just a collection of mismatched tools. Building wheels might feel good momentarily, but maintenance becomes a nightmare. Even now, we are still painfully working on unifying dozens of these scattered tools across more than `100` `Golang` projects. Of course, this issue is also closely related to organizational structure and team management. +2. **Lack of Systematic Approach:** + 1. We firmly believed in the principle of one `package` doing one thing, and specifically used **single repository packages** for package management, meaning each `package` was maintained as an independent `git` repository. In reality, there was no necessity for **single repository packages** and such `package` design; instead, independent single repository packages increased the maintenance cost of components and frameworks. + 2. This single repository package design made it difficult to form a cohesive technical system and establish a unified technical framework for team management. Single repository packages appeared isolated, while building a technical system requires not only establishing standards and specifications but also needs a technical framework for accurate implementation. A systematic, unified technical framework involves at least dozens of basic technical components and cannot be designed in complete isolation. While implementing basic functionality in each `package` is simple, organizing them cohesively is not an easy task. This requires technical managers with deep technical knowledge, vision, and foresight, rather than having the limited perspective of ordinary developers who only focus on individual `packages`. + 3. This isolated single repository package design provided weak standardization constraints for business projects, as each component could be independently replaced, which exacerbated the issues mentioned in point 1 (we even had several logging components, though they all met basic logging specification requirements). Eventually, our initially proud single repository package design turned into scattered pieces. For example, even adding standardized trace functionality became extremely costly to implement due to the scattered and inconsistent nature of single repository packages. + 4. Besides making it difficult to establish a technical system and accurately implement technical specifications, each component was also poorly designed in terms of usability. For example, our logging components, cache components, database components, and HTTP/gRPC Server components all needed to interface with configuration management functionality. Single repository packages needed to ensure low coupling design, so developers had to manually read configurations, convert them to target configuration objects, and inject them into corresponding component initialization methods before using these objects in business logic - steps that were repeated across multiple business projects. Actually, `goframe`'s usability design in this aspect is quite good. While each package is independently designed, under a unified technical framework system, it provides a coupled singleton module that encapsulates commonly used objects, automatically implementing configuration reading, configuration object conversion, configuration object injection, and component object initialization. Developers only need to call a single singleton method. This common singleton module became part of our technical framework system, greatly improving the development and maintenance efficiency of business projects. +3. **Version Inconsistency:** As business projects continued to multiply, the inconsistency in wheel versions became increasingly apparent. What does version inconsistency mean? Here's an example: We have a wheel called `httpClient` with about `10` versions released; we have more than `100` `Golang` projects, with almost every version being used somewhere. When we submit a `bug fix`, it's difficult to get all projects updated. Similar situations exist with other wheels, and we have dozens of various wheels being used independently across different projects. + +After reflection and analysis, we concluded the following points: + +1. The team needs a unified technical framework, not a hodgepodge of single repository packages. +2. We only need to maintain one framework version, not versions of dozens of single repository packages. +3. Framework components must be modular and loosely coupled, ensuring internal components can also be used independently. +4. Core components must strictly prohibit single repository package design and must be maintained uniformly by the framework. + +The Final Choice +----- + +After taking so many detours, we were determined to establish a systematic `Golang` development framework. Beyond requiring quick team learning and low maintenance costs, our primary demand was that core components couldn't be half-finished products - the framework had to be stable, mature, and validated in large-scale production environments. Subsequently, we re-evaluated popular technical frameworks in the industry, including those mentioned above. Our original intention was to unify our internal wheels into a systematic framework while looking for valuable references from open-source projects. + +Later, we found `goframe`, and after careful evaluation and study of its framework design, we discovered that its design philosophy aligned perfectly with our experience and conclusions! + +Here, I must mention an embarrassing situation. Actually, before switching to `Golang` (mid-2019), we did some research. At that time, `goframe`'s version wasn't very high, and our team members responsible for evaluation had a preconceived notion - seeing so many modules and documentation, they felt it must be complex and probably not performant, so they quickly passed on it without much investigation. Later, we chose some seemingly simple open-source wheels and did our own secondary encapsulation. + +This time, after a period of careful research and source code study, we reached a conclusion: `goframe`'s framework architecture, modularization, and engineering design philosophy are excellent, with high execution efficiency. The modules are not only rich but also of remarkably high quality! Compared to our previous half-finished wheels, it's like comparing an apprentice to a master. **It took the team over a year of stumbling through pitfalls to realize that we truly needed a unified technical framework rather than a pile of unsystematic wheels, when in fact, they had already paved a bright path ahead and had been quietly working on it all along.** + +After internal team research and discussion, we decided to gradually refactor our business projects using `goframe`. Since `goframe` is modularly designed, we could also make necessary replacements for some modules. The refactoring process went smoothly - restructuring the basic technical framework didn't affect business logic. Instead, through `goframe`'s engineering philosophy and excellent development toolchain, after unifying our technical framework, we greatly improved project development and maintenance efficiency, allowing the team to focus on business development. The department has also subsequently achieved more output. Currently, we have migrated most of our business projects to `goframe`. Our platform handles tens of millions of daily traffic. + +Finally, we thank the open-source authors for their quiet contributions! We are also working to encourage our team to embrace the spirit of sourcing from and giving back to the community, and we will participate more in community contributions in the future. + + + + + + + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" new file mode 100644 index 00000000000..ceb689b91ea Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" new file mode 100644 index 00000000000..9989fc1b717 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" @@ -0,0 +1,13 @@ +--- +slug: '/articles' +title: 'Community Articles' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Open Source, Community Articles, Articles, Community, Open Source Projects] +description: 'Community Articles' +--- + + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/QQ_1731756142305.png b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/QQ_1731756142305.png new file mode 100755 index 00000000000..0e499964019 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/QQ_1731756142305.png differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/architecture.png b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/architecture.png new file mode 100644 index 00000000000..768f3655437 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/architecture.png differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/coffee.jpg b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/coffee.jpg new file mode 100644 index 00000000000..8113f4f55f1 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/assets/coffee.jpg differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/proxima-book.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/proxima-book.md new file mode 100644 index 00000000000..fe07122b69c --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/proxima-book.md @@ -0,0 +1,37 @@ +--- +slug: '/course/proxima-book' +title: 'Microservice Tutorial - Proxima Book' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame, GoFrame Framework, Proxima Notebook, Microservices, gRPC, etcd, Service Registry, GoFrame Microservices, Golang Microservices, Golang Tutorial, Programming Tips, Project Development, Developer Guide, Tech Stack, Software Development, Computer Science] +description: 'This book uses the Proxima Notebook project as a practical example to help readers quickly master GoFrame microservices development. Perfect for developers who are familiar with GoFrame and want to advance into microservices development.' +--- + +## Introduction +--- +The **Proxima Notebook** is an intermediate-level practical tutorial for GoFrame. Unlike the beginner-level tutorial [Starbook](../starbook/starbook.md), this book focuses primarily on **microservices** development. + +## Motivation +--- +Many online tutorials overwhelm readers with technical jargon, complex architectural layers, and intimidating explanations. After spending hours reading through numerous articles, developers often find themselves unable to write a single line of code. + +As the saying goes, "Hearing is not as good as seeing, seeing is not as good as doing." Hands-on practice is the best way to master microservices. This book takes you on a journey from the basics of the `GoFrame` framework to building a real microservices project, demystifying the process along the way. You'll discover that developing microservices isn't as complicated as it seems. The real complexity lies not in the development itself, but in microservices governance. + +Our goal is to share professional, practical programming tips and experiences through a project-based approach, helping you achieve real mastery! + +## Target Audience +--- +This book is designed for developers who are already familiar with `GoFrame` and want to advance their skills in microservices development. + +## Contact the Author +--- +While writing this book, some errors or omissions are inevitable. If you have any questions or suggestions, feel free to leave a comment below or contact me directly. I'll respond as soon as possible! +- Email: `tyyn1022@gmail.com` `tyyn1022@163.com` +- Website: [https://oldme.net](https://oldme.net) +- WeChat: `NobodyIsRight` (Please mention your purpose when adding) + +## Troubleshooting +--- +Encountering problems during development is normal. The key is knowing how to solve them. When facing issues, try to resolve them independently first. Consulting the `GoFrame` documentation and using search engines are excellent problem-solving approaches. If you still can't find a solution, feel free to contact me, and I'll do my best to help. + +import DocCardList from '@theme/DocCardList'; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" new file mode 100644 index 00000000000..1335159a558 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" @@ -0,0 +1,37 @@ +--- +title: '1.1 Writing Conventions' +hide_title: true +slug: '/course/proxima-book/about-convention' +keywords: [GoFrame, proxima-book, writing convention, code examples, command line usage, code simplification, microservices development] +description: "This chapter introduces the writing conventions used in the GoFrame microservices tutorial, including code simplification principles, command line usage standards, and code omission explanations to help readers better understand the tutorial content." +--- + +## Simplified Code Examples +--- +If you're reading this book, you're likely already an experienced developer. Therefore, I'll minimize unnecessary verbosity and skip non-essential code details, **focusing primarily on the microservices development process and its key features.** + +## Command Line Usage +--- +Throughout this book, I'll use the `$` symbol as a command prompt - you don't need to type this symbol. For example, if you see `$ echo "Hello, GoFrame!"`, you should only type `echo "Hello, GoFrame!"`. + +```bash +$ echo "Hello, GoFrame!" +Hello, GoFrame! +``` + +## Code Omissions +--- +To keep the content concise and clean, I'll use `...` to indicate omitted code in vertical code blocks. + +```go +package main + +import "fmt" + +... + +func main() { + fmt.Println("Hello GoFrame") +} + +... diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..2c6b6b97648 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" @@ -0,0 +1,45 @@ +--- +title: '1.2 Architecture Overview' +hide_title: true +slug: '/course/proxima-book/about-arch' +keywords: [GoFrame, microservices, architecture design, API gateway, user service, word service, gRPC, HTTP, load balancing, authentication] +description: "An overview of the Proxima Notebook project's microservices architecture, including the functional separation of user and word services, and the role and responsibilities of the API gateway, detailing inter-service communication methods and core gateway functions." +--- + + +The **Proxima Notebook** is a lightweight application designed to help users learn English vocabulary. It includes the following features: +- User registration +- User login +- User information queries +- Word management (CRUD operations) + +We've organized these features into logical groups, resulting in two microservices: +- User Service: Handles user registration, login, and information queries +- Word Service: Provides word-related functionality, such as CRUD operations + +Rather than exposing microservices directly, all requests are routed through an API gateway. The gateway operates as a web service that doesn't implement business logic directly. Instead, it receives requests, forwards them to appropriate microservices, and aggregates responses to complete business operations. + +The gateway's responsibilities extend beyond protocol translation, including: +- Load balancing +- Authentication and authorization +- Logging +- Monitoring +- Rate limiting + +Microservices typically communicate using either HTTP or gRPC protocols. In this project, we'll be using gRPC. + +![](../assets/architecture.png) + +## Code Repository +--- +When breaking down a monolithic service into microservices, the code naturally separates as well. There are two common approaches to managing the code repository: + +- **Multirepo:** Each microservice has its own repository. + - Advantages: Smaller, more manageable repositories + - Disadvantages: Requires additional tools and processes to coordinate dependencies and versions between services + +- **Monorepo:** All microservices code lives in a single repository. + - Advantages: Unified version and dependency management + - Disadvantages: Repository can become large and complex to manage + +Our project uses the `Monorepo` approach. The `Multirepo` approach, with its one-service-per-directory structure, is straightforward enough to not require further explanation. diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..113db7c608b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,96 @@ +--- +title: '1.3 Environment Setup' +hide_title: true +slug: '/course/proxima-book/about-prepare' +keywords: [GoFrame, gRPC, Protocol Buffers, development environment, installation guide, etcd, microservices tools, Go installation] +description: "A comprehensive guide to setting up the development environment for GoFrame microservices projects, including Go language configuration, GoFrame framework installation, gRPC toolchain setup, and installation instructions for related dependencies." +--- + +Don't worry if your versions differ from mine - the principles remain largely the same. + +## GoFrame +--- +We'll skip the basic installation of `Golang` and `GoFrame`. Here are the versions used in this tutorial: +- `go version go1.23.4 windows/amd64` +- `goframe v2.8.2` + +## gRPC +--- +`gRPC` is a Remote Procedure Call (RPC) framework developed by Google, built on top of HTTP/2. It uses Protocol Buffers as its default serialization format. + +Go provides gRPC functionality through the `gRPC-go` plugin. Install it using these commands: +```bash +$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +``` + +### gRPC Testing Tools +After developing gRPC interfaces, you'll need testing tools to verify their functionality. Popular options include `Postman`, `Apifox`, and `Apipost`. They're all similar - choose the one you prefer. + +Throughout this book, we'll display test results in `json` format, like this: +```json +grpc 127.0.0.1:32001.account.v1.Account.UserRegister +{ + "username": "oldme", + "password": "123456", + "email": "tyyn1022@gmail.com" +} +{ + "id": 1 +} +``` + +These represent the request address, request parameters, and response parameters, respectively. + +## Protocol Buffers +--- +Protocol Buffers is Google's data serialization format for structured data. It uses `.proto` files to define message structures, which are then compiled into language-specific code. + +Download the appropriate version for your operating system from [Protocol Buffers Releases](https://github.com/protocolbuffers/protobuf/releases). For MacOS users, you can install dependencies using `brew`: + +```bash +$ brew install grpc protoc-gen-go protoc-gen-go-grpc +``` + +Verify the installation: +```bash +$ protoc --version +libprotoc 26.1 +``` + +## etcd +--- +etcd is a distributed key-value store commonly used for service discovery in distributed systems. There are several ways to install it. Here's a reference `docker-compose.yaml` file: + +```yaml +version: "3.7" + +services: + etcd: + image: "bitnami/etcd:3.5" + container_name: "etcd" + restart: "always" + ports: + - 2379:2379 + environment: + - TZ=Asia/Shanghai + - ALLOW_NONE_AUTHENTICATION=yes + - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 +``` + +If installed successfully, visiting [http://IP:2379/version](http://IP:2379/version) in your browser should display: +```json +{"etcdserver": "3.5.17","etcdcluster": "3.5.0"} +``` + +For a more advanced setup, like installing an etcd cluster or learning etcd basics, check out [this article](https://oldme.net/article/32). + +## Database +--- +MySQL installation is straightforward, and you can use other databases if preferred. + +Important: In a microservices architecture, each service should have its own database. We'll need to create two databases named `user` and `word`: + +```sql +CREATE DATABASE user; +CREATE DATABASE word; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 00000000000..904aa13fa58 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,70 @@ +--- +title: '1.4 Project Initialization' +hide_title: true +slug: '/course/proxima-book/about-init' +keywords: [GoFrame, project initialization, Monorepo, project structure, dependency management, go.mod, microservices setup] +description: "A detailed guide on initializing a microservices project using the GoFrame CLI tool, including creating a Monorepo repository, configuring dependencies, and setting up the project structure." +--- + +## Repository Initialization +--- +Use the following command to initialize a Monorepo repository named `proxima`: + +```bash +$ gf init proxima -m +``` + +Update the minimum Go version in your environment to be compatible with GoFrame's requirements. + +*go.mod* +```text +module proxima + +go 1.23.4 +``` + +Upgrade GoFrame to the latest version: +```bash +$ cd proxima +gf up +``` + +Remove unnecessary example files: +```bash +$ rm -rf app/* +``` + +The resulting project structure will look like this: +```text +app +hack + hack.mk + hack-cli.mk +utility +go.mod +go.sum +``` + +In Monorepo mode, the root directory only manages project dependencies and doesn't contain a `main.go` file. + +The `app` directory stores code for each microservice, such as `app/user/main.go` and `app/word/main.go`. + +## Installing Microservice Components +--- +Install the `grpcx` component to enable microservice development with GoFrame: +```bash +$ go get -u github.com/gogf/gf/contrib/rpc/grpcx/v2 +``` + +## Installing Database Drivers +--- +Like monolithic applications, you'll need to install the appropriate database driver. Here we'll demonstrate using MySQL: +```bash +$ go get -u github.com/gogf/gf/contrib/drivers/mysql/v2 +``` + +## Installing etcd Components +--- +Install the `etcd` component for service registration: +```bash +$ go get -u github.com/gogf/gf/contrib/registry/etcd/v2 diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" new file mode 100644 index 00000000000..f8bf47a2bb3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" @@ -0,0 +1,33 @@ +--- +title: '1.5 Source Code' +hide_title: true +slug: '/course/proxima-book/about-source' +keywords: [GoFrame, source code, GitHub repository, MIT license, open source, proxima project] +description: "Access the tutorial project's source code, including the GitHub repository link and detailed information about the MIT open source license." +--- + +The source code for this book is available on [https://github.com/oldme-git/proxima](https://github.com/oldme-git/proxima). + +The project is licensed under the `MIT` License: +```text +MIT License + +Copyright (c) 2024 oldme + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..34d58612188 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" @@ -0,0 +1,10 @@ +--- +title: 'Chapter 1 - Basic Information' +hide_title: true +sidebar_position: 1 +slug: '/course/proxima-book/about' +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..faf896fc5d9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,87 @@ +--- +title: '3.1 Prerequisites' +hide_title: true +slug: '/course/proxima-book/word-prepare' +keywords: [GoFrame, word service setup, microservice initialization, database configuration, project structure, service preparation] +description: "A detailed guide on initializing the Word Service, including creating the service using GoFrame CLI, configuring database connections, and setting up the project structure." +--- + +After successfully implementing our first microservice, developing the second one will be more straightforward as we're now familiar with the process. + +## Code Initialization +--- +Execute the following command to create a service named `word` in the `app` directory. + +```bash +$ gf init app/word -a +initializing... +initialization done! +you can now run "cd app/word && gf run main.go" to start your journey, enjoy! +``` + +Following the same process as before, remove the following files to start with a clean environment: +```text +app/word/api/* +app/word/internal/controller/* +app/word/internal/cmd/cmd.go +``` + +Navigate to the microservice directory to begin development: +```bash +$ cd app/word +``` + +## Generating Data Models +--- +### Creating Database Tables +Execute the following SQL statement in the `word` database to create the table for storing word data: +```sql +CREATE TABLE words ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + uid INT UNSIGNED NOT NULL, + word VARCHAR ( 255 ) NOT NULL, + definition TEXT, + example_sentence TEXT, + chinese_translation VARCHAR ( 255 ), + pronunciation VARCHAR ( 255 ), + created_at DATETIME, + updated_at DATETIME +); +``` + +### Generating DAO Models +*app/user/hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" + descriptionTag: true +``` + +```bash +$ gf gen dao +generated: D:\project\proxima\app\word\internal\dao\words.go +generated: D:\project\proxima\app\word\internal\dao\internal\words.go +generated: D:\project\proxima\app\word\internal\model\do\words.go +generated: D:\project\proxima\app\word\internal\model\entity\words.go +done! +``` + +### Generating Protocol Buffer Entity Models +*app/user/hack/config.yaml* +```bash +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" + descriptionTag: true + + pbentity: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" +``` + +```bash +$ gf gen pbentity +generated: D:\project\proxima\app\word\manifest\protobuf\pbentity\words.proto +done! diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" new file mode 100644 index 00000000000..e0de1fd3074 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" @@ -0,0 +1,38 @@ +--- +title: '3.2 Business Logic' +hide_title: true +slug: /course/proxima-book/word-logic +keywords: [GoFrame, business logic, word management, CRUD operations, microservices logic, vocabulary service] +description: "This section covers the implementation of core business logic for the Word Service, including basic functionalities such as word creation and retrieval." +--- + +Similar to our previous implementation, we'll create a simple example here. + +*app/word/internal/logic/words/words.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/os/gtime" + "proxima/app/word/internal/model/entity" +) + +func Create(ctx context.Context) (id uint, err error) { + return 1, nil +} + +func Get(ctx context.Context) (word *entity.Words, err error) { + return &entity.Words{ + Id: 1, + Uid: 1, + Word: "hello", + Definition: "used as a greeting when you meet somebody.", + ExampleSentence: "Hello, I am oldme!", + ChineseTranslation: "你好", + Pronunciation: "həˈləʊ", + CreatedAt: gtime.New("2024-12-05 22:00:00"), + UpdatedAt: gtime.New("2024-12-05 22:00:00"), + }, nil +} diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..0ff2c8afc63 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" @@ -0,0 +1,42 @@ +--- +title: '3.3 Protocol Files' +hide_title: true +slug: '/course/proxima-book/word-protocol' +keywords: [GoFrame, Protocol Buffers, gRPC, API definition, word service protocol, microservices communication] +description: "This section covers the Protocol Buffers protocol file definitions for the Word Service, including the design and implementation of interfaces for creating and retrieving words." +--- + +For simplicity, we'll focus on the basic `Create` and `Get` operations as examples, omitting update and delete operations. + +*app/word/manifest/protobuf/words/v1/words.proto* +```go +syntax = "proto3"; + +package words.v1; + +option go_package = "proxima/app/word/api/words/v1"; + +import "pbentity/words.proto"; + +service Words{ + rpc Create(CreateReq) returns (CreateRes) {} + rpc Get(GetReq) returns (GetRes) {} +} + +message CreateReq { + uint32 uid = 1; // v:required + string word = 2; // v:required + string definition = 3; // v:required +} + +message CreateRes { + uint32 id = 1; +} + +message GetReq { + uint32 id = 1; // v:required +} + +message GetRes { + pbentity.Words words = 1; +} \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..23a4019e658 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,64 @@ +--- +title: "3.4 Controllers" +hide_title: true +slug: '/course/proxima-book/word-controller' +keywords: [GoFrame, controller generation, gRPC controller, word service controller, protobuf generation, service implementation] +description: "A comprehensive guide on generating and implementing controllers for the Word Service, including using GoFrame's code generation tools and implementing specific business logic." +--- + +Execute the following command to generate the controllers: + +```bash +$ gf gen pb +``` + +Now, let's implement the logic for the word microservice: + +*app/word/internal/controller/words/words.go* +```go +package words + +import ( + "context" + + "proxima/app/word/api/pbentity" + v1 "proxima/app/word/api/words/v1" + "proxima/app/word/internal/logic/words" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" +) + +type Controller struct { + v1.UnimplementedWordsServer +} + +func Register(s *grpcx.GrpcServer) { + v1.RegisterWordsServer(s.Server, &Controller{}) +} + +func (*Controller) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + id, err := words.Create(ctx) + if err != nil { + return nil, err + } + return &v1.CreateRes{Id: uint32(id)}, nil +} + +func (*Controller) Get(ctx context.Context, req *v1.GetReq) (res *v1.GetRes, err error) { + data, err := words.Get(ctx) + if err != nil { + return nil, err + } + return &v1.GetRes{ + Words: &pbentity.Words{ + Id: uint32(data.Id), + Uid: uint32(data.Uid), + Word: data.Word, + Definition: data.Definition, + ExampleSentence: data.ExampleSentence, + ChineseTranslation: data.ChineseTranslation, + Pronunciation: data.Pronunciation, + CreatedAt: timestamppb.New(data.CreatedAt.Time), + UpdatedAt: timestamppb.New(data.CreatedAt.Time), + }, + }, nil +} diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..bd6cb04f113 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,132 @@ +--- +title: '3.5 Service Startup' +hide_title: true +slug: '/course/proxima-book/word-run' +keywords: [GoFrame, gRPC service, word service startup, microservices deployment, service registration, etcd integration] +description: "A comprehensive guide on starting and running the Word microservice, including service registration configuration, gRPC service setup, and health check implementation." +--- + +## Importing Controllers in CMD +--- +*app/word/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/os/gcmd" + "google.golang.org/grpc" + "proxima/app/word/internal/controller/words" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "word grpc service", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + c := grpcx.Server.NewConfig() + c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., + ) + s := grpcx.Server.New(c) + words.Register(s) + s.Run() + return nil + }, + } +) +``` + +## Main Entry File +--- +Import the database driver and `cmd` in the main entry file. + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/word/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +## Configuration File +--- +*app/user/manifest/config/config.yaml* +```yaml +grpc: + name: "word" + address: ":32002" + +database: + default: + link: "mysql:root:12345678@tcp(srv.com:3306)/word" + debug: true +``` + +## Starting the Service +--- +Ensure all dependencies are properly installed, then run the word microservice. + +```bash +$ cd app/word +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 2416 +2024-12-09 15:10:40.546 [DEBU] {18cc6c8aa5700f18bf2deb5e3439664a} set default registry using file registry as no custom registry set, path: C:\Users\half\AppData\Local\Temp\gsvc +2024-12-09 15:10:40.566 [DEBU] {18cc6c8aa5700f18bf2deb5e3439664a} service register: &{Head: Deployment: Namespace: Name:word Version: Endpoints:192.168.10.98:32002 Metadata:map[protocol:grpc]} +2024-12-09 15:10:40.567 [INFO] {18cc6c8aa5700f18bf2deb5e3439664a} pid[2416]: grpc server started listening on [:32002] +``` + +With this, we've completed the development of the second microservice for Proxima Notebook. + +## Testing Results +--- +```json +grpc 127.0.0.1:32002.words.v1.Words.Create +{ + "uid": 1, + "word": "hello", + "definition": "used as a greeting when you meet somebody." +} +{ + "id": 1 +} + +grpc 127.0.0.1:32002.words.v1.Words.Get +{ + "id": 1 +} +{ + "words": { + "Id": 1, + "Uid": 1, + "Word": "hello", + "Definition": "used as a greeting when you meet somebody.", + "ExampleSentence": "Hello, I am oldme!", + "ChineseTranslation": "你好", + "Pronunciation": "həˈləʊ", + "CreatedAt": { + "seconds": "1733407200", + "nanos": 0 + }, + "UpdatedAt": { + "seconds": "1733407200", + "nanos": 0 + } + } +} diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..1013ffa1cbe --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" @@ -0,0 +1,55 @@ +--- +title: '3.6 Service Registration' +hide_title: true +slug: '/course/proxima-book/word-etcd-register' +keywords: [GoFrame, etcd, service registration, word service discovery, microservices registry, configuration] +description: "Learn how to register the Word microservice with the etcd service registry, including configuration file setup and registration logic implementation." +--- + +Add the configuration file: + +*app/word/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +Add the registration logic to the entry file: + +*app/word/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/word/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +Restart the project, enter the `etcd` container again, and execute the following command to verify: +```bash +$ etcdctl get "" --prefix --keys-only +/service/default/default/user/latest/{IP}:32001 +/service/default/default/word/latest/{IP}:32002 +``` + +As we can see, both of our microservices have been successfully registered. Next, we can run the gateway to start calling these services. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..43cdcfbba1a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 3 - Word Service' +hide_title: true +sidebar_position: 3 +slug: '/course/proxima-book/word' +keywords: [GoFrame, word service, vocabulary management, microservices, CRUD operations, English learning] +description: "This chapter provides a detailed guide on implementing the Word Service using the GoFrame framework, covering core functionalities such as CRUD operations for vocabulary management and integration with other microservices." +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..373963c4dd0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,94 @@ +--- +title: '2.1 Prerequisites' +hide_title: true +slug: '/course/proxima-book/user-overview' +keywords: [GoFrame, microservice setup, user service initialization, database configuration, project structure, service preparation] +description: "This section covers the prerequisites for the user service, including service initialization using GoFrame CLI, database configuration, and project structure setup." +--- + +Most aspects of microservice development are similar to monolithic services, and in many cases, even simpler. This chapter covers some basic preparations that should be familiar to everyone. + +## Code Initialization +--- +GoFrame provides a command for initializing microservice repositories. Execute the following command to create a service named `user` in the `app` directory. + +```bash +$ gf init app/user -a +initializing... +initialization done! +you can now run "cd app/user && gf run main.go" to start your journey, enjoy! +``` + +After successful initialization, a microservice will be created in the `app` directory. It's similar to a monolithic service, except that it lacks the `go.mod` and `go.sum` files. + +Delete the following files to start with a clean environment: +```text +app/user/api/* +app/user/internal/controller/* +app/user/internal/cmd/cmd.go +``` + +After completing these steps, enter the microservice repository to begin development. +```bash +$ cd app/user +``` + +## Database Setup +--- +### Creating Tables +In the `user` database, execute the following SQL statement to create a table for storing user data: +```sql +CREATE TABLE users ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + password CHAR(32) NOT NULL, + email VARCHAR(100), + created_at DATETIME, + updated_at DATETIME +); +``` + +### Generating DAO Models +*app/user/hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" + descriptionTag: true +``` + +```bash +$ gf gen dao +generated: D:\project\proxima\app\user\internal\dao\users.go +generated: D:\project\proxima\app\user\internal\dao\internal\users.go +generated: D:\project\proxima\app\user\internal\model\do\users.go +generated: D:\project\proxima\app\user\internal\model\entity\users.go +done! +``` + +> Note: Execute the `gf gen dao` command in the microservice repository (i.e., the `app/user` directory). Be careful not to get this wrong. Similar rules apply to other related operations. + +### Generating Protocol Buffer Entity Models +*app/user/hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" + descriptionTag: true + + pbentity: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" +``` + +```bash +$ gf gen pbentity +generated: D:\project\proxima\app\user\manifest\protobuf\pbentity\users.proto +done! +``` + +### Differences Between `gen dao` and `gen pbentity` + +- `gen dao` generates Go files primarily used within the microservice, such as for ORM operations +- `gen pbentity` generates Protocol Buffer files mainly used for gRPC communication between microservices diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" new file mode 100644 index 00000000000..89af8801467 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" @@ -0,0 +1,41 @@ +--- +title: '2.2 Business Logic' +hide_title: true +slug: '/course/proxima-book/user-logic' +keywords: [GoFrame, business logic, user registration, account management, microservices logic, user service implementation] +description: "Detailed explanation of business logic implementation in the user service, including core functionality code implementation and best practices for user registration and management." +--- + +Like monolithic services, microservice business logic is stored in the `*/internal/logic` directory. As we're all experienced developers, I'll keep this simple and provide a basic example. + +*app/user/internal/logic/account/account.go* +```go +package account + +import ( + "context" + + "github.com/gogf/gf/v2/os/gtime" + "proxima/app/user/internal/dao" + "proxima/app/user/internal/model/entity" +) + +func Register(ctx context.Context) (id int, err error) { + return 1, nil +} + +func Login(ctx context.Context) (token string, err error) { + return "I am token", nil +} + +// Info get user info +func Info(ctx context.Context, token string) (user *entity.Users, err error) { + return &entity.Users{ + Id: 1, + Username: "oldme", + Password: "123456", + Email: "tyyn1022@gmail.com", + CreatedAt: gtime.New("2024-12-05 22:00:00"), + UpdatedAt: gtime.New("2024-12-05 22:00:00"), + }, nil +} \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..bbba7a6803b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" @@ -0,0 +1,109 @@ +--- +title: '2.3 Protocol Files' +hide_title: true +slug: '/course/proxima-book/user-protocol' +keywords: [GoFrame, Protocol Buffers, gRPC, API definition, microservices communication, proto files, user service protocol] +description: "Introduction to Protocol Buffers protocol file definitions in the user service, including protocol design for user registration, login interfaces, and gRPC service definition best practices." +--- + +Protocol files refer to `*.proto` files. Proto is the standard communication protocol for gRPC, similar to how JSON relates to HTTP. However, it's essential to note that proto and JSON have distinct differences: proto defines both "interface" information and response/request parameters, while JSON simply stores data. + +Proto files are stored uniformly under `manifest/protobuf`, and like regular HTTP services, interface versions are managed through directory hierarchies. + +## User Registration +--- +Create a directory named `account` to manage user account-related business logic. + +*app/user/manifest/protobuf/account/v1/account.proto* +```proto +syntax = "proto3"; + +package account.v1; + +option go_package = "proxima/app/user/api/account/v1"; + +service Account{ + rpc UserRegister(UserRegisterReq) returns (UserRegisterRes) {} +} + +message UserRegisterReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 + string email = 3; // v:required|email +} + +message UserRegisterRes { + int32 id = 1; +} +``` + +Let's briefly explain the proto syntax: +- **syntax**: Specifies the file's syntax version +- **package**: Defines the service namespace, similar to a package name +- **option**: Sets compilation options; `go_package` specifies the package name for generated Go code. *In GoFrame, the fixed format is `project_name + app + microservice_name + api + module_name + v1`* +- **service**: Defines remote call methods, typically RPC, specifying request and response parameters +- **message**: Defines data structures, where `string` is the data type, `username` is the field name, and the incremental numbers after the equals sign are field numbers. *The trailing comments are framework-provided parameter validations, used similarly to regular HTTP interfaces* + +Our file defines: +- Uses proto3 syntax version +- Defines package name as `account.v1` +- Sets the Go code generation package path option `go_package` to `proxima/app/user/api/account/v1` +- Defines an `Account` service with one RPC method `UserRegister` that accepts `UserRegisterReq` message and returns `UserRegisterRes` message +- Defines a message type `UserRegisterReq` with three fields: + - `username` (string type, number 1) + - `password` (string type, number 2) + - `email` (string type, number 3) +- Defines a message type `UserRegisterRes` with one field: + - `id` (integer type, number 1) + +## User Login/Query +--- +Following the same pattern, let's define the user login and query interfaces. Here's the complete file content: + +*app/user/manifest/protobuf/account/v1/account.proto* +```proto +syntax = "proto3"; + +package account.v1; + +option go_package = "proxima/app/user/api/account/v1"; + +import "pbentity/users.proto"; + +service Account{ + rpc UserRegister(UserRegisterReq) returns (UserRegisterRes) {} + rpc UserLogin(UserLoginReq) returns (UserLoginRes) {} + rpc UserInfo(UserInfoReq) returns (UserInfoRes) {} +} + +message UserRegisterReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 + string email = 3; // v:required|email +} + +message UserRegisterRes { + int32 id = 1; +} + +message UserLoginReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 +} + +message UserLoginRes { + string token = 1; +} + +message UserInfoReq { + string token = 1; // v:required +} + +message UserInfoRes { + pbentity.Users user = 1; +} +``` + +This introduces two new syntax elements: +- `import "pbentity/users.proto"`: Imports another proto file. This file was generated by `gf gen pbentity` +- `pbentity.Users user`: Uses the imported data model, which is almost identical to Go structs \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..06e335b015f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,74 @@ +--- +title: "2.4 Controllers" +hide_title: true +slug: '/course/proxima-book/user-controller' +keywords: [GoFrame, controller generation, gRPC controller, protobuf generation, microservices controller, service implementation] +description: "A detailed guide on using GoFrame's code generation tools to generate gRPC controllers and implementing various functional interfaces for the user service." +--- + +While HTTP service controllers are generated using `gf gen ctrl`, microservices also have controllers, generated using `gf gen pb`. + +```bash +$ gf gen pb +``` + +The `gen pb` command requires all dependencies to be properly set up. When executed successfully, it generates several Go files. We only need to focus on the controller files; the framework maintains the rest. The subsequent development process is similar to HTTP services - calling `logic` functions. + +*app/user/internal/controller/account/account.go* +```go +package account + +import ( + "context" + + "google.golang.org/protobuf/types/known/timestamppb" + v1 "proxima/app/user/api/account/v1" + "proxima/app/user/api/pbentity" + "proxima/app/user/internal/logic/account" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" +) + +type Controller struct { + v1.UnimplementedAccountServer +} + +func Register(s *grpcx.GrpcServer) { + v1.RegisterAccountServer(s.Server, &Controller{}) +} + +func (*Controller) UserRegister(ctx context.Context, req *v1.UserRegisterReq) (res *v1.UserRegisterRes, err error) { + id, err := account.Register(ctx) + if err != nil { + return nil, err + } + return &v1.UserRegisterRes{ + Id: int32(id), + }, nil +} + +func (*Controller) UserLogin(ctx context.Context, req *v1.UserLoginReq) (res *v1.UserLoginRes, err error) { + token, err := account.Login(ctx) + if err != nil { + return nil, err + } + return &v1.UserLoginRes{ + Token: token, + }, nil +} + +func (*Controller) UserInfo(ctx context.Context, req *v1.UserInfoReq) (res *v1.UserInfoRes, err error) { + data, err := account.Info(ctx, req.Token) + if err != nil { + return nil, err + } + return &v1.UserInfoRes{ + User: &pbentity.Users{ + Id: uint32(data.Id), + Username: data.Username, + Password: data.Password, + Email: data.Email, + CreatedAt: timestamppb.New(data.CreatedAt.Time), + UpdatedAt: timestamppb.New(data.UpdatedAt.Time), + }, + }, nil +} diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..ae213b86942 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,152 @@ +--- +title: '2.5 Service Startup' +hide_title: true +slug: '/course/proxima-book/user-run' +keywords: [GoFrame, gRPC service, service startup, microservices deployment, service registration, etcd integration] +description: "A comprehensive guide on starting and running the user microservice, including service registration, gRPC service configuration, etcd integration, and service health checks." +--- + +## Importing Controllers in CMD +--- +Like monolithic services, microservices also need to be imported in the `cmd`. The difference is that the service startup changes from `HTTP` to `gRPC`. + +*app/user/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/os/gcmd" + "google.golang.org/grpc" + "proxima/app/user/internal/controller/account" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "user grpc service", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + c := grpcx.Server.NewConfig() + c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., + ) + s := grpcx.Server.New(c) + account.Register(s) + s.Run() + return nil + }, + } +) +``` + +## Main Entry File +--- +Import the database driver and `cmd` in the main entry file. + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/user/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +## Configuration File +--- +*app/user/manifest/config/config.yaml* +```yaml +grpc: + name: "user" + address: ":32001" + +database: + default: + link: "mysql:root:12345678@tcp(srv.com:3306)/user" + debug: true +``` + +The `grpc` field defines two essential parameters: the microservice name and the listening port. The service name is used for service registration, and the listening port is self-explanatory. These two are mandatory; for other configuration options, refer to the [configuration template](../../../docs/微服务开发/服务端配置.md). + +## Starting the Service +--- +Switch to the root directory and ensure all dependencies are properly installed. + +```bash +$ cd ../../ +go mod tidy +``` + +Return to the microservice directory and start the user microservice. + +```bash +$ cd app/user +gf run .\main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 15480 +2024-12-06 15:02:01.246 [DEBU] {d8e6fef56e840e1815d0325bc73eda8f} set default registry using file registry as no custom registry set, path: C:\Users\half\AppData\Local\Temp\gsvc +2024-12-06 15:02:01.269 [DEBU] {d8e6fef56e840e1815d0325bc73eda8f} service register: &{Head: Deployment: Namespace: Name:user Version: Endpoints:192.168.10.91:32001 Metadata:map[protocol:grpc]} +2024-12-06 15:02:01.270 [INFO] {d8e6fef56e840e1815d0325bc73eda8f} pid[15480]: grpc server started listening on [:32001] +``` + +With this, we've completed the development of the first microservice for Proxima Notebook, which isn't much different from developing a monolithic service. + +## Testing Results +--- +> When testing gRPC in your testing tool, you'll need to use the proto protocol file. Make sure to specify the correct dependency paths. + +```json +grpc 127.0.0.1:32001.account.v1.Account.UserRegister +{ + "username": "oldme", + "password": "123456", + "email": "tyyn1022@gmail.com" +} +{ + "id": 1 +} + +grpc 127.0.0.1:32001.account.v1.Account.UserLogin +{ + "username": "oldme", + "password": "123456" +} +{ + "token": "I am token" +} + +grpc 127.0.0.1:32001.account.v1.Account.UserInfo +{ + "token": "I am token" +} +{ + "user": { + "Id": 1, + "Username": "oldme", + "Password": "123456", + "Email": "tyyn1022@gmail.com", + "CreatedAt": { + "seconds": "1733407200", + "nanos": 0 + }, + "UpdatedAt": { + "seconds": "1733407200", + "nanos": 0 + } + } +} diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..3bde477246d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" @@ -0,0 +1,65 @@ +--- +title: '2.6 Service Registration' +hide_title: true +slug: '/course/proxima-book/user-etcd-register' +keywords: [GoFrame, etcd, service registration, service discovery, microservices registry, configuration management] +description: "A guide on registering the user microservice with etcd service registry, including configuration file setup, registration logic implementation, and service discovery mechanism configuration." +--- + +Next, we'll register the user microservice with `etcd` to make it available for other services to call. + +Add a configuration file with the `etcd` access address. + +*app/user/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +Add the registration logic in the entry file: + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/user/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +In fact, the key code for service registration is just one line, while the rest is code for reading the configuration file: +```go +grpcx.Resolver.Register(etcd.New(address)) +``` + +Restart the project to apply the changes. Then enter the `etcd` container and execute the following command to verify the registration: +```bash +$ etcdctl get "" --prefix --keys-only +``` + +This command shows all existing `keys` in `etcd`, where we should see our registered service: +```text +/service/default/default/user/latest/{IP}:32001 +``` + +> Service registration can be understood as similar to DNS name resolution. The service name `grpc.name` in the configuration file can be thought of as analogous to a domain name. diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..c1506b36b2c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 2 - User Service' +hide_title: true +sidebar_position: 2 +slug: '/course/proxima-book/user' +keywords: [GoFrame, user service, microservices, authentication, user management, registration, login, user information] +description: "This chapter provides a detailed guide on implementing a user service using the GoFrame framework, covering core functionalities such as user registration, login, and information retrieval." +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" new file mode 100644 index 00000000000..dc224819e28 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" @@ -0,0 +1,25 @@ +--- +title: Chapter 5 - Further Learning +hide_title: true +sidebar_position: 99 +slug: /course/proxima-book/appendix +keywords: [GoFrame, microservices learning, authentication, load balancing, service configuration, interceptors, future directions] +description: "This chapter summarizes advanced learning paths for GoFrame microservices development, covering key topics such as user authentication, multi-service calls, interceptor usage, and load balancing strategies." +--- + +First, thank you to every reader who has patiently completed this book! Are your three services running smoothly, like Proxima and its two companions? +Next, here are some additional learning directions for everyone. As the author's knowledge is limited, please forgive any shortcomings. I hope your career continues to rise and flourish! + +## Further Learning Directions +--- +- User Authentication & Authorization: Enhance user authentication in microservices and implement it in the gateway service +- Multi-Service Integration: Call multiple microservices within a single controller to complete business functions +- Server-Side Interceptors: Use the gRPC server interceptors provided by `GoFrame` +- Load Balancing: Understand microservices load balancing strategies +- Service Configuration Management: Integrate with configuration centers to provide more flexible configuration options for microservices + +## Buy Me a Coffee +--- +**Thank you to all supporters!** + +![Feature List](../assets/coffee.jpg) diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..ce8417426e9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,31 @@ +--- +title: '4.1 Prerequisites' +hide_title: true +slug: '/course/proxima-book/gateway-prepare' +keywords: [GoFrame, gateway initialization, API Gateway setup, microservices gateway, project structure] +description: "A guide on initializing the API Gateway, including creating the gateway service using GoFrame CLI and setting up the project structure." +--- + +The API Gateway is similar to a monolithic web service, with the main difference being that the concrete business logic is now implemented through microservice calls. + +## Code Initialization +--- +Execute the following command to create a service named `gateway` in the `app` directory. + +```bash +$ gf init app/gateway -a +initializing... +initialization done! +you can now run "cd app/gateway && gf run main.go" to start your journey, enjoy! +``` + +Remove the following files to start with a clean environment: +```text +app/word/api/* +app/word/internal/controller/* +app/word/internal/cmd/cmd.go +``` + +Navigate to the repository to begin development: +```bash +$ cd app/gateway \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..3f0102ca536 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,79 @@ +--- +title: "4.2 APIs and Controllers" +hide_title: true +slug: '/course/proxima-book/gateway-controller' +keywords: [GoFrame, API design, gateway controller, HTTP endpoints, request validation, response handling] +description: "A detailed guide on designing APIs and implementing controllers for the API Gateway, including the definition and request handling logic for user login, registration, and other endpoints." +--- + +This step should be familiar to everyone, so we'll keep the explanation brief. + +## API +--- +*app/gateway/api/user/v1/user.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type LoginReq struct { + g.Meta `path:"users/login" method:"post" sm:"Login" tags:"User"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` +} + +type LoginRes struct { + Token string `json:"token" dc:"Add Authorization: token in header for authenticated endpoints"` +} +``` + +*app/gateway/api/words/v1/words.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +type CreateReq struct { + g.Meta `path:"words" method:"post" sm:"Create" tags:"Word"` + Word string `json:"word" v:"required|length:1,100" dc:"Word"` + Definition string `json:"definition" v:"required|length:1,300" dc:"Word definition"` +} + +type CreateRes struct { +} + +type DetailReq struct { + g.Meta `path:"words/{id}" method:"get" sm:"Details" tags:"Word"` + Id uint `json:"id" v:"required"` +} + +type DetailRes struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ExampleSentence string `json:"exampleSentence"` + ChineseTranslation string `json:"chineseTranslation"` + Pronunciation string `json:"pronunciation"` + CreatedAt *gtime.Time `json:"createdAt"` + UpdatedAt *gtime.Time `json:"updatedAt"` +} +``` + +## Controller +--- +Execute the following command to generate controllers: +```bash +$ gf gen ctrl +generated: D:\project\proxima\app\gateway\api\user\user.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user_new.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user_v1_login.go +generated: D:\project\proxima\app\gateway\api\words\words.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_new.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_v1_create.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_v1_detail.go +done! diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..1020ade87ee --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,132 @@ +--- +title: "4.3 Service Startup" +hide_title: true +slug: '/course/proxima-book/gateway-run' +keywords: [GoFrame, gateway configuration, service startup, HTTP server, OpenAPI, Swagger, logging setup] +description: "Detailed instructions on configuring and starting the API Gateway service, including server configuration, OpenAPI documentation, logging setup, and other key components." +--- + +Let's start by running the service before we call the microservices. + +## Configuration Files +--- +*app/gateway/manifest/config/config.yaml* +```yaml +server: + address: ":8000" + openapiPath: "/api.json" + swaggerPath: "/swagger" + logger: + path: "./log" + file: "{Y-m-d}.log" + level: "all" + stdout: true +``` + +*app/gateway/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +## Command File +--- +*app/gateway/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "proxima/app/gateway/internal/controller/user" + "proxima/app/gateway/internal/controller/words" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http gateway server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Group("/", func(group *ghttp.RouterGroup) { + group.Bind(user.NewV1()) + group.Bind(words.NewV1()) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +## Starting the Service +--- +*app/gateway/main.go* +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "proxima/app/gateway/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +```bash +$ gf run .\main.go +build running pid: 16144 +2024-12-10 15:30:30.788 [INFO] pid[16144]: http server started listening on [:8000] +2024-12-10 15:30:30.788 [INFO] {f0f10d9d4ec00f181b7a6f615f39d54b} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-12-10 15:30:30.789 [INFO] {f0f10d9d4ec00f181b7a6f615f39d54b} openapi specification is serving at address: http://127.0.0.1:8000/api.json +2024-12-10 15:30:30.817 [DEBU] {f0f10d9d4ec00f181b7a6f615f39d54b} service register: &{Head: Deployment: Namespace: Name:default Version: Endpoints:192.168.10.98:8000 Metadata:map[insecure:true protocol:http]} +2024-12-10 15:30:30.900 [DEBU] {f0f10d9d4ec00f181b7a6f615f39d54b} etcd put success with key "/service/default/default/default/latest/192.168.10.98:8000", value "{"insecure":true,"protocol":"http"}", lease "7587883327293376805" + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | POST | /v1/users/login | proxima/app/gateway/internal/controller/user.(*ControllerV1).Login | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | POST | /v1/words | proxima/app/gateway/internal/controller/words.(*ControllerV1).Create | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | GET | /v1/words/{id} | proxima/app/gateway/internal/controller/words.(*ControllerV1).Detail | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + +``` + +Entering the `etcd` container and executing the command to view: +```bash +$ etcdctl get "" --prefix --keys-only + +/service/default/default/default/latest/{IP}:8000 +/service/default/default/word/latest/{IP}:32001 +/service/default/default/word/latest/{IP}:32002 +``` + +As we can see, all three of our services have been successfully registered. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" new file mode 100644 index 00000000000..f29adf72b97 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" @@ -0,0 +1,140 @@ +--- +title: "4.4 Implementing the gRPC Client" +hide_title: true +slug: '/course/proxima-book/gateway-client' +keywords: [GoFrame, gRPC client, microservices communication, client configuration, service discovery, etcd integration] +description: "A detailed guide on implementing and configuring gRPC clients in the API Gateway for communication with microservices, including client initialization and service discovery functionality." +--- + +## Client Implementation +--- +The API Gateway acts as a gRPC client, while each microservice acts as a gRPC server. We'll define the gRPC client in our controller properties for later use. + +The client is defined using `grpcx.Client.MustNewGrpcClientConn(service, opts...)`. + +*app/gateway/internal/controller/user/user_new.go* +```go +package user + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/user" + v1 "proxima/app/user/api/account/v1" +) + +type ControllerV1 struct { + AccountClient v1.AccountClient +} + +func NewV1() user.IUserV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("user") + + return &ControllerV1{ + AccountClient: v1.NewAccountClient(conn), + } +} +``` + +*app/gateway/internal/controller/words/words_new.go* +```go +package words + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/words" + v1 "proxima/app/word/api/words/v1" +) + +type ControllerV1 struct { + WordsClient v1.WordsClient +} + +func NewV1() words.IWordsV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("word") + + return &ControllerV1{ + WordsClient: v1.NewWordsClient(conn), + } +} +``` + +## Interceptors +--- +Currently, our client doesn't have timeout handling, and gRPC's default timeout threshold is very high. If the gRPC server, etcd service, or network encounters issues, the API Gateway could hang indefinitely. Let's add a timeout interceptor to handle these situations. + +### Defining the Interceptor +The timeout mechanism is simple, implemented using Go's context. + +*app/gateway/utility/grpc.go* +```go +package utility + +import ( + "context" + "time" + "google.golang.org/grpc" +) + +func GrpcClientTimeout(ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, +) error { + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + + err := invoker(ctx, method, req, reply, cc, opts...) + return err +} +``` + +### Using the Interceptor + +*app/gateway/internal/controller/user/user_new.go* +```go +package user + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/user" + "proxima/app/gateway/utility" + v1 "proxima/app/user/api/account/v1" +) + +type ControllerV1 struct { + AccountClient v1.AccountClient +} + +func NewV1() user.IUserV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("user", grpcx.Client.ChainUnary( + utility.GrpcClientTimeout, + )) + + return &ControllerV1{ + AccountClient: v1.NewAccountClient(conn), + } +} +``` + +*app/gateway/internal/controller/words/words_new.go* +```go +package words + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/words" + "proxima/app/gateway/utility" + v1 "proxima/app/word/api/words/v1" +) + +type ControllerV1 struct { + WordsClient v1.WordsClient +} + +func NewV1() words.IWordsV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("word", grpcx.Client.ChainUnary( + utility.GrpcClientTimeout, + )) + + return &ControllerV1{ + WordsClient: v1.NewWordsClient(conn), + } +} \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..c267f597b00 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" @@ -0,0 +1,123 @@ +--- +title: "4.5 Calling Microservices" +hide_title: true +slug: '/course/proxima-book/gateway-call' +keywords: [GoFrame, microservices integration, service invocation, gRPC communication, gateway implementation, service orchestration] +description: "A detailed guide on integrating and calling microservices from the API Gateway, including implementations for both user and word services, and the encapsulation of business logic." +--- + +Next, we'll implement the microservices calls within our controllers to handle specific business logic. + +> In real-world development, complex business logic should be encapsulated in the `logic` layer, similar to monolithic web services, and then called by controllers. + +## User Service +--- +*app/gateway/internal/controller/user/user_v1_login.go* +```go +package user + +import ( + "context" + + account "proxima/app/user/api/account/v1" + + "proxima/app/gateway/api/user/v1" +) + +func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) { + user, err := c.AccountClient.UserLogin(ctx, &account.UserLoginReq{ + Username: req.Username, + Password: req.Password, + }) + + if err != nil { + return nil, err + } + + return &v1.LoginRes{ + Token: user.GetToken(), + }, nil +} +``` + +Let's test the gateway-to-microservice communication with a request: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "oldme", + "password": "12345678" + }' + +{ + "code": 0, + "message": "", + "data": { + "token": "I am token" + } +} +``` + +Congratulations on seeing this response! You can now try implementing the other services as well. Here's the source code for reference. + +## Word Service +--- +*app/gateway/internal/controller/words/words_v1_create.go* +```go +package words + +import ( + "context" + + words "proxima/app/word/api/words/v1" + + "proxima/app/gateway/api/words/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + _, err = c.WordsClient.Create(ctx, &words.CreateReq{ + Uid: 1, + Word: req.Word, + Definition: req.Definition, + }) + + if err != nil { + return nil, err + } + + return &v1.CreateRes{}, nil +} +``` + +*app/gateway/internal/controller/words/words_v1_detail.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + words "proxima/app/word/api/words/v1" + + "proxima/app/gateway/api/words/v1" +) + +func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) { + word, err := c.WordsClient.Get(ctx, &words.GetReq{ + Id: uint32(req.Id), + }) + + if err != nil { + return nil, err + } + + if word == nil { + return nil, gerror.New("word not found") + } + + return &v1.DetailRes{ + Id: uint(word.Words.Id), + Word: word.Words.Word, + Definition: word.Words.Definition, + }, nil +} \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" new file mode 100644 index 00000000000..f130b4eacba --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 4 - API Gateway' +hide_title: true +sidebar_position: 4 +slug: '/course/proxima-book/gateway' +keywords: [GoFrame, API Gateway, microservices gateway, service orchestration, request routing, load balancing] +description: "This chapter provides a comprehensive guide on implementing an API Gateway using the GoFrame framework, covering core functionalities such as request routing, service orchestration, and load balancing." +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/coffee.jpg b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/coffee.jpg new file mode 100755 index 00000000000..8113f4f55f1 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/coffee.jpg differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/mvc.png b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/mvc.png new file mode 100755 index 00000000000..79be3768448 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/mvc.png differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/mvc.svg b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/mvc.svg new file mode 100755 index 00000000000..83e2c0869e5 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/mvc.svg @@ -0,0 +1,4 @@ + + + +
View
Controller
Model
\ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" new file mode 100644 index 00000000000..112f4dff1ea --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" @@ -0,0 +1,186 @@ +思维导图

Starbook

User

Register

Login

Get userinfo

Word

Create word

Update Word

Get word list

Get word

Delete word

Learn word

Get a random number of words

Set proficiency level

\ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" new file mode 100755 index 00000000000..e1d3594df45 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" @@ -0,0 +1 @@ +
Request
Api
Response
Input
Controller
Output
Do
Logic
Entity
ORM
Dao
Database
Model
\ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/swagger.png b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/swagger.png new file mode 100755 index 00000000000..e9c631fce07 Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/swagger.png differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" new file mode 100755 index 00000000000..0473940a4b6 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/starbook.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/starbook.md new file mode 100644 index 00000000000..c86f5ca564f --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/starbook.md @@ -0,0 +1,47 @@ +--- +slug: '/course/starbook' +title: 'Practical Tutorial - Star English Book' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame, GoFrame framework, Star English Book, Golang tutorial, programming skills, project development, programmer entry-level, tech stack, software development, computer science] +description: 'This book uses the GoFrame framework and the Star English Book project as a practical example to help readers quickly master the GoFrame framework and the Golang language. It does not involve front-end development and is suitable for readers who have a foundational understanding of Golang, including students and programmers. The book provides programming skills and experience sharing, and it is recommended that readers study in conjunction with the official manual for better understanding and practice.' +--- + +## Book Introduction +--- +The knowledge in the `Web` development field is vast, with various libraries, standards, and frameworks intertwining like the roots of a century-old tree. Beginners may feel like they're facing a hedgehog, unsure where to start. + +This book will start with the `GoFrame` framework, guiding beginners to actually develop a project: **Star English Book**. The aim is to provide readers with a comprehensible `GoFrame` framework learning guide to help them quickly master it. + +Oriented towards practical projects, the book shares more professional and practical programming skills and experiences, with the expectation that readers will achieve success! + +## Target Audience +--- +This book is suitable for those who have a basic understanding of `Golang` and wish to learn the `GoFrame` framework. + +The book does not cover any front-end development, so even if you are completely unfamiliar with any front-end technologies, you can read it without obstacles. + +### Students +If you are a student interested in the `Go` language and want to learn how to use it to develop an interesting project or a graduation design, laying a foundation for your career, this book can serve as an entry point into programming, guiding you on how to become a real programmer. + +### Beginner Programmers +If you are already a programmer but not very familiar with `Go` and want to further master the language or learn the `GoFrame` framework, this book can help you quickly get started with the `GoFrame` framework, allowing you to quickly integrate into the world of the `Go` language and expand your tech stack. + +## Reading Suggestions +--- +This is a hands-on book, and the only suggestion is to follow it step by step. Learning from books alone can be superficial; practical experience is essential! + +While following the book, it's best to refer to the [official development manual](../../docs/框架设计/框架设计.md) and try to extrapolate on it to better understand and master the content. + +## Contact the Author +--- +Inevitably, there may be some errors or shortcomings in the writing of this book. If you have any questions or suggestions, you can leave a comment below or contact me, and I will reply as soon as possible! +- Email: `tyyn1022@gmail.com` `tyyn1022@163.com` +- Website: [https://oldme.net](https://oldme.net) +- WeChat: `NobodyIsRight` Please mention your purpose! + +## Facing Problems +--- +Encountering various problems during development is normal. The key is how to solve them. When facing a problem, please try to resolve it yourself first. Refer to the `GoFrame` documentation; using search engines is also a good way to solve issues. If you still cannot resolve them, you can contact me, and I will do my best to help you. + +import DocCardList from '@theme/DocCardList'; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..d8f6e1c8505 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" @@ -0,0 +1,51 @@ +--- +title: '1.1 Project Introduction' +hide_title: true +slug: '/course/starbook/about-intro' +keywords: [Project Requirements, Star English Book, User Registration, Word Management, Proficiency Setting, Front-end and Back-end Separation, Web Application, Front-end Framework, JSON Format, GoFrame Framework] +description: "Star English Book is a demonstration project using GoFrame to help users learn and manage English vocabulary, including registration and login features, and provides random review and proficiency settings. The project adopts a front-end and back-end separation model, using the GoFrame framework for development, outputting standard JSON format data for the front end to fetch and build pages through HTTP requests." +--- +Before developing a real project, it is essential to understand the project requirements as they form the foundation for subsequent development. If the foundation is not solid, everything built upon it will be shaky. + +**Star English Book** is lightweight software designed to help users learn English vocabulary and provides the following features: +- User registration; +- Manage their vocabulary after logging in; +- Randomly retrieve several words for review; +- Ability to set the proficiency level of words. + +Starting from the feature points, we can create a more intuitive mind map: +![Feature List](../assets/svg/功能清单.svg) + +## Front-end and Back-end Separation +--- +As previously mentioned, this project does not involve any front-end development, so it is necessary to explain how the front end should handle it. + +For a web application, the front end and back end are two vital components. The front end refers to the interactive pages users can operate, typically built with `HTML`, `CSS`, and `JavaScript`; it represents the face of the software program. The front end doesn't provide any data; it is merely a tool for displaying data, with the data source being the back end. The back end supplies all the application data and processes it, serving as the core of the software program. + +Many years ago, there was no clear boundary between the front end and back end, and typically back-end programming languages would directly output `HTML` pages. As the internet developed, front-end pages became increasingly complex, placing a heavy burden on back-end programmers. Thus, front-end and back-end separation gradually became mainstream, and many frameworks emerged on the front end, such as `Vue`, `React`, `Angular`, etc., which help better manage front-end projects. + +The design purpose of **Star English Book** is to enable readers to quickly master `GoFrame`, so it also adopts the front-end and back-end separation model. All developed content does not directly output `HTML`, but bypasses the front end, directly outputting standard `JSON` format data. + +### JSON +`JSON` is currently the most mainstream data format for front-end and back-end interaction. Data returned by a standard API is as follows: +```json +{ + "code": 0, + "message": "", + "data": { + "id": 1, + "uid": 1, + "word": "example", + "definition": "A representative form or pattern.", + "exampleSentence": "This is an example sentence.", + "chineseTranslation": "例子", + "pronunciation": "ɪɡˈzɑːmp(ə)l", + "proficiencyLevel": 3, + "createdAt": "2024-11-12 15:38:50", + "updatedAt": "2024-11-13 14:42:19" + } +} +``` +`code` represents the status code, where `0` means success; `message` is a custom message, and `data` is the response data. + +Once the front-end developers retrieve the `JSON` data through an `HTTP` request, they can then fully utilize their expertise to construct specific pages. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" new file mode 100644 index 00000000000..384288f32f2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" @@ -0,0 +1,18 @@ +--- +title: '1.2 MVC' +hide_title: true +slug: '/course/starbook/about-mvc' +keywords: [MVC,Model,View,Controller,GoFrame,程序设计架构,前后端分离,数据管理,用户界面,业务逻辑] +description: "Learn about the MVC (Model-View-Controller) design architecture, where the Model handles application data and business logic, managing data and database interactions; the View is responsible for data display and the user interface, interacting with users to present data; and the Controller processes user input and requests, acting as an intermediary between the model and the view. In a front-end and back-end separation, focus on the Controller and Model layers." +--- +If you are already familiar with `MVC`, you can skip this section. If you are a beginner, you need to understand the `MVC (Model-View-Controller)` design architecture. + +`MVC` divides an application into three main components: Model, View, and Controller. + +![mvc](../assets/mvc.png) + +- **Model**: Responsible for the application's data and business logic. Directly manages data, logic, and rules. Interacts with the database to handle data storage and retrieval. +- **View**: Responsible for data display and the user interface. Directly interacts with the user, presenting data and receiving user input. Updates the display to reflect the latest state of the model. +- **Controller**: Responsible for processing user input and requests. Receives input from the view, processes it, and updates the model or view. Acts as an intermediary between the model and view, orchestrating their interaction. + +Since we have separated the front-end and back-end, the `View` layer is actually handled by the front-end. We only need to focus on the `Controller` and `Model` layers. It should be noted that `MVC` is just a design concept, and there is no need to delve too deeply into it. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" new file mode 100644 index 00000000000..6162062375f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" @@ -0,0 +1,35 @@ +--- +title: '1.3 Writing Conventions' +hide_title: true +slug: '/course/starbook/about-convention' +keywords: [command line, GoFrame, GoFrame framework, code omission, output command, terminal, prompt, clean code, program example, Go language] +description: "This book uses the command line in some parts, with the dollar symbol as a prompt. It outputs information to the terminal, omitting unnecessary code to maintain cleanliness. Examples show how to perform basic output operations and code writing techniques using the GoFrame framework." +--- +## Command Line +--- +This book will use the command line in some places. I will use the `$` symbol as a prompt, and you do not need to type this symbol. For example, if I write `$ echo "Hello, GoFrame!"`, you only need to type `echo "Hello, GoFrame!"`. + +```bash +$ echo "Hello, GoFrame!" +Hello, GoFrame! +``` + +`echo` is an output command, which outputs `Hello, GoFrame!` to the terminal. + +## Code Omission +--- +To maintain neatness, I will use `...` to omit code in unnecessary vertical code blocks. + +```go +package main + +import "fmt" + +... + +func main() { + fmt.Println("Hello GoFrame") +} + +... +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..31400ac280b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,155 @@ +--- +title: '1.4 Environment Preparation' +hide_title: true +slug: '/course/starbook/about-environment' +keywords: [GoFrame,Go,MySQL,Database,API Testing,Postman,Project Initialization,Project Directory,GF CLI,IDEA Plugin] +description: "GoFrame is a web backend framework based on the Go language, requiring Go version 1.20 or above. The project development needs database support, and the examples use MySQL, which is compatible with various databases such as MariaDB and TiDB. It is recommended to use Postman or other tools for API testing. This document also introduces the GF CLI installation and project initialization process, providing a detailed explanation of the project directory structure." +--- +## Basic Environment +--- +### Go Environment +`GoFrame` is a web backend framework based on the `Go` language. First, we need a `Go` development environment to proceed with further learning. Ensure that your `Go` version is above `1.20`. +```bash +$ go version +go version go1.22.2 windows/amd64 +``` + +### Database +During software usage, numerous data are generated and eventually need to be stored in a database. You need to prepare a database, and this book uses `MySQL` as an example, with the database name of the sample project being `star`. + +Besides `MySQL`, `GoFrame` also supports various databases like `MariaDB`, `TiDB`, `PostgreSQL`, `SQL Server`, `SQLite`, `Oracle`, `ClickHouse`, and `DM`. A simple modification of configurations and the introduction of related drivers are enough, which will be explained in detail during usage. + +### API Testing Tools +After the development of APIs, I use the `curl` command to test if the APIs meet expectations. However, command-line testing can be inconvenient, so it is recommended to prepare an API testing tool to transform `curl` commands into tests within the tool. Popular testing tools like `Postman`, `Apifox`, and `Apipost` have similar functionalities, allowing you to choose based on personal preference. + +## Install GF CLI Tool +--- +The `GoFrame` framework offers powerful development assistance tools, which are an essential part of the framework. These tools help with project creation, code generation, project running, etc. The latest version of `GF CLI` can be installed with the following command: +```bash +$ go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +After this, run the following command to check if the installation was successful: +```bash +$ gf version +v2.8.0 +Welcome to GoFrame! +Env Detail: + Go Version: go1.22.2 windows/amd64 + GF Version(go.mod): cannot find go.mod +... +``` + +> The development environment used in this book is `Go 1.22.2` and `GoFrame v2.8.0`. Even if your versions differ, there's no need to worry as `Go` and `GoFrame` boast strong backward compatibility. + +### Unable to Access Github +Due to network issues within China, accessing `Github` might fail, a common problem for domestic programmers. You can look for a usable `Github` mirror or use other proxy tools online to resolve this issue. + +## Project Initialization +--- +Once the environment is ready, we can officially initialize the project. Find the directory where you want to store your program and execute the following command: +```bash +$ gf init star +initializing... +initialization done! +you can now run "cd star && gf run main.go" to start your journey, enjoy! +``` + +`star` is the project name, meaning StarBook in English, but you can choose any name you prefer. Next, enter the directory and run the project. + +```bash +$ cd star && gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 13628 +2024-11-06 16:45:46.015 [INFO] pid[13628]: http server started listening on [:8000] +2024-11-06 16:45:46.015 [INFO] {4c9b7c33a654051860769a5fdef82a84} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-06 16:45:46.015 [INFO] {4c9b7c33a654051860769a5fdef82a84} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | star/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|---------------------------------- +``` +As the demonstration uses a `Windows` system, a `main.exe` file is generated. If you're using a `Mac/Linux` system, a `main` file is generated. + +Then enter [http://127.0.0.1:8000/hello](http://127.0.0.1:8000/hello) in the browser or access it using an API testing tool. Seeing `Hello, GoFrame!` indicates that you have successfully launched a `GoFrame` project. Congratulations on taking the first step into great programming! + +> If you are using the `IntelliJ IDEA` or `GoLand` editor, you can install the [GoFrame Helper](https://plugins.jetbrains.com/plugin/23324-goframe-helper) plugin for better code suggestions and auto-completion features. + +### Upgrade Project +The default installed `GoFrame` may not be the latest version; you can upgrade in the project directory using the `up` command: +```bash +$ gf up +``` + +## Project Directory Structure +--- +After initializing the project, let's look at the project's directory structure: + +| Directory/File Name | Type | Description | +| -------------------------------------------------------- | --------- | ---------------------------------------------------------------------------- | +| `api` | API | Definition of input/output data structures for providing external services. Due to version management needs, it often exists as `api/xxx/v1...`. | +| `hack` | Tools | Directory for project development tools and scripts, such as CLI tool configurations, various shell/bat scripts, etc. | +| `internal` | Logic | Directory for business logic. It hides visibility from the outside using Golang’s `internal` feature. | +|     `cmd` | Commands | Command-line management directory, can manage and maintain multiple command lines. | +|     `consts` | Constants | Directory for all project constants. | +|     `controller` | Controller | Layer for receiving/parsing user input parameters, the entrance/interface layer. | +|     `dao` | Data Access | Data Access Objects, an abstraction layer for interacting with the underlying database and only includes the most basic CRUD methods. | +|     `logic` | Logic | Management and encapsulation of business logic, often the most complex part of the project. | +|     `model` | Model | Data structure management module, managing data entities, and input/output data structure definitions. | +|         `do` | Domain Objects | Models for converting between business models and instance models within DAO operations. Maintained by tools, not editable by users. | +|         `entity` | Entity | Data models represent a one-to-one relationship between a model and a data set. Maintained by tools, not editable by users. | +|     `service` | Service | Interface definition layer for business module decoupling. Specific interface implementations are injected in logic. | +| `manifest` | Manifest | Includes files for compiling, deploying, running, and configuring the program. Common contents include: | +|     `config` | Config | Directory for storing configuration files. | +|     `docker` | Docker | Files related to Docker images, such as dependence files, and script files. | +|     `deploy` | Deploy | Files related to deployment. By default, Kubernetes’ yaml templates for cluster deployment managed by kustomize are provided. | +|     `protobuf` | Protobuf | Protobuf protocol definition files used in GRPC protocol, compiled into go files in the api directory. | +| `resource` | Resource | Static resources files. These files can often be injected into published files through resource packaging/image compilation. | +| `go.mod` | Dependency Management | Description file for dependency management, using Go Module package management. | +| `main.go` | Entry File | Program entry file. | + +It may seem confusing at first, but don't worry, the important directories will be used gradually later. + +Now, let's delete all initial sample files, leaving a blank environment for subsequent development. Press `CTRL+C` to terminate project operation and delete all files in the following directories: +```text +api/* +internal/controller/* +``` + +Edit the `cmd` file to remove unnecessary code. + +*internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + }) + s.Run() + return nil + }, + } +) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" new file mode 100644 index 00000000000..c2bf7adf587 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" @@ -0,0 +1,33 @@ +--- +title: '1.5 The Source Code of This Book' +hide_title: true +slug: '/course/starbook/about-source' +keywords: [GoFrame, open source, source code, MIT License, software licensing, code management, copyright, software release, online resources, source code acquisition] +description: "The source code of this book is open source on GitHub, and its open-source license is based on the MIT protocol, allowing users to use, copy, and modify the software freely without restrictions. The document details the contents and conditions of the software license to ensure that users comply with regulations when using copyrighted content, and make declarations about the applicability and liability of the software for better application and development." +--- +The source code of this book is open-source at [https://github.com/oldme-git/star](https://github.com/oldme-git/star). + +The open-source license is based on the `MIT` protocol: +```text +MIT License + +Copyright (c) 2024 oldme + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..28aa8f9891a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" @@ -0,0 +1,17 @@ +--- +title: 'Chapter 1 - Basic Information' +hide_title: true +sidebar_position: 1 +slug: '/course/starbook/about' +keywords: [GoFrame,GoFrame Framework,Programming Framework,Open Source,Web Development,Application Development,High Performance,Modular Design,Enterprise Applications,Backend Development] +description: "The GoFrame framework is an open-source, high-performance web application development framework that supports modular design, suitable for enterprise-level application development. Its user-friendly features and powerful functionality make it a reliable tool for developers to achieve efficient backend development." +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..05abd1256da --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,26 @@ +--- +title: '3.1 Preface' +hide_title: true +slug: '/course/starbook/session-overview' +keywords: [GoFrame, User Session Management, JWT, HTTP Stateless Protocol, Cookie, Json Web Token, golang-jwt, User Login, Authentication, Local Storage] +description: "We need to implement user session management functions, including login and user information retrieval. JWT is a modern solution for user authentication, transmitted through HTTP headers, not relying on Cookies and supporting cross-domain. JWT consists of Header, Payload, and Signature and is widely used in frontend-backend separation projects." +--- +In this chapter, we need to complete the user session management functions: +- Login; +- Retrieve user information. + +## Introduction to JWT +--- +`HTTP` is a stateless protocol, meaning every request is independent with no contextual relationship. This requires a mechanism to preserve user state information, and `Cookie` is one of such solutions. `Cookies` are small pieces of data stored in the user's browser that can be sent to the server in subsequent requests to maintain session state. However, `Cookies` have some limitations, such as cross-domain issues and security concerns. In contrast, `JWT` is a more modern solution that can be transmitted through the `HTTP` header without relying on `Cookies` and offers better cross-domain support and security. + +`JWT`, or `Json Web Token`, appears as a string of unordered characters. It consists of three parts: Header, Payload, and Signature. + +In projects with frontend-backend separation, after a user logs in, the server generates a `JWT` and returns it. The client saves it independently, for example, in the browser's local storage `Local Storage`. In subsequent requests, it is typically included in the `Authorization` field of the `Header` to complete user authentication. + +## Install golang-jwt +--- +Generating and verifying `JWT` requires complex encryption and decryption logic, which can be cumbersome to write yourself. Fortunately, others have already created this tool, and you can directly install and use it. + +```bash +$ go get -u github.com/golang-jwt/jwt/v5 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" new file mode 100644 index 00000000000..475d4e02b77 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" @@ -0,0 +1,130 @@ +--- +title: '3.2 Login' +hide_title: true +slug: '/course/starbook/session-login' +keywords: [GoFrame, login endpoint, user authentication, generate Token, password encryption, user requests, error handling, JwtKey, interface testing, logic writing] +description: "The login function generates a Token upon successful verification by receiving the username and password. Using the GoFrame framework, it adheres to the development principle of Three Boards, including Api generation Controller, and writing core Logic logic. The JwtKey is used to generate the signature, and the Token is valid for six hours. The core logic is invoked in the Controller to implement the login function, and the interface is tested to ensure functionality." +--- +Upon receiving the username and password at the login endpoint, it compares them with the information in the database. If correct, it generates and returns a `Token`, otherwise, it prompts "Username or password error." + +Similarly, it follows the development principle of Three Boards: writing `Api` to generate `Controller`, writing core logic `Logic`, and having the `Controller` call `Logic`. +## Add Api +--- +*api/users/v1/users.go* +```go +... + +type LoginReq struct { + g.Meta `path:"users/login" method:"post" sm:"Login" tags:"User"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` +} + +type LoginRes struct { + Token string `json:"token" dc:"Add Authorization: token in the header for authentication-required interfaces"` +} +``` + +> Don't forget to execute `gf gen ctrl`! You need to execute it every time you change the `Api`, not repeated in the text below. + +## Write Logic +--- +The difficulty of the login logic lies in generating the `Token`. Prepare a random string `JwtKey` as the signature, and define it in the `utility` directory. + +*utility/jwt.go* +```go +package utility + +var JwtKey = []byte("db03d23b03ec405793b38f10592a2f34") +``` + +Write the core logic. First, perform a `Where` query based on the username. After data retrieval, encrypt the password again. If it matches the encrypted text in the database, it's a legitimate user and a `Token` is generated and returned. + +*internal/logic/users/account.go* +```go +package users + +import ( + "context" + "errors" + "time" + + "github.com/golang-jwt/jwt/v5" + + "star/internal/dao" + "star/internal/model/entity" + "star/utility" +) + +type userClaims struct { + Id uint + Username string + jwt.RegisteredClaims +} + +func (u *Users) Login(ctx context.Context, username, password string) (tokenString string, err error) { + var user entity.Users + err = dao.Users.Ctx(ctx).Where("username", username).Scan(&user) + if err != nil { + return "", errors.New("Username or password error") + } + + if user.Id == 0 { + return "", errors.New("User does not exist") + } + + // Encrypt the password and compare it with the password in the database + if user.Password != encryptPassword(password) { + return "", errors.New("Username or password error") + } + + // Generate token + uc := &userClaims{ + Id: user.Id, + Username: user.Username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(6 * time.Hour)), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc) + return token.SignedString(utility.JwtKey) +} +``` + +As seen in the above code, we need to declare a struct `UserClaims` to store the signature information. It saves the `Id` and `Username` and sets the token validity to 6 hours. The final declared object needs to call the `SignedString` method and pass in `JwtKey` to generate a signature. + +## Controller calls Logic +--- +*internal/controller/users/users_v1_login.go* +```go +package users + +import ( + "context" + + "star/internal/logic/users" + "star/api/users/v1" +) + +func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) { + token, err := c.users.Login(ctx, req.Username, req.Password) + if err != nil { + return + } + return &v1.LoginRes{Token: token}, nil +} +``` + +## Interface Testing +--- +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/login -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\"}" + +{ + "code":0, + "message":"", + "data":{ + "token":"eyJhbGciOi...ZY_ATzOU" + } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..9f31b0170f8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" @@ -0,0 +1,229 @@ +--- +title: '3.3 Get User Information' +hide_title: true +slug: '/course/starbook/session-get-user-info' +keywords: [GoFrame, User Information API, Middleware, Authentication, Token Verification, HTTP Handling, Request Control, API Design, JWT Parsing, Interface Development] +description: "The user information API requires login to access and uniformly validates the token's validity through middleware. The GoFrame framework provides a flexible middleware mechanism supporting pre-and post-request operations. The Auth middleware verifies the token in an HTTP request, extracts user information in Logic by parsing the token, and accesses the user information API through API and Controller definitions for access control and authentication." +--- +User information is a sensitive interface that must ensure it can only be accessed after the user has logged in. Similarly, interfaces related to users editing the word library also need authentication. It is not feasible to write the same code for authentication before every interface that requires it. Hence, a **middleware/interceptor** should be developed to uniformly verify if the `Token` is valid. + +> Middleware/interceptors are functions or components that handle `HTTP` requests and responses. They are typically used to perform certain actions before the request reaches the final handler or before the response is sent to the client. + +## Auth Middleware +--- +`GoFrame` provides an elegant way to control requests using middleware, which is also the mainstream method provided by `WebServer` for controlling the request flow, making it a flexible and powerful plugin mechanism based on middleware design. + +*internal/logic/middleware/auth.go* +```go +package middleware + +import ( + "net/http" + + "github.com/gogf/gf/v2/net/ghttp" + "github.com/golang-jwt/jwt/v5" + "star/utility" +) + +func Auth(r *ghttp.Request) { + var ( + jwtKey = utility.JwtKey + tokenString = r.Header.Get("Authorization") + ) + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return jwtKey, nil + }) + if err != nil || !token.Valid { + r.Response.WriteStatus(http.StatusForbidden) + r.Exit() + } + + r.Middleware.Next() +} +``` + +The code `r.Middleware.Next()` is the core control of the middleware. Placing it at the end of the function makes it a **pre-middleware**, meaning it's called before the request. Placing it at the beginning makes it a **post-middleware**, effective after the request. + +`r.Header.Get("Authorization")` retrieves the `Authorization` field from the `HTTP Header`, i.e., the `Token` passed from the frontend. After parsing the `Token` with `jwt.Parse`, it uses `token.Valid` to verify its validity. If it invalidates, it returns an `HTTP StatusForbidden 403` status code, indicating insufficient permissions. Otherwise, it calls `r.Middleware.Next()` to proceed. + +The written middleware is reserved and will be registered uniformly after interface development is complete. Next, it follows the pleasant triple-axe rule. + +## Add Api +--- +*api/account/v1/account.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type InfoReq struct { + g.Meta `path:"account/info" method:"get" sm:"Get Information" tags:"User"` +} + +type InfoRes struct { + Username string `json:"username" dc:"Username"` + Email string `json:"email" dc:"Email"` + CreatedAt *gtime.Time `json:"created" dc:"Creation Time"` + UpdatedAt *gtime.Time `json:"update" dc:"Update Time"` +} +``` + +The `InfoRes` structure defines four response data fields, where the `*gtime.Time` data type is a framework time type provided by the `gtime` component. + +## Write Logic +--- +*internal/logic/users/account.go* +```go +package users + +import ( + "context" + "errors" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/golang-jwt/jwt/v5" + "star/internal/dao" + "star/internal/model/entity" + "star/utility" +) + +... + +func (u *Users) Info(ctx context.Context) (user *entity.Users, err error) { + user = new(entity.Users) + tokenString := g.RequestFromCtx(ctx).Request.Header.Get("Authorization") + + tokenClaims, _ := jwt.ParseWithClaims(tokenString, &userClaims{}, func(token *jwt.Token) (interface{}, error) { + return utility.JwtKey, nil + }) + + if claims, ok := tokenClaims.Claims.(*userClaims); ok && tokenClaims.Valid { + err = dao.Users.Ctx(ctx).Where("id", claims.Id).Scan(&user) + } + return +} +``` + +In `Logic`, you cannot directly access the `HTTP` object; instead, use `g.RequestFromCtx(ctx).Request` to obtain it from the context. After obtaining the `Token`, parse out the user `ID`, and call the `Scan` method to assign the query result to the `entity.Users` structure. + +The `Scan` method is a powerful one that automatically recognizes and converts based on the given parameter type, commonly used in data query operations. + +## Controller Calls Logic +--- +Also register 'logic' with the controller. + +*internal/controller/users/users_new.go* +```go +... + +package account + +import ( + "star/api/account" + usersL "star/internal/logic/users" +) + +type ControllerV1 struct { + users *usersL.Users +} + +func NewV1() account.IAccountV1 { + return &ControllerV1{ + users: &usersL.Users{}, + } +} +``` + +*internal/controller/account/account_v1_info.go* +```go +package account + +import ( + "context" + + "star/api/account/v1" + "star/internal/logic/users" +) + +func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoRes, err error) { + user, err := c.users.Info(ctx) + if err != nil { + return nil, err + } + return &v1.InfoRes{ + Username: user.Username, + Email: user.Email, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }, nil + return +} +``` + +## Register New Controller +--- +Use `group.Group` to add a new route group and `group.Middleware` to register the `Auth` middleware. All controllers under this group need authentication before accessing. + +*internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "star/internal/controller/account" + "star/internal/controller/users" + "star/internal/logic/middleware" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + group.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(middleware.Auth) + group.Bind( + account.NewV1(), + ) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +## API Testing +--- +Remember to replace `Authorization` with your own: +```bash +$ curl -H "Authorization: eyJhbGci...W6Ed_d3P77Mc" http://127.0.0.1:8000/v1/account/info + +{ + "code":0, + "message":"", + "data":{ + "username":"oldme", + "email":"tyyn1022@gmail.com", + "created":"2024-11-08 17:02:16", + "update":"2024-11-08 17:02:16" + } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..70f04a57336 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" @@ -0,0 +1,30 @@ +--- +title: '3.4 Summary' +hide_title: true +slug: '/course/starbook/session-summary' +keywords: [GoFrame, User Session Management, Login Functionality, User Information Retrieval, Token Generation, Permission Authentication, Middleware Usage, Grouped Routing, JWT, Blacklist and Whitelist Mechanism] +description: "With GoFrame, accomplish user session management, implementing login and user information retrieval. Understand GoFrame in depth through token generation and user information interface declaration. Introduce middleware for user permission authentication applied in grouped routing. When using JWT, address the logout issue with blacklist and whitelist mechanisms, discussing their pros, cons, and implementation methods." +--- +In this chapter, we used `GoFrame` to complete the management of user sessions, providing the two major features of **login** and **user information retrieval**, deepening our understanding of `GoFrame` and learning the following content: +- Declare a user struct to generate `Token`; +- Parse `Token` to extract user information and provide a user information retrieval interface; +- Basic usage of middleware to complete user permission authentication; +- Register middleware in grouped routing. + +## About Logging Out +--- +Why didn't we develop a logout feature? This is because `JWT` is essentially a stateless token, and once issued, the server does not store it. This means that when using `JWT`, logging out cannot be as simple as destroying a session on the server-side as in traditional sessions. There are roughly two solutions for handling `JWT` logout, each with its pros and cons: + +1. **Blacklist Mechanism**: + - When a user logs out or the `JWT` is revoked, add the token to a blacklist database; + - With each request, extract the `JWT` from the request header and check if it's in the blacklist; + - If the token is in the blacklist, deny the request; + - Its advantage lies in ease of implementation and maintenance, suitable for most cases; the disadvantage is the necessity to store all revoked tokens, which may lead to increased storage space. + +2. **Whitelist Mechanism**: + - Upon user login, add the generated `JWT` to a whitelist database; + - With each request, extract the `JWT` from the request header and check if it's in the whitelist; + - If the token is not in the whitelist, deny the request. + - Its advantage lies in higher security, as only tokens on the whitelist are accepted, ensuring stricter access control. The downside is increased complexity, requiring tokens to be added to the whitelist upon login and removed at the appropriate time. + +Typically, blacklist and whitelist data are stored in non-relational databases, such as `Redis`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..5fe9b70f322 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 3 - Session Management' +hide_title: true +sidebar_position: 3 +slug: '/course/starbook/session' +keywords: [GoFrame,GoFrame Framework,Documentation Site,Online Course,Programming Guide,Software Development,Application Framework Tutorial,Technical Documentation,Engineering Project] +description: "GoFrame is a highly efficient Go language development framework suitable for quickly building scalable applications. By using the GoFrame framework, developers can easily address complex project requirements while providing a rich set of functional modules to support various application scenarios." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..c12b32c0cbb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,141 @@ +--- +title: '2.1 Preface' +hide_title: true +slug: '/course/starbook/register-overview' +keywords: [Star English Book, user registration, GoFrame, code layering, MVC concept, ORM, database driver, MySQL driver, Go language, software development] +description: "The development of the user registration feature for the Star English Book project, which details the code layering design and ORM feature application of the GoFrame framework, provides installation and usage instructions for the MySQL driver, explains the process of database interaction through simple examples, enhances code readability and maintainability, and explains the actual connection process with the MySQL database to prevent SQL injection risks." +--- +This chapter will formally develop the **Star English Book** project, where we will develop the first interface: user registration, to provide user registration functionality. + +## Code Layering +--- +The `GoFrame` framework has an excellent code layering design, with a clear and well-structured software architecture. During development, you can follow the "three-step" rule: first, define interface information at the `Api` layer, then the `Controller` layer receives HTTP interface requests, and finally, the `Logic` layer completes the specific logic and database interaction. This is actually a variant of the `MVC` concept. + +![Flow](../assets/流程.png) + + +## ORM +--- +`ORM` stands for `Object-Relational Mapping`, and its core function is to map data tables to objects, typically structs in the Go language. + +After mapping, we can handle data tables by manipulating objects, avoiding the need to write `SQL` statements directly. This not only improves code readability and maintainability but also prevents common `SQL` injection issues from a security standpoint. + +`GoFrame` provides built-in `ORM` functionality through the [gdb](../../../docs/核心组件/数据库ORM/数据库ORM.md) component, offering convenient and common `SQL` operations. + +Here's an example: +```go +ctx := gctx.New() +Users.Ctx(ctx).Where("username", "admin").One() +``` + +This is a simple query operation, and `ORM` will automatically generate `SQL` statements based on it. +```sql +SELECT * FROM users WHERE username = 'admin'; +``` + +`ORM` only provides the data table mapping function, and actual interaction with the database still requires using a database driver. + +## Database Driver +--- +`GoFrame` is a component-based framework, and for better extensibility, the database driver has been decoupled, allowing users to choose different database drivers according to their needs to support different databases. + +### Installing Drivers +Install the `MySQL` driver: +```bash +$ go get -u github.com/gogf/gf/contrib/drivers/mysql/v2 +``` + +Other database drivers: +```text +go get -u github.com/gogf/gf/contrib/drivers/clickhouse/v2 +go get -u github.com/gogf/gf/contrib/drivers/dm/v2 +go get -u github.com/gogf/gf/contrib/drivers/mssql/v2 +go get -u github.com/gogf/gf/contrib/drivers/oracle/v2 +go get -u github.com/gogf/gf/contrib/drivers/pgsql/v2 +go get -u github.com/gogf/gf/contrib/drivers/sqlite/v2 +go get -u github.com/gogf/gf/contrib/drivers/sqlitecgo/v2 +``` + +### Importing Drivers +Globally import the driver: + +*main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + ... +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +### Checking the Driver +After completing the above, we need to check if the database driver is successfully installed. Add the database configuration in the configuration file, and if there is any content, clear all of it to maintain consistency with the following: + +*manifest/config/config.yaml* +```yaml +server: + address: ":8000" # Server listening port + openapiPath: "/api.json" # OpenAPI interface document address + swaggerPath: "/swagger" # Built-in SwaggerUI display address + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/star?loc=Local" + debug: true +``` + +Modify the main function to check if the database is connected properly before the program runs. + +*main.go* +```go +package main + +··· + +func main() { + var err error + + // Check if the database is connected + err = connDb() + if err != nil { + panic(err) + } + + cmd.Main.Run(gctx.GetInitCtx()) +} + +// connDb checks if the database connection is normal +func connDb() error { + err := g.DB().PingMaster() + if err != nil { + return errors.New("Failed to connect to the database") + } + return nil +} +``` + +Run the project, if no error is reported, the database driver is successfully installed. +```base +$ gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 24612 +2024-11-07 16:42:51.197 [INFO] {f89117371ba305188476a74abc958a23} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-07 16:42:51.197 [INFO] pid[24612]: http server started listening on [:8000] +2024-11-07 16:42:51.197 [INFO] {f89117371ba305188476a74abc958a23} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|-------------------- +``` + +`gf run main.go` is a command provided by `GF CLI` to run the program. When a developer modifies a `go` file in the project, the command stops the original program, automatically compiles, and runs the current program. It can be used as a replacement for `go run main.go`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..23cbc4b2f45 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,60 @@ +--- +title: '2.2 Data Model' +hide_title: true +slug: '/course/starbook/register-model' +keywords: [GoFrame framework, data table, database, data model, GF-CLI, data access object, creation time, user information, email address, auto-increment] +description: "Create a data table named users in the database to store user information, including fields such as username and password, supporting auto-increment primary key identification. By modifying configuration files and executing GF-CLI commands, generate data models and data access objects. The generated four files in the model and dao layers are responsible for managing data structures and data access. The GoFrame framework operates on the data table through ORM." +--- +## Establish Data Table +--- +Execute the following `sql` statement in your database to create a data table named `users` to store user information. + +```sql +CREATE TABLE users ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + password CHAR(32) NOT NULL, + email VARCHAR(100), + created_at DATETIME, + updated_at DATETIME +); +``` + +| Field Name | Type | Description | +| ------------ | -------------- | ----------------------------- | +| `id` | `INT UNSIGNED` | Primary key, auto-increment, uniquely identifies the user | +| `username` | `VARCHAR(50)` | Username, cannot be null | +| `password` | `CHAR(32)` | User password hash value, fixed length of 32 characters, cannot be null | +| `email` | `VARCHAR(100)` | User's email address, can be null | +| `created_at` | `DATETIME` | Creation time | +| `updated_at` | `DATETIME` | Record last update time | +## Generate Data Model +--- +Modify the tool configuration file: + +*hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/star" +``` + +> Do not confuse `hack/config.yaml` with `manifest/config/config.yaml`; the former is a configuration file for development tools, while the latter is for business configuration. + +Execute the `GF-CLI` command to generate the data model: +```bash +$ gf gen dao +``` + +Upon successful execution, four files will be generated in the `model` and `dao` layers: +```text +internal/model/do/users.go +internal/model/entity/users.go +internal/dao/internal/users.go +internal/dao/users.go +``` + +The `model` layer is used by `GoFrame` to manage data structures, corresponding one-to-one with the data table, and should not be modified by users. `model/do/users.go` is used as a data writing object, adopting a generic design for easy data storage; `model/entity/users.go` is used as a data reading object, with types consistent with the data table. + +The `dao` layer manages data access objects, with `GoFrame ORM` using it to perform CRUD operations on the data table. `dao/internal/users.go` holds internal object implementations, should not be modified by users, and is not exposed. `dao/users.go` instantiates and exposes data access objects, where users can add custom methods. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..93e3a74ec42 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" @@ -0,0 +1,211 @@ +--- +title: '2.3 Registration API' +hide_title: true +slug: '/course/starbook/register-general' +keywords: [GoFrame, User Registration API, Data Model, API Development, Business Logic Layer, HTTP Request, Code Generation, Controller Registration, Database Interaction, Project Execution] +description: "Developing a user registration API using the GoFrame framework, including steps for adding an API, writing the business logic layer, controller call logic, controller registration, and running the project. By generating data access objects and data models, the interface interacts with the database, and ultimately tests the interface's functionality during project execution." +--- +After preparing the data model, we can use our "three-step" rule to develop the user interface. +## Add API +--- +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +type RegisterRes struct { +} +``` + +For better maintenance of the interface, a version number is usually added at the beginning of the interface address. `GoFrame` recommends using multi-level directories to manage versions, and the version number of this interface is `v1`. + +`RegisterReq` and `RegisterRes` define the HTTP request object and response object respectively. `g.Meta` is embedded into the request structure and defines general interface attributes through the `Go Tag` method. This code means we have added a user registration interface, with the address `/users/register`, request method `POST`, and three request parameters: `Username`, `Password`, and `Email`. + +Execute the command to generate the `Controller` corresponding to the API: +```bash +$ gf gen ctrl +generated: D:\project\star\api\users\users.go +generated: internal\controller\users\users.go +generated: internal\controller\users\users_new.go +generated: internal\controller\users\users_v1_register.go +done! +``` + +Among the four generated files, we only need to focus on `users_v1_register.go`, which is used to receive HTTP requests and call `Logic` to complete the business process. + +> If you have installed the [GoFrame Helper](https://plugins.jetbrains.com/plugin/23324-goframe-helper) plugin, the `gf gen ctrl` command will be executed automatically. You can also use the official automatic generation method: [Tutorial Configuration](../../../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md#automatic-mode-recommended). + +## Write Logic +--- +`Logic` is the business logic layer, stored in `internal/logic`, which is called by the `Controller` to implement specific business logic. + +Define a 'Users' object: + +*internal/logic/users/users.go* +```go +package users + +type Users struct { +} +``` + +Write registration methods: + +*internal/logic/users/register.go* +```go +package users + +import ( + "context" + + "star/internal/dao" + "star/internal/model/do" +) + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: password, + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} +``` + +`dao.Users` is the data access object generated earlier, used for interacting with the database. `do.Users` is the generated data model used for data entry, and there is a similar data model `entity.Users` used for data output. + +## Controller Calls Logic +--- +The `Controller` layer is responsible for receiving `Req` request objects and then calling one or more `Logic` to complete the business logic. Some simple logic can also be directly handled in the `Controller`. The results of the processing are wrapped in the agreed `Res` data structure and returned. Here, the `Res` data structure is empty, so returning `nil` is sufficient. + +Encapsulate the 'Users' object into the controller for easy subsequent calls. + +*internal/controller/users/users_new.go* +```go +... + +package users + +import ( + "star/api/users" + usersL "star/internal/logic/users" +) + +type ControllerV1 struct { + users *usersL.Users +} + +func NewV1() users.IUsersV1 { + return &ControllerV1{ + users: &usersL.Users{}, + } +} +``` + +*internal/controller/users/users_v1_register.go* +```go +package users + +import ( + "context" + + "star/internal/logic/users" + + "star/api/users/v1" +) + +func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { + err = c.users.Register(ctx, req.Username, req.Password, req.Email) + return nil, err +} +``` + +## Register the Controller +--- +All controllers must be registered in `cmd` to be effective. The `cmd` layer is responsible for guiding the program to start, with significant work including initializing logic, registering route objects, starting the `server` to listen, and blocking the running program until the `server` exits. + +*internal/cmd/cmd.go* +```go +package cmd + +··· + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + }) + }) + s.Run() + return nil + }, + } +) +``` + +`group.Group` is a method for grouped route registration provided by the framework, and it is the recommended registration method by the framework. We prefix with `v1` corresponding to the `api` directory to facilitate interface version management. + +## Run the Project +--- +```bash +$ gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 8648 +2024-11-08 10:36:48.013 [INFO] pid[8648]: http server started listening on [:8000] +2024-11-08 10:36:48.013 [INFO] {e05c16b565dd0518360ebe639e1c623d} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-08 10:36:48.014 [INFO] {e05c16b565dd0518360ebe639e1c623d} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | POST | /v1/users/register | star/internal/controller/users.(*ControllerV1).Register | ghttp.MiddlewareHandlerResponse +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- +``` + +The result of running prints out three interface addresses. `/swagger` and `/api.json` are interface document addresses generated by the framework, which we will explain in detail in [2.5 API Documentation](./2.5.接口文档.md). The other address `/v1/users/register` is the user registration interface we developed. Send a `POST` request to test it. + +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":0, + "message":"", + "data":null +} +``` + +A `code` of `0` indicates success. Check the database to see if a record has been inserted: +```sql +SELECT * FROM users; +``` + +| ID | Username | Password | Email | Created_At | Updated_At | +| --- | -------- | -------- | ------------------ | ------------------- | ------------------- | +| 1 | oldme | 123456 | tyyn1022@gmail.com | 2024-11-08 10:36:48 | 2024-11-08 10:36:48 | + +`Created_At` and `Updated_At` are two convention fields that will be automatically maintained by the `ORM`, representing the creation time and modification time respectively. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" new file mode 100644 index 00000000000..67ea541584c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" @@ -0,0 +1,320 @@ +--- +title: '2.4 Business Optimization' +hide_title: true +slug: '/course/starbook/register-optimization' +keywords: [registration process optimization, interface parameter validation, username validation, password encryption, GoFrame framework, i18n multilingual, data uniqueness, structure optimization, validation rules, password security] +description: "Problems and optimization solutions in the registration process, including interface parameter validation, prohibiting duplicate usernames, password encryption methods, and function parameter optimization. Use the GoFrame framework for parameter validation, including basic username and password verification, and implement multilingual support with i18n. By setting unique database indexes, ensure user uniqueness and use MD5 encryption to protect password security, enhancing system security and usability." +--- +There are several significant issues that need to be optimized in the current registration process: +- Interface parameters are not validated, meaning entries with empty or random values can be successfully stored. Therefore, username, password, and email should be required, with a certain level of security verification. For example, passwords should be between `6-12` characters, and emails should follow the `xx@xx.xx` format; +- Prohibiting the registration of identical users; +- Passwords should not be stored in plain text but should be encrypted before storage; +- The `logic/Register` function has too many parameters, which is neither elegant nor maintainable. User information should be defined in a structure and used as a function parameter. +## Parameter Validation +--- +`GoFrame` has built-in powerful interface parameter validation features, which can be enabled by adding `v` to the `g.Meta` tag. + +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` + Email string `json:"email" v:"required|email"` +} + +type RegisterRes struct { +} +``` + +Multiple validation rules are separated by `|`, `required` indicates the field is mandatory, `length` indicates the length is between `3-12`, and `email` indicates only valid email addresses are accepted. Available validation rules can be found in the [Developer's Manual](../../../docs/核心组件/数据校验/数据校验-校验规则.md). + +Testing with an empty username request: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":51, + "message":"The Username field is required", + "data":null +} +``` + +`The Username field is required` indicates that the username cannot be empty. + +If you are not satisfied with the English prompt, you can use the i18n component provided by the framework to change it to a Chinese prompt. + +### Parameter Validation i18n +Download the file from [Github](https://github.com/gogf/gf/blob/master/util/gvalid/testdata/i18n/cn/validation.toml) and store it in the `manifest/i18n` directory, or simply copy it from below. + +*manifest/i18n/zh-CN/validation.toml* +```toml +"gf.gvalid.rule.required" = "{field} field cannot be empty" +"gf.gvalid.rule.required-if" = "{field} field cannot be empty" +"gf.gvalid.rule.required-unless" = "{field} field cannot be empty" +"gf.gvalid.rule.required-with" = "{field} field cannot be empty" +"gf.gvalid.rule.required-with-all" = "{field} field cannot be empty" +"gf.gvalid.rule.required-without" = "{field} field cannot be empty" +"gf.gvalid.rule.required-without-all" = "{field} field cannot be empty" +"gf.gvalid.rule.date" = "{field} field value `{value}` does not match date format Y-m-d, e.g.: 2001-02-03" +"gf.gvalid.rule.datetime" = "{field} field value `{value}` does not match datetime format Y-m-d H:i:s, e.g.: 2001-02-03 12:00:00" +"gf.gvalid.rule.date-format" = "{field} field value `{value}` does not match the specified format {format}" +"gf.gvalid.rule.email" = "{field} field value `{value}` is not a valid email address format" +"gf.gvalid.rule.phone" = "{field} field value `{value}` is not a valid phone number format" +"gf.gvalid.rule.phone-loose" = "{field} field value `{value}` is not a valid phone number format" +"gf.gvalid.rule.telephone" = "{field} field value `{value}` is not a valid telephone number format" +"gf.gvalid.rule.passport" = "{field} field value `{value}` is not a valid account format, must start with a letter and can only contain letters, numbers, and underscores, with a length of 6~18" +"gf.gvalid.rule.password" = "{field} field value `{value}` is not a valid password format, must be any visible character of length 6-18" +"gf.gvalid.rule.password2" = "{field} field value `{value}` is not a valid password format, must be any visible character of length 6-18, containing uppercase and lowercase letters and numbers" +"gf.gvalid.rule.password3" = "{field} field value `{value}` is not a valid password format, must be any visible character of length 6-18, containing uppercase and lowercase letters, numbers, and special characters" +"gf.gvalid.rule.postcode" = "{field} field value `{value}` is not a valid postal code" +"gf.gvalid.rule.resident-id" = "{field} field value `{value}` is not a valid resident ID number format" +"gf.gvalid.rule.bank-card" = "{field} field value `{value}` is not a valid bank card number format" +"gf.gvalid.rule.qq" = "{field} field value `{value}` is not a valid QQ number format" +"gf.gvalid.rule.ip" = "{field} field value `{value}` is not a valid IP address format" +"gf.gvalid.rule.ipv4" = "{field} field value `{value}` is not a valid IPv4 address format" +"gf.gvalid.rule.ipv6" = "{field} field value `{value}` is not a valid IPv6 address format" +"gf.gvalid.rule.mac" = "{field} field value `{value}` is not a valid MAC address format" +"gf.gvalid.rule.url" = "{field} field value `{value}` is not a valid URL format" +"gf.gvalid.rule.domain" = "{field} field value `{value}` is not a valid domain format" +"gf.gvalid.rule.length" = "{field} field value `{value}` length should be between {min} and {max} characters" +"gf.gvalid.rule.min-length" = "{field} field value `{value}` minimum length should be {min}" +"gf.gvalid.rule.max-length" = "{field} field value `{value}` maximum length should be {max}" +"gf.gvalid.rule.size" = "{field} field value `{value}` length must be {size}" +"gf.gvalid.rule.between" = "{field} field value `{value}` size should be between {min} and {max}" +"gf.gvalid.rule.min" = "{field} field value `{value}` minimum value should be {min}" +"gf.gvalid.rule.max" = "{field} field value `{value}` maximum value should be {max}" +"gf.gvalid.rule.json" = "{field} field value `{value}` should be in JSON format" +"gf.gvalid.rule.xml" = "{field} field value `{value}` should be in XML format" +"gf.gvalid.rule.array" = "{field} field value `{value}` should be an array" +"gf.gvalid.rule.integer" = "{field} field value `{value}` should be an integer" +"gf.gvalid.rule.float" = "{field} field value `{value}` should be a float" +"gf.gvalid.rule.boolean" = "{field} field value `{value}` should be a boolean" +"gf.gvalid.rule.same" = "{field} field value `{value}` must be the same as {field}" +"gf.gvalid.rule.different" = "{field} field value `{value}` cannot be the same as {field}" +"gf.gvalid.rule.in" = "{field} field value `{value}` should be within the range: {pattern}" +"gf.gvalid.rule.not-in" = "{field} field value `{value}` should not be within the range: {pattern}" +"gf.gvalid.rule.regex" = "{field} field value `{value}` does not match the rule: {pattern}" +"gf.gvalid.rule.__default__" = "{field} field value `{value}` is not valid" +"CustomMessage" = "Custom Error" +"project id must between {min}, {max}" = "Project ID must be greater than or equal to {min} and less than or equal to {max}" +``` + +Modify the main function to enable `i18n`: + +*main.go* +```go +package main + +··· + +func main() { + var err error + + // Globally set i18n + g.I18n().SetLanguage("zh-CN") + + // Check if the database can be connected + err = connDb() + if err != nil { + panic(err) + } + + cmd.Main.Run(gctx.GetInitCtx()) +} + +··· +``` + +Make another request: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":51, + "message":"Username field cannot be empty", + "data":null +} +``` + +You can see that the `message` has been changed to a Chinese prompt. + +## Prevent Duplicate Usernames +--- +Usernames are an important basis for login. If there are two users with the same name in the system, it will cause major logical confusion. Therefore, we need to check if the user exists before data is stored. If it exists, return an error message indicating that the user already exists. + +*internal/logic/users/register.go* +```go +package users + +... + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + if err := u.checkUser(ctx, username); err != nil { + return err + } + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: password, + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} + +func (u *Users) checkUser(ctx context.Context, username string) error { + count, err := dao.Users.Ctx(ctx).Where("username", username).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("User already exists") + } + return nil +} +``` + +Test the request result: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":50, + "message":"User already exists", + "data":null +} +``` + +Code detection alone is not sufficient for security. We can add a unique index in the data table to enforce user uniqueness. +```sql +ALTER TABLE users ADD UNIQUE (username); +``` + +## Password Encryption +--- +Saving passwords in plain text is very insecure. A common practice is to perform a `hash` calculation before saving to the database, such as `md5` or `SHA-1`. + +Add a new function `encryptPassword` to implement password encryption functionality. + +*internal/logic/users/utility.go* +```go +package users + +import "github.com/gogf/gf/v2/crypto/gmd5" + +func encryptPassword(password string) string { + return gmd5.MustEncryptString(password) +} +``` + +The `gmd5` component helps us quickly implement `md5` encryption functionality. Write registration logic code and introduce password encryption. + +*internal/logic/users/register.go* +```go +package users + +... + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + ... + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: encryptPassword(password), + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} + +... +``` + +Delete the original user: +```sql +DELETE FROM users WHERE id = 1; +``` + +Request the interface again to see if the password is successfully encrypted: +```bash +curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" +``` + +Result: + +| ID | Username | Password | Email | Created_At | Updated_At | +| --- | -------- | -------------------------------- | ------------------ | ------------------- | ------------------- | +| 1 | oldme | e10adc3949ba59abbe56e057f20f883e | tyyn1022@gmail.com | 2024-11-08 10:36:48 | 2024-11-08 10:36:48 | + +## Register Function Optimization +--- +Customize a data model in the `model` layer for use as input parameters in the `Logic` layer. + +*internal/model/users.go* +```go +package model + +type RegisterInput struct { + Username string + Password string + Email string +} +``` + +*internal/logic/users/register.go* +```go +package users + +import ( + "star/internal/model" + ... +) + +func (u *Users) Register(ctx context.Context, in *model.RegisterInput) error { + if err := u.checkUser(ctx, in.Username); err != nil { + return err + } + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: in.Username, + Password: encryptPassword(in.Password), + Email: in.Email, + }).Insert() + if err != nil { + return err + } + return nil +} + +... +``` + +Change the `Controller` layer to pass in `RegisterInput`. + +*internal/controller/users/users_v1_register.go* +```go +package users + +import ( + "star/internal/model" + ... +) + +func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { + err = c.users.Register(ctx, &model.RegisterInput{ + Username: req.Username, + Password: req.Password, + Email: req.Email, + }) + return nil, err +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" new file mode 100644 index 00000000000..034c3af0821 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" @@ -0,0 +1,45 @@ +--- +title: '2.5 API Documentation' +hide_title: true +slug: '/course/starbook/register-api-doc' +keywords: [GoFrame, Interface Documentation, Automatic Generation, API Tools, Registration API, Swagger, OpenAPI, Tags, User Interface, JSON Documentation] +description: "Providing interface documentation to the frontend using built-in features of the GoFrame framework. Developers can automatically generate detailed interface documentation by adding specific tags to the interface code. The documentation can be accessed via the Swagger browser or interfaced with other API tools in JSON format, saving developers time and effort. Common OpenAPI tags and their purposes are listed to help developers better organize and manage interface information." +--- +When the completed interface is handed over to the frontend for invocation, it is necessary to prepare interface documentation for them. Fortunately, `GoFrame` has a built-in feature to automatically generate interface documentation, saving us a lot of time. By carrying some additional `tags` when writing `api`, beautiful documentation can be generated. + +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post" sm:"Register" tags:"User"` + Username string `json:"username" v:"required|length:3,12" dc:"Username"` + Password string `json:"password" v:"required|length:6,16" dc:"Password"` + Email string `json:"email" v:"required|email" dc:"Email"` +} + +type RegisterRes struct { +} +``` + +Open the browser at [http://127.0.0.1:8000/swagger](http://127.0.0.1:8000/swagger): +![swagger](../assets/swagger.png) + + Another address [http://127.0.0.1:8000/api.json](http://127.0.0.1:8000/api.json) provides interface documentation in `Json` format, which can be imported into various `Api` tools. + +In addition to `sm`, `tags`, and `dc` tags, `GoFrame` also provides the following tags: + +| Common OpenAPIv3 Tags | Description | Note | +| ---------------------- | --------------------------------------------------------------------------- | ---------------------------- | +| `path` | Combines with the prefix registered to form the interface URI path | Used in `g.Meta` to identify interface metadata | +| `tags` | The tag to which the interface belongs for interface categorization | Used in `g.Meta` to identify interface metadata | +| `method` | The request method of the interface: `GET/PUT/POST/DELETE...` (case-insensitive) | Used in `g.Meta` to identify interface metadata | +| `deprecated` | Marks the interface as deprecated | Used in `g.Meta` to identify interface metadata | +| `summary` | Summary description of the interface/parameter | Abbreviated as `sm` | +| `description` | Detailed description of the interface/parameter | Abbreviated as `dc` | +| `in` | Method of parameter submission | `header/path/query/cookie` | +| `default` | Default value of the parameter | Abbreviated as `d` | +| `mime` | `MIME` type of the interface, such as `multipart/form-data`, generally set globally, default is `application/json`. | Used in `g.Meta` to identify interface metadata | +| `type` | Type of the parameter, generally does not need to be set, specific parameters need to be set manually, such as `file` | Only for parameter properties | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..da5ffba1245 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" @@ -0,0 +1,15 @@ +--- +title: '2.6 Summary' +hide_title: true +slug: '/course/starbook/register-summary' +keywords: [GoFrame, Register Interface, Database Driver, gf gen dao, Data Model, GoFrame ORM, API File, gf gen ctrl, Controller, Interface Parameter Validation] +description: "In the process of completing the registration interface using GoFrame, we learned to install database drivers, generate data models, interact with the database through DAO, write API files, use gf gen ctrl to generate controllers, and methods of interface parameter validation and internationalization processing, mastering the business logic connection between the Controller layer and the Logic layer." +--- +In this chapter, we completed a registration interface using `GoFrame`. We initially got in touch with `GoFrame` and learned the following: + +- Installing database drivers; +- Using `gf gen dao` to generate data models; +- Interacting with the database using `dao` calling `GoFrame ORM`; +- Writing `api` files and using `gf gen ctrl` to generate controllers; +- The `Controller` layer calls the `Logic` layer to complete business logic; +- Interface parameter validation and `i18n`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..4092c8e9b55 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 2 - User Registration' +hide_title: true +sidebar_position: 2 +slug: '/course/starbook/register' +keywords: [GoFrame,GoFrame Framework,User Registration,Registration Feature,Account Management,User Account,User Experience,Secure Registration,Registration Process,Identity Verification] +description: "This section provides a detailed description of how to implement user registration features in the GoFrame framework, including account creation, user information validation, and security assurance. Users can easily register and manage their personal accounts through this process, enjoying a high-quality user experience." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..bba251be232 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,12 @@ +--- +title: '5.1 Preface' +hide_title: true +slug: '/course/starbook/learn-word-overview' +keywords: [GoFrame, Word Review, Star English Book, Word Mastery Level, RESTful Interface, Random Word Retrieval, User Functionality, Review Target, English Learning, Interface Design] +description: "This chapter explores two features of the Star English Book, where users can randomly retrieve words for review and set the level of word mastery to better clarify their review targets. These two functions allow users to have a clearer plan for word learning. At the same time, we delve into RESTful related technologies to enhance the richness and practicality of the interfaces." +--- +In this chapter, we will further enrich the Star English Book by providing users with two practical functions: +- Randomly retrieve several words to facilitate user review; +- Set word mastery levels to clarify review targets. + +During this process, we will study `RESTful` in depth to provide more enriched interfaces. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..7843020f1fe --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" @@ -0,0 +1,130 @@ +--- +title: '5.2 Randomly Retrieve Words' +hide_title: true +slug: '/course/starbook/learn-word-rand' +keywords: [GoFrame, API, Random Words, Word Query, GoFrame Framework, Paginated List, API Design, Programming Logic, API Testing, Word List] +description: "Design a random word retrieval API using the GoFrame framework. Provide a path words/rand API that supports retrieving a limited number of words between 1 and 300. Use OrderRandom for random queries and Limit to restrict the number of results. The controller calls the logic to process query results and conducts API testing to verify the functionality." +--- +Randomly retrieving several words is similar to fetching a paginated list of words: +- No paginated query, instead use random query; +- No fuzzy query; +- The returned data is basically the same. +## Add Api +--- +*api/words/v1/learn_words.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "star/internal/model" +) + +type RandListReq struct { + g.Meta `path:"words/rand" method:"get" sm:"Randomly Retrieve Word List" tags:"Words"` + Limit uint `json:"limit" v:"between:1,300" dc:"Limit number, default 50"` +} + +type RandListRes struct { + List []List `json:"list"` +} +``` + +`RandListReq` provides a `Limit` field to specify the number of items to retrieve, ranging from `1-300`. `RandListRes` uses the same data structure as the paginated word list but lacks the `Total` field. + +> `words/rand` is an exact match with higher priority than the word detail interface: `words/{id}`. + +## Write Logic +--- +*internal/logic/words/learn_words.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + "star/internal/dao" + "star/internal/model" + "star/internal/model/entity" +) + +// Rand Randomly retrieve a few words +func (w *Words) Rand(ctx context.Context, uid, limit uint) ([]entity.Words, error) { + if limit <= 0 { + limit = 50 + } + var ( + list = make([]entity.Words, limit) + err error + ) + db := dao.Words.Ctx(ctx) + if uid > 0 { + db = db.Where("uid", uid) + } + + err = db.Limit(int(limit)).OrderRandom().Scan(&list) + return list, err +} +``` + +`OrderRandom` is a random query method provided by `GoFrame ORM`, and the `Limit` method is used to restrict the number of queries. + +## Controller Calls Logic +--- +*internal/controller/words/words_v1_rand_list.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" + "star/internal/model" +) + +func (c *ControllerV1) RandList(ctx context.Context, req *v1.RandListReq) (res *v1.RandListRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + wordList, err := c.words.Rand(ctx, uid, req.Limit) + if err != nil { + return nil, err + } + + var list []v1.List + for _, v := range wordList { + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: model.ProficiencyLevel(v.ProficiencyLevel), + }) + } + + return &v1.RandListRes{ + List: list, + }, nil +} +``` + +## API Testing +--- +Prepare some test data and conduct tests; detailed explanations are omitted here for brevity. +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words/rand \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": { +        "list": [ +     ... +        ] +    } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" new file mode 100644 index 00000000000..86da10b92cb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" @@ -0,0 +1,89 @@ +--- +title: '5.3 Set Proficiency Level' +hide_title: true +slug: '/course/starbook/learn-word-set-level' +keywords: [GoFrame,ProficiencyLevel,PATCH Method,API Design,Route Style,Data Validation,Interface Testing,Database Update,Go Language,Level Setting] +description: "Implement the function to set the word proficiency level using the GoFrame framework. Use the PATCH method to modify the ProficiencyLevel field and introduce parameter validation to ensure the level is between 1 and 5. The designed API route style follows the resource hierarchy relationship, and it is recommended to use the form words/{id}/level. Complete the level setting through database updates, providing an interface test example to verify the correctness of the function." +--- +The function of setting the proficiency level is essentially an edit that only changes one field, `ProficiencyLevel`, so using the `PATCH` method is more suitable than `PUT`. +## Add Api +--- +*api/words/v1/learn_words.go* +```go +... + +type SetLevelReq struct { + g.Meta `path:"words/{id}/level" method:"patch"` + Id uint `json:"id" v:"required"` + Level model.ProficiencyLevel `json:"level" v:"required|between:1,5"` +} + +type SetLevelRes struct { +} +``` + +Both `words/{id}/level` and `words/level/{id}` route styles are acceptable, but the former is more in line with the resource hierarchy, and it is recommended to use it. + +## Write Logic +--- +*internal/logic/words/learn_words.go* +```go +... + +// SetLevel sets the word proficiency level +func (w *Words) SetLevel(ctx context.Context, uid, id uint, level model.ProficiencyLevel) error { + if level < 0 || level > 5 { + return gerror.New("Invalid proficiency level value") + } + + db := dao.Words.Ctx(ctx) + if uid > 0 { + db = db.Where("uid", uid) + } + + _, err := db.Data("proficiency_level", level).Where("id", id).Update() + return err +} +``` + +To prevent data anomalies, we need to check if the level is between `1-5` before entering the database. + +## Controller calls Logic +--- +*internal/controller/words/words_v1_set_level.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) SetLevel(ctx context.Context, req *v1.SetLevelReq) (res *v1.SetLevelRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + err = c.words.SetLevel(ctx, uid, req.Id, req.Level) + return nil, err +} +``` + +## Interface Testing +--- +```bash +$ curl -X PATCH http://127.0.0.1:8000/v1/words/1/level \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "proficiency_level": 5 + }' + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..aef56abcde0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" @@ -0,0 +1,10 @@ +--- +title: '5.4 Summary' +hide_title: true +slug: '/course/starbook/learn-word-summary' +keywords: [GoFrame, word learning, feature development, random query, RESTful, PATCH method, programming technology, web application, API interface, software engineering] +description: "This chapter developed functions for obtaining random words and understanding RESTful through the PATCH method. This includes random query techniques and enhances understanding of the RESTful architecture through practice, helping users apply and master related programming techniques in the process of learning words." +--- +In this chapter, we developed functions related to learning words: + - Obtained a random number of words, learning random queries; + - Used the `PATCH` method to deepen understanding of `RESTful`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..11d6e038948 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 5 - Learning Words' +hide_title: true +sidebar_position: 5 +slug: '/course/starbook/learn-word' +keywords: [GoFrame, GoFrame Framework, Course Guide, Starbook Tutorial, GoFrame Learning, Programming Guide, Framework Usage, Application Development, Software Development] +description: "This chapter covers the application of the GoFrame framework in Starbook, providing a detailed course guide and learning tutorial to help developers master the use of the GoFrame framework, enhance programming skills and development efficiency, suitable for various application development scenarios." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" new file mode 100644 index 00000000000..0a9f7145960 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" @@ -0,0 +1,25 @@ +--- +title: 'Chapter 6 - Postscript' +hide_title: true +sidebar_position: 6 +slug: '/course/starbook/appendix' +keywords: [GoFrame, GoFrame framework, Golang, middleware, JWT mechanism, configuration management, business optimization, custom error information, data validation, blacklist and whitelist] +description: "This book provides a series of optimization suggestions for development functions, including the use of middleware and custom error messages, incorporating the Jwtkey into configuration files, implementing JWT blacklist and whitelist mechanisms, and validating data when entering the database. Through these directions, readers can deepen their understanding of GoFrame and Golang, enhance programming skills, and gain more experience in business optimization." +--- + +First of all, thank you to every reader for patiently finishing this book! Then, here are some suggestions and directions for everyone. I hope everyone can progress step by step and achieve great success in their careers! +## Further Learning Directions and Suggestions +--- +In all the development features of this book, the author has reserved some space for business optimization. Why not give it a try? +- Further use of middleware, adding post-middleware, customizing error messages; +- Incorporating `Jwtkey` into `config.yaml` instead of storing it directly in the code; +- Implementing blacklist and whitelist mechanisms for `JWT`; +- No validation performed on data when adding/editing a word, such as the word mastery level. + +While optimizing these businesses, you will need to consult a large number of related documents. In this process, you will deepen your understanding of `GoFrame` and even `Golang`. + +## Buy Me a Coffee +--- +**Thanks to every supporter!** + +![Feature List](../assets/coffee.jpg) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..5b528b0b0c5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,37 @@ +--- +title: '4.1 Preface' +hide_title: true +slug: '/course/starbook/word-overview' +keywords: [GoFrame, CRUD Operations, Word Management, RESTful Architecture, Web Services, HTTP Methods, Create Read Update Delete, Resource Management, URI, Client Interface] +description: "Programmers' core skills CRUD operations complete word management through Create, Read, Update, and Delete operations. RESTful is a design principle for implementing simple and flexible web service architecture for resource management, using URIs to access resources and ensuring communication between systems through HTTP methods such as GET, POST, PUT, PATCH, and DELETE." +--- +In this chapter, you will learn the core skills of a programmer—the famous CRUD operations, which stand for `Create`, `Read`, `Update`, and `Delete`. These operations form the basis of most application functionalities, and through them, we will accomplish the following tasks: +- Add new words; +- Edit words; +- Paginated list of words; +- Word details; +- Delete words. + +## Introduction to `RESTful` +--- +`RESTful` is a design principle for web services based on the `REST (Representational State Transfer)` architectural style. It is primarily used to create and access web resources and has the following characteristics: +- **Resources**: Every object in `RESTful` services is viewed as a resource that can be accessed through `URI` (Uniform Resource Identifier). For example, `/words` can represent word resources; +- **HTTP Methods**: `RESTful` services mainly use `HTTP Method` for performing operations: + - `GET`: Read resources + - `POST`: Create resources + - `PUT`: Update resources + - `PATCH`: Update partial resources + - `DELETE`: Delete resources +- **Representational State Transfer**: Clients operate resources by requesting specific `URI`s, and servers return the representation of resources (usually in JSON or XML) in the response. +- **Uniform Interface**: Standardized interface design allows different systems to communicate with each other. + +These principles make `RESTful` architecture simpler, more flexible, and scalable for `Web` services. Here is an example of the `CRUD` operations for words. + +| Operation | Method | URI | Description | +| -------------- | ------ | ------------- | ------------------------- | +| Create (`Create`) | `POST` | `/words` | Create a new word | +| Read (`Read`) | `GET` | `/words` | Get the list of all words | +| Read (`Read`) | `GET` | `/words/{id}` | Get a single word based on `ID` | +| Update (`Update`) | `PUT` | `/words/{id}` | Update the word with specified `ID` | +| Update (`Update`) | `PATCH` | `/words/{id}` | Update partial fields of the word with specified `ID` | +| Delete (`Delete`) | `DELETE` | `/words/{id}` | Delete the word with specified `ID` | diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..2d1c34f7181 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,86 @@ +--- +title: '4.2 Data Model' +hide_title: true +slug: '/course/starbook/word-model' +keywords: [Create Data Table, Word Management, GoFrame Framework, Generate Data Model, Customize Data Model, Word Definition, Chinese Translation, Pronunciation Record, Proficiency Level, Programming Tips] +description: "Create a new data table to save word information and add a unique index to prevent users from adding duplicates. Generate a data model and customize the ProficiencyLevel type to represent word proficiency, defining five levels. This use of fixed enumeration values improves code readability and maintainability, applicable to various state scenarios." +--- +## Create Data Table +--- +Create a new data table to store words and add a combined unique index `uid, word` to restrict a single user from adding the same word multiple times. +```sql +CREATE TABLE words ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + uid INT UNSIGNED NOT NULL, + word VARCHAR ( 255 ) NOT NULL, + definition TEXT, + example_sentence TEXT, + chinese_translation VARCHAR ( 255 ), + pronunciation VARCHAR ( 255 ), + proficiency_level SMALLINT UNSIGNED, + created_at DATETIME, + updated_at DATETIME +); + +ALTER TABLE words ADD UNIQUE (uid, word); +``` + +| Field Name | Type | Description | +| --------------------- | ---------------- | ----------------------------------- | +| `id` | `INT UNSIGNED` | Primary key, auto-increment, unique ID for words | +| `uid` | `INT UNSIGNED` | User ID, indicates the owner of the word | +| `word` | `VARCHAR(255)` | Word, cannot be null | +| `definition` | `TEXT` | Word definition | +| `example_sentence` | `TEXT` | Example sentence | +| `chinese_translation` | `VARCHAR(255)` | Chinese translation of the word | +| `pronunciation` | `VARCHAR(255)` | Pronunciation of the word | +| `proficiency_level` | `SMALLINT` | Word proficiency level, 1 is lowest, 5 is highest | +| `created_at` | `DATETIME` | Record creation time | +| `updated_at` | `DATETIME` | Record last update time | + +## Generate Word Data Model +--- +```bash +$ gf gen dao +``` + +After successful execution, the following files will be generated: +```text +internal/model/do/words.go +internal/model/entity/words.go +internal/dao/internal/words.go +internal/dao/words.go +``` + +## Customize Data Model +--- +Like the user model, replicate the approach in `model` to customize a data model for use as a `Logic` input parameter. + +*internal/model/words.go* +```go +package model + +type ProficiencyLevel uint + +const ( + ProficiencyLevel1 ProficiencyLevel = iota + 1 + ProficiencyLevel2 + ProficiencyLevel3 + ProficiencyLevel4 + ProficiencyLevel5 +) + +type WordInput struct { + Uid uint + Word string + Definition string + ExampleSentence string + ChineseTranslation string + Pronunciation string + ProficiencyLevel ProficiencyLevel +} +``` + +Here, we have customized a data type `ProficiencyLevel` to indicate word proficiency and have defined five enumeration values: `ProficiencyLevel1-5` representing the levels from low to high. + +This method of using custom types with fixed enumeration values is an advanced programming technique that can be widely applied to various states, such as order status, project stages, etc. Beginners often like to use `int` across the board, resulting in `1,2,3...` numeric states scattered throughout the code, making it less readable and maintainable. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..9bef0cf085b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" @@ -0,0 +1,262 @@ +--- +title: '4.3 Add Word' +hide_title: true +slug: '/course/starbook/word-create' +keywords: [GoFrame,RESTful,API,Word Creation,Data Reception Layer,Logical Operation Layer,Data Model,Database Consistency,Word Table,Controller Registration] +description: "Implement a RESTful-style word creation API using the GoFrame framework, including the division of responsibilities between the API layer and the logical layer in the architecture design, emphasizing that data structures should not be passthrough. Detailed explanation on how to ensure data consistency in the logical layer and avoid duplicate input, and how to call multi-layer logic in the controller to maintain single functionality. Additionally, covers the method of controller route registration and interface testing." +--- +According to the `RESTful` style, adding should use the `POST` method. Here comes our three-point development approach. + +## Add Api +--- +*api/words/v1/words.go* +```go +type CreateReq struct { + g.Meta `path:"words" method:"post" sm:"Create" tags:"Word"` + Word string `json:"word" v:"required|length:1,100" dc:"Word"` + Definition string `json:"definition" v:"required|length:1,300" dc:"Definition"` + ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"Example Sentence"` + ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"Chinese Translation"` + Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"Pronunciation"` + ProficiencyLevel uint `json:"proficiency_level" v:"required|between:1,5" dc:"Proficiency Level, 1 is lowest, 5 is highest"` +} + +type CreateRes struct { +} +``` + +Did you notice that the `CreateReq` structure is very similar to the previously defined `model.WordInput`? Can we reuse it to keep the `api` and `logic` consistent and streamline the code? Like this: + +*api/words/v1/words.go* +```go +type CreateReq struct { + g.Meta `path:"words" method:"post" sm:"Create" tags:"Word"` + model.WordInput +} + +... +``` + +*internal/model/words.go* +```go +package model + +... + +type WordInput struct { + Word string `json:"word" v:"required|length:1,100" dc:"Word"` + Definition string `json:"definition" v:"required|length:1,300" dc:"Definition"` + ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"Example Sentence"` + ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"Chinese Translation"` + Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"Pronunciation"` + ProficiencyLevel uint `json:"proficiency_level" v:"required|between:1,5" dc:"Proficiency Level, 1 is lowest, 5 is highest"` +} +``` + +The answer is that the program runs normally, but **this approach is highly inadvisable**. This is because the `Api` layer is the data reception layer, and the `Logic` layer is the logical operation layer. This method of passthrough will bring the following problems: +- Method parameter definitions are unclear, leading to additional collaboration costs and risks of ambiguity. +- The same data structure is coupled with multiple methods, any change in the data structure will affect all related methods. +- Related methods cannot be fully reused. + +The best practice is **to write more lines of code rather than passing through data models.** + +## Write Logic +--- +*internal/logic/words/words.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + "star/internal/dao" + "star/internal/model" + "star/internal/model/do" +) + +type Words struct { +} + +func (w *Words) Create(ctx context.Context, in *model.WordInput) error { + if err := w.checkWord(ctx, in); err != nil { + return err + } + + _, err := dao.Words.Ctx(ctx).Data(do.Words{ + Uid: in.Uid, + Word: in.Word, + Definition: in.Definition, + ExampleSentence: in.ExampleSentence, + ChineseTranslation: in.ChineseTranslation, + Pronunciation: in.Pronunciation, + ProficiencyLevel: in.ProficiencyLevel, + }).Insert() + if err != nil { + return err + } + return nil +} + +func (w *Words) checkWord(ctx context.Context, in *model.WordInput) error { + count, err := dao.Words.Ctx(ctx).Where("uid", in.Uid).Where("word", in.Word).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("Word already exists") + } + return nil +} +``` + +In the `Logic` layer, we also need to ensure that the same user's words cannot be duplicated, keeping consistency with the database. + +### account logic +The 'uid' field is stored in the word list, and we need to provide the 'uid' by wrapping a 'GetUid' function in the 'logic/users' package. + +*internal/logic/users/account.go* +```go +func (u *Users) GetUid(ctx context.Context) (uint, error) { + user, err := u.Info(ctx) + if err != nil { + return 0, err + } + return user.Id, nil +} +``` + +## Controller Calls Logic +--- +In the controller where the words are created, we need to call both 'account' and 'Words' logic, which we encapsulate in the controller. + + +*internal/controller/words/words_new.go* +```go +... + + +package words + +import ( + "star/api/words" + usersL "star/internal/logic/users" + wordsL "star/internal/logic/words" +) + +type ControllerV1 struct { + users *usersL.Users + words *wordsL.Words +} + +func NewV1() words.IWordsV1 { + return &ControllerV1{ + users: &usersL.Users{}, + words: &wordsL.Words{}, + } +} +``` + + +*internal/controller/words/words_v1_create.go* +```go +package words + +import ( + "context" + + "star/internal/model" + "star/api/words/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + err = c.words.Create(ctx, &model.WordInput{ + Uid: uid, + Word: req.Word, + Definition: req.Definition, + ExampleSentence: req.ExampleSentence, + ChineseTranslation: req.ChineseTranslation, + Pronunciation: req.Pronunciation, + ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel), + }) + return nil, err +} +``` + +In the `Controller`, two `Logic` layer functions are called: `users.GetUid` and `words.Create` to implement the functionality. Note, do not directly call `users.GetUid` in `words.Create`, deleting `users.GetUid` from the `Controller` will increase the coupling of the `words` package. + +The best practice is **to ensure that the functionality of `Logic` functions is single, calling `Logic` multiple times in the `Controller` to achieve functionality.** + +## Register Controller +--- +*internal/cmd/cmd.go* +```go +package cmd + +... + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + group.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(middleware.Auth) + group.Bind( + account.NewV1(), + words.NewV1(), + ) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +The controller is registered under the same route group as `account.NewV1()` to ensure it can pass through the `Auth` middleware. + +## Interface Testing +--- +```bash +$ curl -X POST http://127.0.0.1:8000/v1/words \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "word": "example", + "definition": "A representative form or pattern.", + "example_sentence": "This is an example sentence.", + "chinese_translation": "例子", + "pronunciation": "ɪɡˈzɑːmp(ə)l", + "proficiency_level": 3 + }' + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` + +Execute the command to check if the data is added correctly: +```sql +$ SELECT * FROM words; +``` + +| id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at | +| --- | --- | ------- | --------------------------------- | ---------------------------- | ------------------- | ------------- | ----------------- | ------------------- | ------------------- | +| 1 | 1 | example | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..de3547001f0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" @@ -0,0 +1,160 @@ +--- +title: '4.4 Edit Word' +hide_title: true +slug: '/course/starbook/word-update' +keywords: [GoFrame, Word Update, REST API, PUT Request, Word Definition, Uniqueness Check, Update Logic, Permission Verification, Database Operation, Error Handling] +description: "Utilize the REST API in the GoFrame framework to update word information, including the word's definition, example sentences, Chinese translation, and pronunciation. Emphasizes the uniqueness check and permission verification logic during edit operations, and demonstrates how to use the database to update data and handle errors through code examples." +--- +Editing a word uses the `PUT` method, which represents updating a resource. +## Add Api +--- +*api/words/v1/words.go* +```go +type UpdateReq struct { + g.Meta `path:"words/{id}" method:"put" sm:"Update" tags:"Word"` + Id uint `json:"id" v:"required"` + Word string `json:"word" v:"required|length:1,100" dc:"Word"` + Definition string `json:"definition" v:"required|length:1,300" dc:"Word Definition"` + ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"Example Sentence"` + ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"Chinese Translation"` + Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"Pronunciation"` + ProficiencyLevel uint `json:"proficiency_level" v:"required|between:1,5" dc:"Proficiency Level, 1 minimum, 5 maximum"` +} + +type UpdateRes struct { +} +``` + +## Implement Logic +--- +During editing, we also need to check if the `word` is unique. The existing `checkWord` function is not sufficient, as it checks including itself, so we need to improve it: +- Add an `id` field; when the `id` is not `0`, use the combination of `id` and `word` for judgment; +- When `id` is `0`, it represents a new addition, and only `word` is used for judgment. + +*internal/logic/words/words.go* +```go +package words + +... + +func (w *Words) Create(ctx context.Context, in *model.WordInput) error { + if err := w.checkWord(ctx, 0, in); err != nil { + return err + } + ... +} + +// checkWord does not check itself during an update +func (w *Words) checkWord(ctx context.Context, id uint, in *model.WordInput) error { + db := dao.Words.Ctx(ctx).Where("uid", in.Uid).Where("word", in.Word) + if id > 0 { + db = db.WhereNot("id", id) + } + count, err := db.Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("Word already exists") + } + return nil +} +``` + +Add the update logic: + +*internal/logic/words/words.go* +```go +package words + +... + +func (w *Words) Update(ctx context.Context, id uint, in *model.WordInput) error { + if err := w.checkWord(ctx, id, in); err != nil { + return err + } + + db := dao.Words.Ctx(ctx).Where("uid", in.Uid).Data(do.Words{ + Word: in.Word, + Definition: in.Definition, + ExampleSentence: in.ExampleSentence, + ChineseTranslation: in.ChineseTranslation, + Pronunciation: in.Pronunciation, + ProficiencyLevel: in.ProficiencyLevel, + }).Where("id", id) + if in.Uid > 0 { + db = db.Where("uid", in.Uid) + } + + _, err := db.Update() + if err != nil { + return err + } + return nil +} + +... +``` + +When `Uid` is greater than zero, the `Uid` condition must be added to the `ORM` chain to prevent unauthorized access, the same applies to subsequent query and delete actions. + +## Controller Calls Logic +--- +*internal/controller/words/words_v1_update.go* +```go +package words + +import ( + "context" + + "star/internal/model" + "star/api/words/v1" +) + +func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + err = c.words.Update(ctx, req.Id, &model.WordInput{ + Uid: uid, + Word: req.Word, + Definition: req.Definition, + ExampleSentence: req.ExampleSentence, + ChineseTranslation: req.ChineseTranslation, + Pronunciation: req.Pronunciation, + ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel), + }) + return nil, err +} +``` + +## API Test +--- +```bash +$ curl -X PUT http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "word": "example_update", + "definition": "A representative form or pattern.", + "example_sentence": "This is an example sentence.", + "chinese_translation": "例子", + "pronunciation": "ɪɡˈzɑːmp(ə)l", + "proficiency_level": 3 + }' + +{ + "code": 0, + "message": "", + "data": null +} +``` +Execute the command to check if the data has been updated correctly: +```sql +$ SELECT * FROM words; +``` + +| id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at | +| --- | --- | -------------- | --------------------------------- | ---------------------------- | ------------------- | ------------- | ----------------- | ------------------- | ------------------- | +| 1 | 1 | example_update | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..6cd96a23eb9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" @@ -0,0 +1,183 @@ +--- +title: '4.5 Word Pagination' +hide_title: true +slug: '/course/starbook/word-list' +keywords: [GoFrame, Word Query, API Pagination, Fuzzy Search, Data Structure, Word List, Interface Testing, GoFrame Framework, API Request, Data Processing] +description: "Retrieve a paginated list of words using the GET method with fuzzy search functionality. Define a structure to store word fields, including ID, word, definition, and proficiency level. Write Logic in the GoFrame framework for querying and paginating data operations. Invoke Logic through the Controller to achieve data retrieval and return, supporting comprehensive interface testing." +--- +Retrieve a paginated list of words using the `GET` method, providing some input parameters: + +| Field Name | Type | json | valid | Description | +| ---- | -------- | ------------- | --------------- | --------- | +| Word | `string` | `json:"word"` | `length:1,100` | Fuzzy search word | +| Page | `int` | `json:"page"` | `min:1` | Page number, default 1 | +| Size | `int` | `json:"size"` | `between:1,100` | Number per page, default 10 | + +## Add Api +--- +First, define a structure to store the fields of a word. + +*api/words/v1/words_struct.go* +```go +package v1 + +import "github.com/gogf/gf/v2/os/gtime" + +type List struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ProficiencyLevel uint `json:"proficiencyLevel"` +} +``` + +In `ListRes`, return a slice of `List`, representing the word list, and `Total` indicates the total number of all words for pagination operations at the frontend. + +*api/words/v1/words.go* +```go +... + +type ListReq struct { + g.Meta `path:"words" method:"get" sm:"List" tags:"Word"` + Word string `json:"word" v:"length:1,100" dc:"Fuzzy search word"` + Page int `json:"page" v:"min:1" dc:"Page number, default 1"` + Size int `json:"size" v:"between:1,100" dc:"Number per page, default 10"` +} + +type ListRes struct { + List []List `json:"list"` + Total uint `json:"total"` +} +``` + +## Write Logic +--- +First, define a `Query` structure to be used as input for the query list, strictly implementing redefined data structures at each layer. + +*internal/model/words.go* +```go +... + +type WordQuery struct { + Uid uint + Word string + Page int + Size int +} +``` + +*internal/logic/words/words.go* +```go +... + +func (w *Words) List(ctx context.Context, query *model.WordQuery) (list []entity.Words, total uint, err error) { + if query == nil { + query = &model.WordQuery{} + } + // Handling initial values for queries + if query.Page == 0 { + query.Page = 1 + } + if query.Size == 0 { + query.Size = 15 + } + + // Compose the query chain + db := dao.Words.Ctx(ctx) + if query.Uid > 0 { + db = db.Where("uid", query.Uid) + } + + // Fuzzy search + if len(query.Word) != 0 { + db = db.WhereLike("word", fmt.Sprintf("%%%s%%", query.Word)) + } + db = db.Order("created_at desc, id desc").Page(query.Page, query.Size) + + data, totalInt, err := db.AllAndCount(true) + if err != nil { + return + } + + list = []entity.Words{} + _ = data.Structs(&list) + total = uint(totalInt) + + return +} +``` + +The above code uses `db.WhereLike("word", fmt.Sprintf("%%%s%%", query.Word))`, which means fuzzy search. It generates a `word LIKE '%{word}%'` clause. + +`AllAndCount` is used to query the data record list and total number simultaneously, commonly used in pagination scenarios to simplify pagination query logic. + +## Controller Calls Logic +--- +*internal/controller/words/words_v1_list.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" + "star/internal/model" +) + +func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + query := &model.WordQuery{ + Uid: uid, + Word: req.Word, + Page: req.Page, + Size: req.Size, + } + wordList, total, err := c.words.List(ctx, query) + if err != nil { + return nil, err + } + + var list []v1.List + for _, v := range wordList { + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: model.ProficiencyLevel(v.ProficiencyLevel), + }) + } + + return &v1.ListRes{ + List: list, + Total: total, + }, nil +} +``` + +## Interface Testing +--- +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ + "code": 0, + "message": "", + "data": { + "list": [ + { + "id": 1, + "word": "example_update", + "definition": "A representative form or pattern.", + "proficiencyLevel": 3 + } + ], + "total": 1 + } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" new file mode 100644 index 00000000000..e7aa5a5d920 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" @@ -0,0 +1,111 @@ +--- +title: '4.6 Word Detail' +hide_title: true +slug: '/course/starbook/word-detail' +keywords: [GoFrame, Word Detail, Chinese Translation, Fuzzy Routing, Example Sentence, Pronunciation, Interface Testing, GoFrame Framework, API, Detailed Information] +description: "The word detail interface obtains detailed information of a word not in the list through a GET request, including fields such as example sentence, Chinese translation, and pronunciation. It uses fuzzy routing to match and implements database query operations through the logic layer of the interface." +--- +The word detail also uses the `GET` method to retrieve detailed information of a word, including fields not present in the list such as example sentences and Chinese translations. +## Add Api +--- +*api/words/v1/words.go* +```go +... + +type DetailReq struct { + g.Meta `path:"words/{id}" method:"get" sm:"详情" tags:"单词"` + Id uint `json:"id" v:"required"` +} + +type DetailRes struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ExampleSentence string `json:"exampleSentence"` + ChineseTranslation string `json:"chineseTranslation"` + Pronunciation string `json:"pronunciation"` + ProficiencyLevel uint `json:"proficiencyLevel"` + CreatedAt *gtime.Time `json:"createdAt"` + UpdatedAt *gtime.Time `json:"updatedAt"` +} +``` + +`words/{id}` is a form of fuzzy routing matching, which recognizes routes like `words/1`, `words/2`, and `words/abc`, and assigns the matched value to the `Id` field. The type of the `Id` field is `uint`, and if the value is invalid, it will use the zero value of `uint`. For example, accessing `words/abc`, the value of the `Id` field will be `0`. + +## Write Logic +--- +*internal/logic/words/words.go* +```go +... + +func (w *Words) Detail(ctx context.Context, uid, id uint) (word *entity.Words, err error) { + word = &entity.Words{} + db := dao.Words.Ctx(ctx).Where("id", id) + if uid > 0 { + db = db.Where("uid", uid) + } + err = db.Scan(word) + return +} +``` + +## Controller Calls Logic +--- +*internal/controller/words/words_v1_detail.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + word, err := c.words.Detail(ctx, uid, req.Id) + if err != nil { + return nil, err + } + + return &v1.DetailRes{ + Id: word.Id, + Word: word.Word, + Definition: word.Definition, + ExampleSentence: word.ExampleSentence, + ChineseTranslation: word.ChineseTranslation, + Pronunciation: word.Pronunciation, + ProficiencyLevel: word.ProficiencyLevel, + CreatedAt: word.CreatedAt, + UpdatedAt: word.UpdatedAt, + }, nil +} +``` + +## Interface Testing +--- +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": { +        "id": 1, +        "word": "example_update", +        "definition": "A representative form or pattern.", +        "exampleSentence": "This is an example sentence.", +        "chineseTranslation": "例子", +        "pronunciation": "ɪɡˈzɑːmp(ə)l", +        "proficiencyLevel": 3, +        "createdAt": "2024-11-14 15:40:54", +        "updatedAt": "2024-11-14 16:09:37" +    } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..2490f84b562 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" @@ -0,0 +1,73 @@ +--- +title: '4.7 Delete Word' +hide_title: true +slug: '/course/starbook/word-delete' +keywords: [GoFrame Framework, DELETE Method, API Delete Word, Logic Layer Implementation, Interface Development, Controller Invocation, Request-Response, Context Management, Path Parameter, Database Operation] +description: "Implement word deletion functionality through API using DELETE method to request the deletion of a word with a specified ID. The Logic layer performs database deletion operations based on user ID and word ID, while the Controller layer handles the request and calls the logic to delete the word. Detailed interface testing steps are provided to verify the functionality implementation." +--- +Delete word using `DELETE` method. +## Add Api +--- +*api/words/v1/words.go* +```go +... +type DeleteReq struct { + g.Meta `path:"words/{id}" method:"delete" sm:"Delete" tags:"Word"` + Id uint `json:"id" v:"required"` +} + +type DeleteRes struct { +} +``` + +## Write Logic +--- +*internal/logic/words/words.go* +```go +... +func (w *Words) Delete(ctx context.Context, uid, id uint) (err error) { + db := dao.Words.Ctx(ctx).Where("id", id) + if uid > 0 { + db = db.Where("uid", uid) + } + _, err = db.Delete() + return +} +``` + +## Controller Invokes Logic +--- +*internal/controller/words/words_v1_delete.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + err = c.words.Delete(ctx, uid, req.Id) + return +} +``` + +## Interface Testing +--- +```bash +$ curl -X DELETE http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..fc87c110d46 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" @@ -0,0 +1,19 @@ +--- +title: '4.8 Summary' +hide_title: true +slug: '/course/starbook/word-summary' +keywords: [GoFrame, Word Management, Create Word, Read Word, Update Word, Delete Word, RESTful API, Database Operations, Pagination, Fuzzy Query] +description: "Developing word management functions based on RESTful, including creating, reading, updating, and deleting words, implementing enumeration value management status, using a layered data model for data operations, supporting optimization techniques for composite indexes, pagination, and fuzzy queries." +--- +In this chapter, we developed basic word-related functions based on `RESTful`: +- **Create Word**: Store words and related information into the database via `POST` requests; +- **Read Word**: Retrieve words and detailed information through `GET` requests, supporting pagination queries and `word` fuzzy queries; +- **Update Word**: Update existing word information via `PUT` requests; +- **Delete Word**: Delete specific words using `DELETE` requests. + +Learned some advanced techniques: +- Managing status using enumeration values; +- Layered data model concept to limit direct data transfer; +- Composite indexes; +- Pagination queries; +- Fuzzy queries. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..4db84eacc56 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +title: 'Chapter 4 - Word Management' +hide_title: true +sidebar_position: 4 +slug: '/course/starbook/word' +keywords: [GoFrame, GoFrame framework, documentation, website, course, tutorial, guide, development, framework] +description: "Course content for building word management using GoFrame, providing a detailed guide to help users better understand and apply the GoFrame framework." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" new file mode 100644 index 00000000000..af1494a45fc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" @@ -0,0 +1,18 @@ +--- +slug: '/course' +title: 'Community Tutorials' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame, GoFrame Framework, Learning Resources, Community Tutorials, Compilation Materials, Tutorial Submission, Code Collaboration, Material Updates, Resource Sharing, Tutorial Integration] +description: "Integrate high-quality learning resources of the GoFrame framework from the community to facilitate everyone's learning and usage. Thanks to the community teachers for contributing learning materials, and feel free to buy them a coffee for their efforts. The content of this chapter will be continuously updated, and contributions are welcome." +--- + +Hello everyone, we have integrated relatively high-quality learning resources from the community here to facilitate everyone's learning and use of the `GoFrame` framework 🚀. + +Thanks to all the community teachers who have worked hard to compile learning materials 💖💐! If you think they have done a good job, feel free to buy them a coffee ☕️! + +The materials in this chapter will be updated gradually, and teachers who are interested are welcome to actively sign up to share learning materials! + +Participation method: Submit your tutorial via `PR`, and we will provide full assistance. + +Repository address: https://github.com/gogf/gf-site \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" new file mode 100644 index 00000000000..da639d936db --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" @@ -0,0 +1,14 @@ +--- +slug: '/course/bilibili-video' +title: 'Introduction Video Tutorial' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame, GoFrame Framework, Introduction Tutorial, Video Tutorial, Infinite Thirteen Years, Learning Video, Step-by-Step, Bilibili, gf framework, Programming] +description: "Infinite Thirteen Years' introductory series of video tutorials, very suitable for beginners to learn in a step-by-step way, provides foundational knowledge and practical experience for building the GoFrame framework, helping users quickly get started and master the use of GoFrame." +--- + +The **introductory series of video tutorials** made by Infinite Thirteen Years is quite good 💖, +**This video is step-by-step, very suitable for beginner learning**: +https://www.bilibili.com/video/BV1Uu4y1u7kX + +![goframe introduction video tutorial](QQ_1731756142305.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" new file mode 100644 index 00000000000..d0cc936aac2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" @@ -0,0 +1,95 @@ +--- +slug: '/docs/web/cookie' +title: 'Cookie' +sidebar_position: 6 +hide_title: true +keywords: [Cookie, GoFrame, GoFrame Framework, ghttp, SessionId, API Documentation, SetCookie, HTTP Server, Session, Web Development] +description: "Use Cookie for session management in the GoFrame framework. Developers can easily get, set, and delete cookies through the ghttp.Request object. It also discusses obtaining and setting SessionId, handling cookie expiration, and simple methods to inherit and use session objects in controllers. These features provide powerful tools for web developers to manage user sessions, ensuring the flexibility and adaptability of web applications." +--- + +## Introduction + +API Documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Cookie](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Cookie) + +Common Methods: + +```go +type Cookie + func GetCookie(r *Request) *Cookie + func (c *Cookie) Contains(key string) bool + func (c *Cookie) Flush() + func (c *Cookie) Get(key string, def ...string) string + func (c *Cookie) GetSessionId() string + func (c *Cookie) Map() map[string]string + func (c *Cookie) Remove(key string) + func (c *Cookie) RemoveCookie(key, domain, path string) + func (c *Cookie) Set(key, value string) + func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) + func (c *Cookie) SetHttpCookie(httpCookie *http.Cookie) + func (c *Cookie) SetSessionId(id string) +``` + +The `Cookie` object corresponding to the current request can be obtained at any time through the `*ghttp.Request` object since both `Cookie` and `Session` are related to the request session and are therefore member objects of `ghttp.Request`, accessible to the public. The `Cookie` object does not need to be manually closed; the `HTTP Server` will automatically close it after the request cycle ends. + +Additionally, `Cookie` encapsulates two methods related to `SessionId`: + +1. `Cookie.GetSessionId()` is used to obtain the `SessionId` submitted by the current request. Each request's `SessionId` is unique and accompanies the entire request cycle. The value may be empty. +2. `Cookie.SetSessionId(id string)` is used to custom-set the `SessionId` into the `Cookie`, which is then returned to the client (often the browser) for storage. Subsequently, the client can carry the `SessionId` in `Cookie` with each request. + +When setting the `Cookie` variable, an expiration time can be given, which is an optional parameter. The default `Cookie` expiration time is one year. + +:::tip +The default storage name for `SessionId` in `Cookie` is `gfsession`. +::: + +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/cookie", func(r *ghttp.Request) { + datetime := r.Cookie.Get("datetime") + r.Cookie.Set("datetime", gtime.Datetime()) + r.Response.Write("datetime:", datetime) + }) + s.SetPort(8199) + s.Run() +} +``` + +Execute the outer `main.go`, and try refreshing the page [http://127.0.0.1:8199/cookie](http://127.0.0.1:8199/cookie), the displayed time keeps changing. + +For controller objects, many session-related object pointers inherited from the base class controller can be regarded as aliases and used directly, all pointing to the same object: + +```go +type Controller struct { + Request *ghttp.Request // Request data object + Response *ghttp.Response // Response data object (r.Response) + Server *ghttp.Server // WebServer object (r.Server) + Cookie *ghttp.Cookie // COOKIE operation object (r.Cookie) + Session *ghttp.Session // SESSION operation object + View *View // View object +} +``` + +Since `Cookie` is already a very familiar component to web developers, and the related `API` is very simple, further elaboration is not needed here. + +## `Cookie` Session Expiry + +The default validity period for `Cookie` is 1 year. If we want the Cookie to expire with the user's browsing session, like this: + +![](/markdown/6aca8ffefa9db267e2a4ecf1423ba6be.png) + +Then we only need to set the `Cookie` key-value pair via `SetCookie` and set the `maxAge` parameter to `0`. Like this: + +``` +r.Cookie.SetCookie("MyCookieKey", "MyCookieValue", "", "/", 0) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" new file mode 100644 index 00000000000..e4b1249b2cb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/web/http-client-proxy' +title: 'HTTPClient - Proxy' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, GoFrame Framework, HTTP Client, Proxy Settings, SetProxy, Proxy Method, HTTP Proxy, SOCKS5 Proxy, Chained Call, HTTP Request] +description: "Set proxy server addresses in the HTTP client of GoFrame framework, supporting both http and socks5 forms. Users can easily configure proxies through SetProxy and Proxy methods to access external resources, including examples of normal calls and chained calls, helping users quickly master the use of proxy functions." +--- + +## Proxy `Proxy` Settings + +When the HTTP client initiates a request, it can set the proxy server address `proxyURL`, which is implemented using `SetProxy*` related methods. The proxy mainly supports two forms: `http` and `socks5`, which are respectively in the form of `http://USER:PASSWORD@IP:PORT` or `socks5://USER:PASSWORD@IP:PORT`. + +Method list: + +```go +func (c *Client) SetProxy(proxyURL string) +func (c *Client) Proxy(proxyURL string) *Client +``` + +Let's look at an example of setting `proxyURL` with the client. + +## Normal Call Example + +Using the `SetProxy` configuration method. + +```go +client := g.Client() +client.SetProxy("http://127.0.0.1:1081") +client.SetTimeout(5 * time.Second) +response, err := client.Get(gctx.New(), "https://api.ip.sb/ip") +if err != nil { + fmt.Println(err) +} +response.RawDump() +``` + +## Chained Call Example + +Using the `Proxy` chained method. + +```go +client := g.Client() +response, err := client.Proxy("http://127.0.0.1:1081").Get(gctx.New(), "https://api.ip.sb/ip") +if err != nil { + fmt.Println(err) +} +fmt.Println(response.RawResponse()) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..2ddcb16b4d3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,150 @@ +--- +slug: '/docs/web/http-client-example' +title: 'HTTPClient - Examples' +sidebar_position: 0 +hide_title: true +keywords: [HTTP,GoFrame,GoFrame framework,HTTP client,GET request,POST request,JSON data,DELETE request,ghttp client,network request] +description: "Use the GoFrame framework to send GET, POST, DELETE requests through basic HTTP client operations and process the return values. This article also discusses how to send JSON data with the POST method, use multiple parameters, and map type parameters for requests. Additionally, it provides a brief introduction to *Bytes, *Content, and *Var methods to help developers handle HTTP requests and responses more conveniently." +--- + +The most basic usage of an `HTTP` client is to send requests using several operation methods named after the `HTTP Method`. **It's important to note that the resulting object returned needs to execute `Close` to prevent memory overflow**. Let's look at some simple examples of `HTTP` client requests. + +## Sending a `GET` request and printing the return value + +```go +if r, err := g.Client().Get(ctx, "https://goframe.org"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +## Sending a `GET` request to download a remote file + +```go +if r, err := g.Client().Get(ctx, "https://goframe.org/cover.png"); err != nil { + panic(err) +} +defer r.Close() +gfile.PutBytes("/Users/john/Temp/cover.png", r.ReadAll()) +``` + +Downloading a file is very simple for small files. It's worth noting that if the remote file is quite large, the server will return data in batches. Thus, the client will need to make multiple `GET` requests, each time requesting the batch file range length through the `Header`. Those interested can research the relevant details. + +## Sending a `POST` request and printing the return value + +```go +if r, err := g.Client().Post(ctx, "http://127.0.0.1:8199/form", "name=john&age=18"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +When passing multiple arguments, users can use the `&` symbol to connect them. Note that parameter values often need to be encoded using `gurl.Encode`. + +## Sending a `POST` request with parameters as a `map` type and printing the return value + +```go +if r, err := g.Client().Post( + ctx, + "http://127.0.0.1:8199/form", + g.Map{ + "submit" : "1", + "callback" : "http://127.0.0.1/callback?url=http://baidu.com", + } +)); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +When passing multiple arguments, users can use the `&` symbol to connect them or directly use `map` (**as previously mentioned, any data type is supported, including `struct`**). + +## Sending a `POST` request with parameters as `JSON` data and printing the return value + +```go +if r, err := g.Client().Post( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +As you can see, it's very convenient to send `JSON` data requests via the `ghttp` client, simply by using the `Post` method to submit. When `ContentType` is not explicitly set, the client will automatically recognize the parameter type and set the request's `Content-Type` to `application/json`. + +## Sending a `DELETE` request and printing the return value + +```go +if r, err := g.Client().Delete(ctx, "http://user.svc/v1/user/delete/1", "10000"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +## `*Bytes` and `*Content` Methods + +Request methods ending with the `Bytes` and `Content` suffix are **shortcut methods** for directly obtaining the return content. These methods will automatically read the content returned by the server **and automatically close the request connection**. The `*Bytes` methods are used to obtain results of type `[]byte`, while the `*Content` methods get results of type `string`. **It's important to note that if the request execution fails, the returned content will be empty.** + +## Sending a `GET` request and printing the return value + +```go +// The returned content is of type []byte +content := g.Client().GetBytes(ctx, "https://goframe.org") +``` + +```go +// The returned content is of type string +content := g.Client().GetContent(ctx, "https://goframe.org") +``` + +## Sending a `POST` request and printing the return value + +```go +// The returned content is of type []byte +content := g.Client().PostBytes( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +) +``` + +```go +// The returned content is of type string +content := g.Client().PostContent( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +) +``` + +## `*Var` Method + +Request methods ending with the `Var` suffix directly request and obtain `HTTP` API results as the generic type `g.Var`, **making it easy to execute type conversions, especially converting request results into struct objects**. It is generally used when the server returns data in `JSON/XML` format. By leveraging the returned `g.Var` generic object, you can perform automatic parsing based on your needs. Moreover, if the request fails or the request result is empty, an empty `g.Var` generic object will be returned, ensuring it doesn't affect the conversion method invocation. + +Example usage: + +```go +type User struct { + Id int + Name string +} +``` + +```go +// For a struct +var user *User +g.Client().GetVar(ctx, url).Scan(&user) +``` + +```go +// For an array of structs +var users []*User +g.Client().GetVar(ctx, url).Scan(&users) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..b8686c4ae74 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,46 @@ +--- +slug: '/docs/web/http-client-faq' +title: 'HTTPClient - FAQ' +sidebar_position: 9 +hide_title: true +keywords: [HTTPClient,GoFrame,GoFrame Framework,gclient.Client,connection pool,efficient,resource usage,short connection request,form request,ContentType] +description: "Explain how to effectively use the gclient.Client object in the GoFrame framework to improve efficiency and reduce resource usage. Includes suggestions for reusing the gclient.Client object and how to handle illegal character issues, demonstrated with examples on setting the correct ContentType." +--- + +## Should I preserve and reuse the created `gclient.Client` object? + +Whether you create a `gclient.Client` object through `g.Client` or the `gclient.New` method, this object should be preserved and reused instead of creating a new `Client` object each time. This approach can improve efficiency, reduce resource usage, and be friendly to `GC`. The object has a built-in connection pool design that can effectively manage a large number of short connection requests. Due to the fact that the `Client` object does not consume a lot of resources, many people might not pay much attention to this point. + +In what situations should I create a new `gclient.Client` object instead of reusing it? You can follow the decoupling design of business modules, where each business module manages its own `gclient.Client` object. Alternatively, when different scenarios require different configurations of the `Client`, you can create different `Client` objects to use. + +## `invalid semicolon separator in query` + +**Cause of the problem**: By default, **form requests** containing the `;` character are illegal (need `urlencode`). For details, please refer to the discussion: [https://github.com/golang/go/issues/25192](https://github.com/golang/go/issues/25192) + +**Error Example**: + +```bash +curl localhost:8000/Execute -d '{ + "Component": "mysql", + "ResourceId": "cdb-gy6hm0ee", + "Port": 6379, + "SQL": "show databases;", + "UserName": "root", + "Password": "" +}' +``` + +**Corrected Example**: + +When submitting the request, you need to specify the `ContentType`; for example, here it should indicate a `JSON` request. + +```bash +curl -X POST -H "Content-Type: application/json" localhost:8000/Execute -d '{ + "Component": "mysql", + "ResourceId": "cdb-gy6hm0ee", + "Port": 6379, + "SQL": "show databases;", + "UserName": "root", + "Password": "" +}' +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" new file mode 100644 index 00000000000..5759cb30d35 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" @@ -0,0 +1,180 @@ +--- +slug: '/docs/web/http-client-middleware' +title: 'HTTPClient - Middleware' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, HTTPClient, Interceptor, Middleware, Client Request, Parameter Validation, Signature Generation, API Security, Request Interception] +description: "The HTTPClient interceptor/middleware feature in the GoFrame framework can be used for global request interception and parameter validation. Through middleware, developers can insert custom logic in the pre and post phases of requests, modify submitted parameters or returned parameters, implement signature parameter injection, and more, ensuring the security of API parameters." +--- + +## Introduction + +`HTTPClient` supports a powerful interceptor/middleware feature, which makes global request interception and injection for the client possible, such as modifying/injecting submitted parameters, modifying/injecting returned parameters, client-based parameter validation, etc. Middleware injection is implemented through the following method: + +```go +func (c *Client) Use(handlers ...HandlerFunc) *Client +``` + +In middleware, execute the next step of the process through the `Next` method, which is defined as follows: + +```go +func (c *Client) Next(req *http.Request) (*Response, error) +``` + +## Types of Middleware + +The middleware feature in `HTTPClient` is similar to the middleware feature in `HTTPServer`, and is also divided into pre-middleware and post-middleware. + +### Pre-middleware + +Processing logic is before the `Next` method, formatted as follows: + +```go +c := g.Client() +c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + // Custom processing logic + resp, err = c.Next(r) + return resp, err +}) +``` + +### Post-middleware + +Processing logic is after the `Next` method, formatted as follows: + +```go +c := g.Client() +c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + resp, err = c.Next(r) + // Custom processing logic + return resp, err +}) +``` + +## Usage Example + +Let's use a code example to better illustrate usage. This example adds an interceptor to the client, injecting custom additional parameters into the submitted JSON data. These additional parameters implement signature generation for the submitted parameters, essentially achieving a simple API parameter security validation. + +### Server + +The server logic is straightforward, it parses the client's submitted `JSON` parameters as a `map` and then constructs a `JSON` string to return to the client. +:::note +Often, the server also needs to perform signature validation through middleware, but here I've taken a shortcut and directly returned the data submitted by the client. Please understand the documentation maintainer😸. +::: +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + r.Response.Write(r.GetMap()) + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +### Client + +The client logic implements basic client parameter submission, interceptor injection, signature-related parameter injection, and signature parameter generation. + +```go +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/guid" + "github.com/gogf/gf/v2/util/gutil" +) + +const ( + appId = "123" + appSecret = "456" +) + +// Inject unified API signature parameters +func injectSignature(jsonContent []byte) []byte { + var m map[string]interface{} + _ = json.Unmarshal(jsonContent, &m) + if len(m) > 0 { + m["appid"] = appId + m["nonce"] = guid.S() + m["timestamp"] = gtime.Timestamp() + var ( + keyArray = garray.NewSortedStrArrayFrom(gutil.Keys(m)) + sigContent string + ) + keyArray.Iterator(func(k int, v string) bool { + sigContent += v + sigContent += gconv.String(m[v]) + return true + }) + m["signature"] = gmd5.MustEncryptString(gmd5.MustEncryptString(sigContent) + appSecret) + jsonContent, _ = json.Marshal(m) + } + return jsonContent +} + +func main() { + c := g.Client() + c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + bodyBytes, _ := ioutil.ReadAll(r.Body) + if len(bodyBytes) > 0 { + // Inject signature-related parameters, modify the original submission parameters of the Request + bodyBytes = injectSignature(bodyBytes) + r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + r.ContentLength = int64(len(bodyBytes)) + } + return c.Next(r) + }) + content := c.ContentJson().PostContent(gctx.New(), "http://127.0.0.1:8199/", g.Map{ + "name": "goframe", + "site": "https://goframe.org", + }) + fmt.Println(content) +} +``` + +### Run Test + +First, run the server: + +```bash +$ go run server.go + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|---------|---------|--------|-------|-------------------|------------- + default | default | :8199 | ALL | / | main.main.func1.1 | +----------|---------|---------|--------|-------|-------------------|------------- + +2021-05-18 09:23:41.865 97906: http server started listening on [:8199] +``` + +Then, run the client: + +```bash +$ go run client.go +{"appid":"123","name":"goframe","nonce":"12vd8tx23l6cbfz9k59xehk1002pixfo","signature":"578a90b67bdc63d551d6a18635307ba2","site":"https://goframe.org","timestamp":1621301076} +$ +``` + +You can see that the server received parameters with several additional items, including `appid/nonce/timestamp/signature`, which are often parameters required by the signature verification algorithm. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" new file mode 100644 index 00000000000..b2dfa416742 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" @@ -0,0 +1,200 @@ +--- +slug: '/docs/web/http-client-file-uploading' +title: 'HTTPClient - File Uploading' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,HTTP Client,File Uploading,Server API,Form File,GoFrame Framework,Single File Upload,Multiple File Upload,File Path,Upload Parameters] +description: "Using the GoFrame framework for HTTP client file uploading, a convenient file upload feature is implemented, and three major APIs are provided to support single and multiple file uploads. Detailed explanations of both server and client implementation code are provided, along with methods for custom file naming and standardized routing to receive uploaded files, suitable for scenarios requiring integration of file upload functionality." +--- + +`GoFrame` supports very convenient form file uploading functionality, and the HTTP client has encapsulated the upload functionality to simplify the calling of the upload feature significantly. +:::warning +Note: The upload file size is affected by the `ClientMaxBodySize` configuration of `ghttp.Server`: [https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig). The default supported upload file size is `8MB`. +::: +## Server + +On the server side, use the `Request` object to get the uploaded files: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +// Upload uploads files to /tmp . +func Upload(r *ghttp.Request) { + files := r.GetUploadFiles("upload-file") + names, err := files.Save("/tmp/") + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit("upload successfully: ", names) +} + +// UploadShow shows uploading single file page. +func UploadShow(r *ghttp.Request) { + r.Response.Write(` + + + GoFrame Upload File Demo + + +
+ + +
+ + + `) +} + +// UploadShowBatch shows uploading multiple files page. +func UploadShowBatch(r *ghttp.Request) { + r.Response.Write(` + + + GoFrame Upload Files Demo + + +
+ + + +
+ + + `) +} + +func main() { + s := g.Server() + s.Group("/upload", func(group *ghttp.RouterGroup) { + group.POST("/", Upload) + group.ALL("/show", UploadShow) + group.ALL("/batch", UploadShowBatch) + }) + s.SetPort(8199) + s.Run() +} +``` + +The server provides three APIs: + +1. [http://127.0.0.1:8199/upload/show](http://127.0.0.1:8199/upload/show) for displaying a single file upload H5 page; +2. [http://127.0.0.1:8199/upload/batch](http://127.0.0.1:8199/upload/batch) for displaying a multiple files upload H5 page; +3. [http://127.0.0.1:8199/upload](http://127.0.0.1:8199/upload) API for real form file uploading, supporting both single and multiple file uploads. + +Visit [http://127.0.0.1:8199/upload/show](http://127.0.0.1:8199/upload/show) to choose a single file to upload. After submitting, you can see that the file has been successfully uploaded to the server. + +**Key Code Explanation** + +1. On the server side, we can obtain all uploaded file objects using the `r.GetUploadFiles` method, or a single uploaded file object using `r.GetUploadFile`. +2. The parameter `"upload-file"` in `r.GetUploadFiles("upload-file")` is the form file field name during the client-side upload in this example. Developers can define this on the client-side according to front-end and back-end agreements to facilitate the server-side's reception of form file fields. +3. Using `files.Save`, you can conveniently save multiple uploaded files to the specified directory and return the successfully saved file names. If a batch save operation is taking place, any single file save failure will immediately return an error. Additionally, the `Save` method's second parameter supports randomly auto-naming the uploaded files. +4. The route registered with `group.POST("/", Upload)` only supports access via `POST`. + +## Client + +### Single File Upload + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + var ( + ctx = gctx.New() + path = "/home/john/Workspace/Go/github.com/gogf/gf/v2/version.go" + ) + result, err := g.Client().Post(ctx, "http://127.0.0.1:8199/upload", "upload-file=@file:"+path) + if err != nil { + glog.Fatalf(ctx, `%+v`, err) + } + defer result.Close() + fmt.Println(result.ReadAllString()) +} +``` + +Did you notice? The file upload parameter format uses `parameter-name=@file:file-path`. The HTTP client will automatically parse the file content corresponding to the **file path** and read it to the server. The originally complicated file upload operation is encapsulated by `gf`, and users only need to use `@file:+file-path` to form the parameter value. The `file-path` should use the local file's absolute path. + +First, run the server program, then run this upload client (make sure to modify the upload file path to the actual file path locally), and upon execution, you can see the file being successfully uploaded to the specified path on the server. + +### Multiple File Upload + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + var ( + ctx = gctx.New() + path1 = "/Users/john/Pictures/logo1.png" + path2 = "/Users/john/Pictures/logo2.png" + ) + result, err := g.Client().Post( + ctx, + "http://127.0.0.1:8199/upload", + fmt.Sprintf(`upload-file=@file:%s&upload-file=@file:%s`, path1, path2), + ) + if err != nil { + glog.Fatalf(ctx, `%+v`, err) + } + defer result.Close() + fmt.Println(result.ReadAllString()) +} +``` + +As can be seen, the multiple file upload submission parameter format is `parameter-name=@file:xxx¶meter-name=@file:xxx...`, and can also be in the form of `parameter-name[]=@file:xxx¶meter-name[]=@file:xxx...`. + +First, run the server program, then run this upload client (make sure to modify the upload file path to the actual file path locally), and upon execution, you can see the files being successfully uploaded to the specified path on the server. + +## Custom File Name + +It's very simple, modify the `FileName` property. + +```go +s := g.Server() +s.BindHandler("/upload", func(r *ghttp.Request) { + file := r.GetUploadFile("TestFile") + if file == nil { + r.Response.Write("empty file") + return + } + file.Filename = "MyCustomFileName.txt" + fileName, err := file.Save(gfile.TempDir()) + if err != nil { + r.Response.Write(err) + return + } + r.Response.Write(fileName) +}) +s.SetPort(8999) +s.Run() +``` + +## Standardized Routing to Receive Uploaded Files + +If the server uses standardized routing, structured parameters can be used to retrieve uploaded files: + +- The data type for receiving parameters uses `*ghttp.UploadFile`. +- If the API documentation should also support file types, set `type` to `file` in the parameter tags. + +![](/markdown/3146c1c8d37ca3745a3519f96361de6a.png) + +![](/markdown/57f441f1e4666f73cc320a4e3d47f50b.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..0ada06e63c2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/web/http-client-metrics' +title: 'HTTPClient - Metrics' +sidebar_position: 10 +hide_title: true +keywords: [HTTP Client, Monitoring Metrics, Performance Optimization, Request Time, GoFrame, Connection Time, Total Requests, Request Size, GoFrame Framework, Response Size] +description: "The monitoring metrics feature of the HTTP client is disabled by default to avoid performance impact. It provides a variety of metrics for users to reference, such as the time cost of request execution, connection creation time, and the total size of request bytes, among others. These metrics are enabled only when the metric feature is globally enabled, helping users perform better performance analysis." +--- + +The `HTTP` client supports monitoring capabilities, which are off by default to avoid performance impact. The monitoring and metrics calculation features are enabled by default only when the global `metric` feature is activated. + +## Metrics List + +| **Metric Name** | **Metric Type** | **Metric Unit** | **Metric Description** | +| --- | --- | --- | --- | +| `http.client.request.duration` | `Histogram` | `ms` | Time cost of client request execution. | +| `http.client.request.duration_total` | `Counter` | `ms` | Total time cost used by each request. | +| `http.client.connection.duration` | `Histogram` | `ms` | Time cost used for creating a connection. | +| `http.client.request.total` | `Counter` | | Total number of requests completed. | +| `http.client.request.active` | `Gauge` | | Number of requests currently being processed. | +| `http.client.request.body_size` | `Counter` | `bytes` | Total size of request bytes. | +| `http.client.response.body_size` | `Counter` | `bytes` | Total size of returned bytes. | + +## Attributes List + +| **Label Name** | **Label Description** | **Label Example** | +| --- | --- | --- | +| `server.address` | Target service address of the request. It could be a domain name or IP address. | `goframe.org`
`10.0.1.132` | +| `server.port` | Target service port of the request. | `8000` | +| `http.request.method` | Request method name. | `GET`; `POST`; `DELETE` | +| `http.response.status_code` | `HTTP` status code of the response. | `200` | +| `url.schema` | Protocol used for the request. | `http`
`https` | +| `network.protocol.version` | Version of the request protocol. | `1.0`
`1.1` | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" new file mode 100644 index 00000000000..3e54874888f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/web/http-client-content-type' +title: 'HTTPClient - ContentType' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, HTTPClient, ContentType, Json Request, Xml Request, Custom ContentType, PostContent, url encode, web request] +description: "Use HTTPClient in GoFrame framework to customize the request's ContentType. Through different operations like ContentJson and ContentXml, you can set the request's Content-Type to application/json and application/xml respectively. It also provides examples of customizing ContentType, helping developers flexibly set request parameters and encoding methods to meet different API request needs." +--- + +## Example 1, Submit `Json` Request + +```go +g.Client().ContentJson().PostContent(ctx, "http://order.svc/v1/order", g.Map{ + "uid" : 1, + "sku_id" : 10000, + "amount" : 19.99, + "create_time" : "2020-03-26 12:00:00", +}) +``` + +By calling the `ContentJson` chain operation method, this request will set the `Content-Type` to `application/json` and automatically encode the submission parameters to `Json`: + +``` +{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"} +``` + +## Example 2, Submit `Xml` Request + +```go +g.Client().ContentXml().PostContent(ctx, "http://order.svc/v1/order", g.Map{ + "uid" : 1, + "sku_id" : 10000, + "amount" : 19.99, + "create_time" : "2020-03-26 12:00:00", +}) +``` + +By calling the `ContentXml` chain operation method, this request will set the `Content-Type` to `application/xml` and automatically encode the submission parameters to `Xml`: + +``` +19.992020-03-26 12:00:00100001 +``` + +## Example 3, Custom `ContentType` + +We can customize the client's request `ContentType` through the `ContentType` method. If the given parameter is `string/[]byte`, the client will directly submit the parameter to the server; if it is another data type, the client will automatically perform `url encode` on the parameter before submitting it to the server. + +Example 1: + +```go +g.Client().ContentType("application/json").PostContent( + ctx, + "http://order.svc/v1/order", + `{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"}`, +) +``` + +or + +```go +g.Client().ContentType("application/json; charset=utf-8").PostContent( + ctx, + "http://order.svc/v1/order", + `{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"}`, +) +``` + +Submitted parameters are as follows: + +``` +{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"} +``` + +Example 2: + +```go +g.Client().ContentType("application/x-www-form-urlencoded; charset=utf-8").GetContent( + ctx, + "http://order.svc/v1/order", + g.Map{ + "category" : 1, + "sku_id" : 10000, + }, +) +``` + +Submitted parameters are as follows: + +``` +category=1&sku_id=10000 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" new file mode 100644 index 00000000000..d5b1e99b1c2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" @@ -0,0 +1,107 @@ +--- +slug: '/docs/web/http-client-cookie' +title: 'HTTPClient - Cookie' +sidebar_position: 2 +hide_title: true +keywords: [HTTPClient, Custom Cookie, GoFrame, GoFrame Framework, SetCookie, SetCookieMap, HTTP Client, ghttp, Cookie, Server] +description: "Customize the Cookie content sent to the server using the HTTP client in the GoFrame framework, mainly implemented through the SetCookie and SetCookieMap methods. Demonstrated with simple server and client examples on how to set and receive custom Cookie parameters, achieving personalized HTTP client requests." +--- + +When an HTTP client initiates a request, it can customize the `Cookie` content sent to the server. This feature is implemented using the `SetCookie*` related methods. + +Method list: + +```go +func (c *Client) SetCookie(key, value string) *Client +func (c *Client) SetCookieMap(m map[string]string) *Client +``` + +Let's look at an example of a client customizing `Cookie`. + +### Server + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Write(r.Cookie.Map()) + }) + s.SetPort(8199) + s.Run() +} +``` + +As this is an example, the server logic is straightforward, directly returning all received `Cookie` parameters to the client. + +### Client + +1. Using the `SetCookie` method + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetCookie("name", "john") + c.SetCookie("score", "100") + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +Create a custom HTTP request client object using `g.Client()` and set custom `Cookie` using the `c.SetCookie` method. Here, we set two example `Cookie` parameters, one `name`, and one `score`. + +2. Using the `SetCookieMap` method + +This method is simpler and allows setting `Cookie` key-value pairs in bulk. + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetCookieMap(g.MapStrStr{ + "name": "john", + "score": "100", + }) + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +3. Execution Result + +After executing the client code, the terminal will print the server's response as follows: + +``` +map[name:john score:100] +``` + +As you can see, the server has received the custom `Cookie` parameters from the client. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" new file mode 100644 index 00000000000..87bf119f734 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/web/http-client-header' +title: 'HTTPClient - Header' +sidebar_position: 3 +hide_title: true +keywords: [HTTPClient, Custom Header, GoFrame, GoFrame Framework, SetHeader, Header Method, Span-Id, Trace-Id, HTTP Request, Client] +description: "With the HTTPClient feature of the GoFrame framework, users can customize the Header information of HTTP requests. This article introduces how to set and send Headers using methods like SetHeader, SetHeaderMap, and SetHeaderRaw, thus implementing custom tracing information such as Span-Id and Trace-Id. Simple code examples demonstrate how the client interacts with the server and returns results." +--- + +When the HTTP client sends a request, it can customize the `Header` content sent to the server, a feature implemented using the `SetHeader*` related methods. + +Method List: + +```go +func (c *Client) SetHeader(key, value string) *Client +func (c *Client) SetHeaderMap(m map[string]string) *Client +func (c *Client) SetHeaderRaw(headers string) *Client +``` + +Let's take a look at an example where the client customizes the sending of custom tracing information `Span-Id` and `Trace-Id` message headers using `Header`. + +### Server + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writef( + "Span-Id:%s,Trace-Id:%s", + r.Header.Get("Span-Id"), + r.Header.Get("Trace-Id"), + ) + }) + s.SetPort(8199) + s.Run() +} +``` + +Since this is an example, the server logic is straightforward, directly returning the received `Span-Id` and `Trace-Id` parameters to the client. + +### Client + +1. Using the `SetHeader` Method + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetHeader("Span-Id", "0.0.1") + c.SetHeader("Trace-Id", "NBC56410N97LJ016FQA") + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +Create a custom HTTP request client object using `g.Client()` and set custom `Header` information using `c.SetHeader`. + +2. Using the `SetHeaderRaw` Method + +This method is simpler and allows setting the client request Header via a raw Header string. + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetHeaderRaw(` + Referer: https://localhost + Span-Id: 0.0.1 + Trace-Id: NBC56410N97LJ016FQA + User-Agent: MyTestClient + `) + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +3. Execution Result + +After executing the client code, the terminal will print the result returned by the server, as follows: + +``` +Span-Id:0.0.1,Trace-Id:NBC56410N97LJ016FQA +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" new file mode 100644 index 00000000000..98ee506dfee --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" @@ -0,0 +1,86 @@ +--- +slug: '/docs/web/http-client-transport' +title: 'HTTPClient - Transport' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame framework, HTTPClient, Transport, Unix Socket, Custom Transport, http.Client, gclient.Client, Client Connection Pool, MaxIdleConnsPerHost] +description: "In the GoFrame framework, advanced usage of HTTPClient is achieved through custom Transport. This includes methods of client-server communication using Unix Socket and specific implementations for setting the size of the client connection pool parameters. The examples provide a wealth of real code snippets to help developers better understand and apply these techniques." +--- + +Since `gclient.Client` internally wraps and extends the `http.Client` object from the standard library, any features available in `http.Client` are also supported by `gclient.Client`. Here, we are discussing examples of Transport usage. Let's look at a few examples: + +## Using `Unix Socket` + +The client and server communicate using `Unix Socket`, implemented using `Transport`. The following code is excerpts from a real project and cannot run independently, only for reference. + +```go +func (*Guardian) ConvertContainerPathToHostPath( + ctx context.Context, namespace, podName, containerName, containerPath string, +) (string, error) { + var ( + client = g.Client() + url = "http://localhost/api/v1/pod/path" + req = webservice.HostPathInfoReq{ + Namespace: namespace, + PodName: podName, + ContainerName: containerName, + ContainerPath: containerPath, + } + res *webservice.HostPathInfoRes + ) + client.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", serviceSocketPath) + }, + } + err := client.ContentJson().GetVar(ctx, url, req).Scan(&res) + if err != nil { + return "", gerror.Wrapf( + err, + `request guardian failed for url: %s, req: %s`, + url, gjson.MustEncodeString(req), + ) + } + if res == nil { + return "", gerror.Newf( + `nil response from guardian request url: %s, req: %s`, + url, gjson.MustEncodeString(req), + ) + } + return res.HostPath, nil +} +``` + +## Setting Client Connection Pool Size Parameters + +```go +func ExampleNew_MultiConn_Recommend() { + var ( + ctx = gctx.New() + client = g.Client() + ) + + // controls the maximum idle (keep-alive) connections to keep per-host + client.Transport.(*http.Transport).MaxIdleConnsPerHost = 5 + + for i := 0; i < 5; i++ { + go func() { + if r, err := client.Get(ctx, "http://127.0.0.1:8999/var/json"); err != nil { + panic(err) + } else { + fmt.Println(r.ReadAllString()) + r.Close() + } + }() + } + + time.Sleep(time.Second * 1) + + // Output: + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..f4b340334bc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" @@ -0,0 +1,77 @@ +--- +slug: '/docs/web/http-client-raw-request-response' +title: 'HTTPClient - Raw' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, HTTP Client, Request Information Printing, Raw Request, Debugging, Response Information, HTTP Request, Go Language, Web Development] +description: "Use the HTTP client feature in the GoFrame framework to obtain and print raw input and output information of HTTP requests. The main methods include Raw, RawDump, RawRequest, and RawResponse, which are useful for debugging HTTP requests. The example demonstrates the method of sending POST requests using the GoFrame framework and printing the request and response." +--- + +## Introduction + +The `http` client supports obtaining and printing the raw input and output information of HTTP requests, which is convenient for debugging. The related methods are as follows: + +```go +func (r *Response) Raw() string +func (r *Response) RawDump() +func (r *Response) RawRequest() string +func (r *Response) RawResponse() string +``` + +As you can see, all methods are bound to the `Response` object, which means they can only be printed after the request is completed. + +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + response, err := g.Client().Post( + gctx.New(), + "https://goframe.org", + g.Map{ + "name": "john", + }, + ) + if err != nil { + panic(err) + } + response.RawDump() +} +``` + +After execution, the terminal output is: + +``` ++---------------------------------------------+ +| REQUEST | ++---------------------------------------------+ +POST / HTTP/1.1 +Host: goframe.org +User-Agent: GoFrameHTTPClient v2.0.0-beta +Content-Length: 9 +Content-Type: application/x-www-form-urlencoded +Accept-Encoding: gzip + +name=john + ++---------------------------------------------+ +| RESPONSE | ++---------------------------------------------+ +HTTP/1.1 405 Method Not Allowed +Connection: close +Transfer-Encoding: chunked +Allow: GET +Cache-Control: no-store +Content-Security-Policy: frame-ancestors 'self' +Content-Type: text/html;charset=UTF-8 +Date: Fri, 03 Dec 2021 09:43:29 GMT +Expires: Thu, 01 Jan 1970 00:00:00 GMT +Server: nginx +... +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" new file mode 100644 index 00000000000..f6aab1cf3ce --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" @@ -0,0 +1,87 @@ +--- +slug: '/docs/web/http-client' +title: 'HTTPClient' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame Framework,HTTP Client,gclient,HTTP Request,Chain Operations,HTTP Methods,Custom Requests,Connection Pool,Return Objects] +description: "The powerful HTTP client component gclient provided by the GoFrame framework supports convenient chain operations for HTTP requests. The client supports custom request settings and return object operations, with detailed instructions on setting parameters such as timeout, Cookie, Header, etc." +--- + +## Introduction + +The `GoFrame` framework offers a powerful and easy-to-use `HTTP` client, implemented by the `gclient` component. Object creation can be done through the package method `gclient.New()` or by calling the `g.Client()` method. It is recommended to use `g.Client()` to conveniently create an `HTTP` client object. Since `gclient.Client` is internally extended from the standard library's `http.Client` object, any features present in `http.Client` are also supported by `gclient.Client`. + +**Method List**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient](https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient) + +**Brief Explanation**: + +1. You can use `New` to create a custom HTTP client object `Client`, which can then be used to execute requests. This object employs a connection pool design internally, so it does not have a `Close` method. An `HTTP` client object can also be created via the `g.Client()` shortcut method. +2. The client provides a series of methods named after `HTTP Methods`. Invoking these methods will initiate the corresponding `HTTP Method` requests. The commonly used methods are `Get` and `Post`, while `DoRequest` is the core request method that users can call to implement custom `HTTP Method` requests. +3. The result of the request is a `*ClientResponse` object. You can obtain the corresponding return results through this object. The `ReadAll`/`ReadAllString` methods can be used to obtain the returned content. After use, this object needs to be closed through the `Close` method to prevent memory overflow. +4. The `*Bytes` method is used to obtain the binary data returned by the server. If the request fails, it returns `nil`; the `*Content` method is used to request string result data. If the request fails, it returns an empty string; the `Set*` method is for setting parameters of the `Client`. +5. The `*Var` method directly requests and retrieves HTTP API results as a generic type for easy conversion. If the request fails or the request result is empty, an empty `g.Var` generic object is returned, which does not affect the invocation of conversion methods. +6. As can be seen, the data parameter `data` for the client's request parameters is of the `interface{}` type, meaning any data type can be passed. Common parameter data types are `string`/`map`. If the parameter is of `map` type, the parameter value will be automatically `urlencode` encoded. + +:::warning +Please use the given methods to create a `Client` object, and do not use `new(ghttp.Client)` or `&ghttp.Client{}` to create a client object, otherwise, hmm. +::: +## Chain Operations + +The client in the `GoFrame` framework supports convenient chain operations. The commonly used methods are as follows (the document method list may lag behind the source code, so it is recommended to check the API documentation or source code [https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient](https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient)): + +```go +func (c *Client) Timeout(t time.Duration) *Client +func (c *Client) Cookie(m map[string]string) *Client +func (c *Client) Header(m map[string]string) *Client +func (c *Client) HeaderRaw(headers string) *Client +func (c *Client) ContentType(contentType string) *Client +func (c *Client) ContentJson() *Client +func (c *Client) ContentXml() *Client +func (c *Client) BasicAuth(user, pass string) *Client +func (c *Client) Retry(retryCount int, retryInterval time.Duration) *Client +func (c *Client) Prefix(prefix string) *Client +func (c *Client) Proxy(proxyURL string) *Client +func (c *Client) RedirectLimit(redirectLimit int) *Client +func (c *Client) Dump(dump ...bool) *Client +func (c *Client) Use(handlers ...HandlerFunc) *Client +``` + +Brief Explanation: + +1. The `Timeout` method is used to set the current request timeout. +2. The `Cookie` method is used to set custom `Cookie` information for the current request. +3. The `Header*` methods are used to set custom `Header` information for the current request. +4. The `Content*` methods are used to set the `Content-Type` information for the current request, and they support automatically checking submitted parameters and encoding according to this information. +5. The `BasicAuth` method is used to set the `HTTP Basic Auth` validation information. +6. The `Retry` method is used to set the number of retries and retry interval after a request failure. +7. The `Proxy` method is used to set up an http access proxy. +8. The `RedirectLimit` method is used to limit the number of redirect hops. + +## Return Objects + +`gclient.Response` is the corresponding return result object of an HTTP request, which inherits from `http.Response` and can use all methods of `http.Response`. It also adds the following methods: + +```go +func (r *Response) GetCookie(key string) string +func (r *Response) GetCookieMap() map[string]string +func (r *Response) Raw() string +func (r *Response) RawDump() +func (r *Response) RawRequest() string +func (r *Response) RawResponse() string +func (r *Response) ReadAll() []byte +func (r *Response) ReadAllString() string +func (r *Response) Close() error +``` + +It should be noted that `Response` requires a manual call to the `Close` method to close it. This means that regardless of whether you use the returned `Response` object or not, you need to assign this return object to a variable and manually call its `Close` method for closure (often using `defer r.Close()`), otherwise, it will cause file handle overflow and memory overflow. + +## Important Notes + +1. The `ghttp` client defaults to disabling the `KeepAlive` feature and the verification function for the server's `TLS` certificate. If you need to enable it, you can customize the client's `Transport` attribute. +2. These advanced functions such as **Connection Pool Parameter Setting** and **Connection Proxy Settings** can also be achieved by customizing the client's `Transport` attribute. This data inherits from the standard library's `http.Transport` object. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" new file mode 100644 index 00000000000..77a5fac1485 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/web/session-file' +title: 'Session - File' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Session,File Storage,ghttp.Server,StorageFile,gcache,Serialization,Deserialization,Session Management] +description: "Using the ghttp.Server of the GoFrame framework to implement file storage for sessions. By default, session storage uses a combination of memory and files through StorageFile for persistent management. Thanks to the gcache module, session data operations are efficient, especially suitable for scenarios with more reads and fewer writes. Meanwhile, the demonstration example shows how to set and get sessions in a GoFrame project." +--- + +## File Storage + +By default, the session storage of `ghttp.Server` uses a combination of `memory + file`, implemented through the `StorageFile` object. The specific principles are: + +1. Session data operations are entirely memory-based; +2. The `gcache` process caching module controls data expiration; +3. File storage is used for persistent management of session data; +4. Session serialization and file persistence storage are executed only when a session is marked as `dirty` (data has been updated); +5. Deserialization from file storage to restore session data to memory only occurs when the session does not exist in memory, reducing IO calls; +6. Serialization/deserialization uses the `json.Marshal/UnMarshal` methods from the standard library. + +As can be seen from the principles, session data operations are very efficient in scenarios with more reads and fewer writes. +:::tip +A detail to note is that because file storage involves file operations, to reduce IO overhead and improve session operation performance, the TTL of the corresponding session is not immediately refreshed after each session request ends. It is only immediately refreshed when it involves update operations (marked as `dirty`). For read requests, the TTL of the session files corresponding to read operations in the previous minute is updated every `one minute`, allowing the session to automatically persist. +::: + +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this instance, to easily observe expiration and invalidation, we set the session expiration time to `1 minute`. After execution: + +1. First, visit [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) to set a session variable; +2. Then, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) to see that the session variable is set and successfully retrieved; +3. Next, stop the program, restart it, and visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again to see that the session variable has been restored from file storage; +4. After waiting for 1 minute, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again to see that the session can no longer be retrieved because it has expired. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" new file mode 100644 index 00000000000..6f643895239 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/web/session-memory' +title: 'Session - Memory' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,Session Storage,Memory Storage,Session Data,StorageMemory,gsession,GoFrame Framework,Session Example,Session Setting,Session Persistence] +description: "Use memory storage to implement Session functionality in the GoFrame framework. Memory storage is simple and efficient, but does not support persistence, so Session data will be lost after the application restarts. Through example code, it is explained in detail how to set the expiration time of the Session and how to store and retrieve Session data." +--- + +## Memory Storage + +Memory storage is relatively simple and efficient, but it does not persistently store `Session` data, so the `Session` data will be lost after the application restarts. It can be used in specific business scenarios. The `memory` storage of `gsession` is implemented using the `StorageMemory` object. + +## Example Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageMemory()) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.MustSet("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this example, to conveniently observe expiration, we set the `Session` expiration time to `1 minute`. After executing, + +1. First, visit [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) to set a `Session` variable; +2. Then, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) to see that the `Session` variable has been set and successfully retrieved; +3. Next, we stop the program and restart it. Visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again to see that the `Session` variable is gone; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" new file mode 100644 index 00000000000..d83da641f19 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/web/session-redis-hash-table' +title: 'Session - Redis-HashTable' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame Framework,RedisHashTableStorage,Session,Redis Storage,Session Management,Go Development,Web Development,Session Expiration,Session Example] +description: "In the GoFrame framework, use RedisHashTableStorage for session management. Unlike RedisKeyValueStorage, this method operates directly through the Redis service without needing a full data fetch. The example code illustrates basic session setting, retrieval, and deletion operations, and how to integrate this feature in GoFrame." +--- + +## RedisHashTableStorage + +Unlike `RedisKeyValueStorage`, `RedisHashTableStorage` uses a `HashTable` to store `Session` data at its core. Each addition, deletion, or query operation on `Session` is directly implemented by accessing the `Redis` service (single data item operation). There is no full data fetch as in `RedisKeyValueStorage`, which fetches all data once during initialization and updates all data to the `Redis` service after modifications when the request ends. + +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageRedisHashTable(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this example, to conveniently observe expiration, the session expiration time is set to `1 minute`. After running: + +1. First, visit [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) to set a session variable; +2. Then, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) to see the session variable has been set and successfully retrieved; +3. Next, stop the program, restart it, and visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again. You can see that the session variable has been restored from the Redis storage. If we manually modify the corresponding key-value data in Redis, the page refresh will read the latest value; +4. After waiting for 1 minute, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again and see that the session cannot be retrieved because it has expired. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" new file mode 100644 index 00000000000..f6e088bfc1d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/web/session-redis-key-value' +title: 'Session - Redis-KeyValue' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, Redis, Session, KeyValue, multi-node deployment, StorageRedis, memory + Redis, JSON serialization, HashTable Storage] +description: "Using Redis in the GoFrame framework for Session KeyValue storage to solve the issue of Session sharing across multiple nodes. By using the StorageRedis object to implement Redis storage, it improves execution efficiency, suitable for scenarios with small amounts of user Session data, and provides specific examples and explanations. In the example, the Session expiration time is set to 1 minute, demonstrating methods of setting, getting, deleting Sessions, and the recovery function of Session data in Redis." +--- + +## Redis KeyValue Storage + +The file storage method works well in single-node scenarios, but when it comes to deploying an application across multiple nodes, the `Session` cannot be shared between nodes, making it necessary to separate `Session` storage for independent management. The `Redis` server is a common choice. + +The `gsession` Redis storage uses the `StorageRedis` object, similar to file storage. To improve execution efficiency, it adopts a `memory + Redis` approach. The only difference from file storage is that if `Session` operations are needed during each request, the latest `Session` data will be pulled from `Redis` (file storage only reads a file once when the `Session` does not exist). At the end of each request, the entire `Session` data is serialized using `JSON` and updated to the `Redis` service using the `KeyValue` method. +:::tip +For business scenarios where the `Session` data of each user is not large (taking the user dimension as an example), this storage method is recommended. If the `Session` data for a single user is large (e.g., `>10MB`), you can refer to the `HashTable` storage method: [Session - Redis-HashTable](Session-Redis-HashTable.md) +::: +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this instance, to facilitate observing expiration, we set the `Session` expiration time to `1 minute`. After execution, + +1. First, visit [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) to set a `Session` variable; +2. Then, visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) to see that the `Session` variable has been set and successfully retrieved; +3. Next, stop the program, restart it, and visit [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again, you will see the `Session` variable has been restored from `Redis` storage; if we manually modify the corresponding key-value data in `Redis`, the latest value will be read when the page is refreshed; +4. After waiting 1 minute, visiting [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) again shows that the `Session` cannot be retrieved since it has expired; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..db64811a878 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,72 @@ +--- +slug: '/docs/web/session-storage' +title: 'Session - Storage Interface' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gsession, Session-Storage, custom storage, interface development, Storage interface, TTL, gmap, session management] +description: "Developing Session-Storage interfaces using the gsession component in the GoFrame framework. The built-in Storage implementation within the component can meet the needs of most business scenarios. Developers can also customize session storage according to specific cases. The article describes in detail the definition of the Storage interface and its invocation timing. To improve session performance, it is recommended to use the gmap container type. This guide will help developers better implement and optimize storage interfaces." +--- + +In most scenarios, the common `Storage` implementations provided by the built-in `gsession` component are sufficient to meet requirements. If there are special scenarios that require the customization of `Storage`, it is certainly supported, as the functionality of `gsession` is designed with interfaces in mind. + +## Storage Definition + +[https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go](https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go) + +```go +// Storage is the interface definition for session storage. +type Storage interface { + // New creates a custom session id. + // This function can be used for custom session creation. + New(ctx context.Context, ttl time.Duration) (id string, err error) + + // Get retrieves and returns session value with given key. + // It returns nil if the key does not exist in the session. + Get(ctx context.Context, id string, key string) (value interface{}, err error) + + // GetMap retrieves all key-value pairs as map from storage. + GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) + + // GetSize retrieves and returns the size of key-value pairs from storage. + GetSize(ctx context.Context, id string) (size int, err error) + + // Set sets one key-value session pair to the storage. + // The parameter `ttl` specifies the TTL for the session id. + Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error + + // SetMap batch sets key-value session pairs as map to the storage. + // The parameter `ttl` specifies the TTL for the session id. + SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error + + // Remove deletes key with its value from storage. + Remove(ctx context.Context, id string, key string) error + + // RemoveAll deletes all key-value pairs from storage. + RemoveAll(ctx context.Context, id string) error + + // GetSession returns the session data as `*gmap.StrAnyMap` for given session id from storage. + // + // The parameter `ttl` specifies the TTL for this session. + // The parameter `data` is the current old session data stored in memory, + // and for some storage it might be nil if memory storage is disabled. + // + // This function is called ever when session starts. It returns nil if the TTL is exceeded. + GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) + + // SetSession updates the data for specified session id. + // This function is called ever after session, which is changed dirty, is closed. + // This copy all session data map from memory to storage. + SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error + + // UpdateTTL updates the TTL for specified session id. + // This function is called ever after session, which is not dirty, is closed. + UpdateTTL(ctx context.Context, id string, ttl time.Duration) error +} +``` + +The timing of each method's invocation is explained in detail within the comments, and developers can fully refer to the several built-in `Storage` implementations when implementing custom `Storage`. + +## Considerations + +- In the `Storage` interface, not all interface methods need to be implemented. Developers only need to implement some interfaces according to the specific invocation timing required by their business needs. +- To enhance the execution performance of `Session`, the interface uses the `gmap.StrAnyMap` container type. During development, you can refer to this section: [Map](../../组件列表/数据结构/字典类型-gmap/字典类型-gmap.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" new file mode 100644 index 00000000000..4eec6e76385 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/web/session' +title: 'Session' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame framework,Session management,gsession,HTTP service,SessionId,Concurrency safety,ghttp.Request,gsession module,Session storage] +description: "The Session management features in the GoFrame framework, including the basic concepts of Session, the implementation of the gsession module, and its applications in different scenarios. The document discusses in detail the methods of SessionId transmission, initialization, destruction, and provides four common Session storage implementations and their characteristics, offering a rich set of tools for developers in Session management in HTTP and other service environments." +--- + +The `GoFrame` framework provides comprehensive `Session` management capabilities, implemented by the `gsession` component. Since the `Session` mechanism is most commonly used in `HTTP` services, the subsequent chapters will focus on the use of `Session` in the context of `HTTP` services. + +## Introduction + +API Documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gsession](https://pkg.go.dev/github.com/gogf/gf/v2/os/gsession) + +You can obtain a `Session` object at any time through `ghttp.Request` since both `Cookie` and `Session` are related to request sessions and thus are part of the `Request` member objects and are publicly accessible. The default expiration time for `Session` in the `GoFrame` framework is `24 hours`. + +The `SessionId` is transmitted by default through `Cookie`, but it also supports transmission via `Header` from the client. The identification name of `SessionId` can be modified through the `SetSessionIdName` of `ghttp.Server`. The operations of `Session` are concurrency-safe, which is why the framework does not operate data directly in a `map` form in its `Session` design. During the `HTTP` request flow, we can obtain the `Session` object through the `ghttp.Request` object and perform the corresponding data operations. + +In addition, the `SessionId` in `ghttp.Server` is generated using the client's `RemoteAddr + Header` request information via the `guid` module, ensuring randomness and uniqueness: [https://github.com/gogf/gf/blob/master/net/ghttp/ghttp_request.go](https://github.com/gogf/gf/blob/master/net/ghttp/ghttp_request.go) + +## `gsession` Module + +The management functionality of `Session` is implemented by the independent `gsession` module and is perfectly integrated into `ghttp.Server`. Since this module is decoupled and independent, it can be applied to more different scenarios, such as `TCP` communication, `gRPC` API services, etc. The `gsession` module has three important objects/APIs: + +1. `gsession.Manager`: Manages `Session` objects, `Storage` persistence storage objects, and expiration time control. +2. `gsession.Session`: A single `Session` management object, used for CRUD operations on `Session` parameters and other data management operations. +3. `gsession.Storage`: This is an API definition used for the persistent storage of `Session` objects, data writing/reading, and survival updates. Developers can implement customized persistent storage features based on this API. For the API definition, see: [https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go](https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go) + +## Storage Implementations + +`gsession` implements and provides developers with four common `Session` storage implementations: + +| Storage | Distributed Support | Persistence Support | Memory Usage | Execution Efficiency | Brief Introduction | +| --- | --- | --- | --- | --- | --- | +| `StorageFile` | No | Yes | Medium | Medium | Based on file storage (default). A more efficient persistent storage method under single-node deployment: [Session - File](Session-File.md) | +| `StorageMemory` | No | No | High | High | Based on pure memory storage. Single-node deployment, highest performance, but cannot be persisted, and is lost on restart: [Session - Memory](Session-Memory.md) | +| `StorageRedis` | Yes | Yes | Medium | Medium | Based on `Redis` storage (`Key-Value`). Remote `Redis` node stores `Session` data, supporting multi-node deployment: [Session - Redis-KeyValue](Session-Redis-KeyValue.md) | +| `StorageRedisHashTable` | Yes | Yes | Low | Low | Based on `Redis` storage (`HashTable`). Remote `Redis` node stores `Session` data, supporting multi-node deployment: [Session - Redis-HashTable](Session-Redis-HashTable.md) | + +Each method has its advantages and disadvantages. For detailed introductions, please refer to the corresponding sections. + +## Initialization of `Session` + +Taking a common HTTP request as an example, the `Session` object in `ghttp.Request` adopts a "**Lazy Initialization**" design approach. By default, there is a `Session` property object in the `Request`, but it is not initialized (an empty object). The initialization is only executed when methods of the `Session` property object are used. This design ensures that the execution performance of requests not utilizing the `Session` feature is maintained, while also ensuring the ease of use of the component. + +## Destruction/Logout of `Session` + +When a user's `Session` is no longer needed, for instance, when a user logs out, the session needs to be hard-deleted from storage. This can be done by calling the `RemoveAll` method. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..6548b70fdcc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/web' +title: 'WEB Service Development' +sidebar_position: 6 +hide_title: true +keywords: [WEB Service Development, GoFrame Framework, WEB Applications, Backend Development, RESTful Services, API Design, Go Language, High-Performance Servers, Application Architecture, Data Processing] +description: "Learn how to develop WEB services using the GoFrame framework, covering all aspects from basic concepts to advanced applications. Through this guide, you will master the techniques and methods for building efficient and reliable WEB applications using the Go language." +--- +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..67a6693d8be --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" @@ -0,0 +1,72 @@ +--- +slug: '/docs/web/paging-ajax' +title: 'Pagination - Ajax Paging' +sidebar_position: 2 +hide_title: true +keywords: [Ajax Pagination, Pagination Management, Javascript Pagination, GoFrame, GoFrame Framework, Golang, Page Rendering, Front-end Development, Dynamic Pagination, Web Development] +description: "Technical details on implementing pagination management using the Ajax method. Unlike traditional pagination, Ajax pagination dynamically retrieves and renders pagination content using Javascript methods for a smoother user experience. The sample code demonstrates how to integrate Ajax pagination functionality in the GoFrame framework, providing a practical backend pagination solution." +--- + +The difference between `Ajax` pagination and other pagination methods is that the pagination links are implemented using `Javascript` methods. This `Javascript` method is the pagination method, with fixed parameters for the pagination `URL` address corresponding to the pagination. This `Javascript` method retrieves the pagination content corresponding to the `URL` link via `Ajax` and renders it onto the page. + +A complete example is as follows: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/ajax", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + page.AjaxActionName = "DoAjax" + buffer, _ := gview.ParseContent(` + + + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this example, we define a `DoAjax(url)` method to perform pagination operations. For demonstration purposes, its logic is quite simple, loading the content of the specified pagination page and replacing the current page's pagination content. + +``` +function DoAjax(url) { + $.get(url, function(data,status) { + $("body").html(data); + }); +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" new file mode 100644 index 00000000000..5916f4c2811 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" @@ -0,0 +1,56 @@ +--- +slug: '/docs/web/paging-template' +title: 'Pagination - URL Template' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, gpage, pagination management, URL template, custom URL, built-in variables, page rendering, code examples, template replacement] +description: "Use the gpage of the GoFrame framework for pagination management and realize dynamic page rendering by replacing page number content with built-in variables through the custom URL template function. The article provides detailed code examples demonstrating how to achieve personalized pagination URL configuration by setting the UrlTemplate property, offering developers a flexible and efficient solution." +--- + +`gpage` supports custom `URL` templates, where the `{.page}` built-in variable can be used to replace the content of the page number. Let's look at a simple example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/template/{page}.html", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + page.UrlTemplate = "/order/list/{.page}.html" + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +In the code, we can set the `URL` template using the `UrlTemplate` property. After executing, the result is as follows: + +![](/markdown/a67f2f6285ed959812f70fd066e7453a.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..0ec6b376b96 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/web/paging-dynamic' +title: 'Pagination - Dynamic Paging' +sidebar_position: 0 +hide_title: true +keywords: [dynamic paging, GoFrame, paging management, GET parameters, QueryString, paging example, ghttp, gview, GoFrame framework, web application] +description: "This document introduces how to use dynamic paging in the GoFrame framework by passing paging configuration through GET parameters, with the default parameter name being 'page'. Through the provided sample code, users can learn how to integrate four predefined paging styles on a webpage and implement the paging management process." +--- + +Dynamic paging passes paging parameters through `GET` parameters (via `QueryString`), with the default paging parameter name being `page`. + +The example is as follows: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/demo", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +In this example, we demonstrate four predefined paging styles and pass paging parameters via `GET`. After execution, the output is shown as below: + +![](/markdown/4e021b3d29b1d1789b1cb03959833c33.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..2cb632dcad6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" @@ -0,0 +1,125 @@ +--- +slug: '/docs/web/paging-customization' +title: 'Pagination - Custom Paging' +sidebar_position: 4 +hide_title: true +keywords: [Custom Paging, Paging Management, GoFrame, GoFrame Framework, Tag Replacement, Paging Style, Web Development, Regex Matching, Go Language, Framework Usage] +description: "Implement custom paging styles and tags in the GoFrame framework. Developers can achieve higher flexibility and personalization by replacing or organizing paging content through regex matching of the properties and methods exposed by the paging object." +--- + +As the predefined styles of the paging object are relatively limited, sometimes we want to customize the style or tags of the paging. Since all properties and methods of the paging object are public, this provides developers with a high degree of flexibility in customizing paging styles. Developers can customize paging content in the following ways: + +1. Use regex matching to replace the output content for customization. +2. Organize paging content based on the properties and methods exposed by the paging object for customization. + +## Custom Tag Replacement + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gpage" +) + +// wrapContent wraps each of the page tag with html li and ul. +func wrapContent(page *gpage.Page) string { + content := page.GetContent(4) + content = gstr.ReplaceByMap(content, map[string]string{ + "": "/span>", + "": "/a>", + }) + return "
    " + content + "
" +} + +func main() { + s := g.Server() + s.BindHandler("/page/custom1/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + content := wrapContent(page) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page}}
+ + + `, g.Map{ + "page": content, + }) + r.Response.Write(buffer) + }) + s.SetPort(10000) + s.Run() +} +``` + +After execution, the page output is: + +![](/markdown/e3f0fff04f626c752f342e6f37ff88fa.png) + +## Define Paging Tag Names + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/util/gpage" +) + +// pageContent customizes the page tag name. +func pageContent(page *gpage.Page) string { + page.NextPageTag = "NextPage" + page.PrevPageTag = "PrevPage" + page.FirstPageTag = "HomePage" + page.LastPageTag = "LastPage" + pageStr := page.FirstPage() + pageStr += page.PrevPage() + pageStr += page.PageBar() + pageStr += page.NextPage() + pageStr += page.LastPage() + return pageStr +} + +func main() { + s := g.Server() + s.BindHandler("/page/custom2/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page}}
+ + + `, g.Map{ + "page": pageContent(page), + }) + r.Response.Write(buffer) + }) + s.SetPort(10000) + s.Run() +} +``` + +After execution, the page output is: + +![](/markdown/adca49269555fe04d83b277c38c656ef.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..f83b22d8b7c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" @@ -0,0 +1,104 @@ +--- +slug: '/docs/web/paging-static' +title: 'Pagination - Static Paging' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Static Paging, Paging Management, Route Parameters, Fuzzy Matching, Named Matching, Field Matching, Paging Object, Paging Parameters] +description: "Implement static paging management in the GoFrame framework. Static paging is achieved by using route parameters, which has a high level of coupling. This example illustrates how to use fuzzy matching routes, named matching routes, and field matching routes in the GoFrame framework to achieve the paging function, allowing the paging object to accept paging parameters from the route, thereby achieving split page display." +--- + +Static paging refers to the use of route parameters for page pagination, where the paging object is highly coupled with the route definition of the `Server`. The route definition requires a route parameter with the name `page`, which can use fuzzy matching route `*page`, named matching route `:page`, or field matching route `{page}`. + +### Example 1, Using Fuzzy Matching Route + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/static/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, we manually visit the page [http://127.0.0.1:8199/page/static/6](http://127.0.0.1:8199/page/static/6), and the result is as follows: + +![](/markdown/e1f6cd68809f5d3b2ceffcd1fb09aa3e.png) + +### Example 2, Using Field Matching Route + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/:obj/*action/{page}.html", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
{{.page1}}
+
{{.page2}}
+
{{.page3}}
+
{{.page4}}
+ + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +The routing rule in this example is more flexible, using the `{page}` field matching rule to obtain current page number information. After execution, we visit any URL according to the routing rule, such as: [http://127.0.0.1:8199/order/list/6.html](http://127.0.0.1:8199/order/list/6.html), and the result is as shown below: + +![](/markdown/bb96317821692384eb3dd794f3d9170e.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..33e1a0b384c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" @@ -0,0 +1,86 @@ +--- +slug: '/docs/web/paging' +title: 'Pagination' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Pagination Management, gpage Module, Dynamic Pagination, Static Pagination, HTML Pagination, MVC Development, Ajax Pagination, Pagination Style] +description: "Pagination management in the GoFrame framework is mainly implemented through the gpage module. The gpage module supports dynamic and static pagination and provides developers with a flexible way to customize pagination styles. This article explains in detail the creation and use of pagination objects, supporting easy retrieval of pagination objects in web services. It also covers the use of predefined pagination styles and the implementation method of Ajax pagination to facilitate rapid integration and use by developers." +--- + +## Introduction + +Pagination management is implemented by the `gpage` module. `gpage` offers powerful dynamic and static pagination functionalities and provides developers with high flexibility in customizing pagination styles. + +:::tip +The `gpage` module is primarily used for generating HTML code for pagination, commonly used in `MVC` development scenarios. +::: + +**Usage:** + +```go +import "github.com/gogf/gf/v2/util/gpage" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gpage](https://pkg.go.dev/github.com/gogf/gf/v2/util/gpage) + +**Pagination Object:** + +```go +// Page is the pagination implementer. +// All the attributes are public, you can change them when necessary. +type Page struct { + TotalSize int // Total size. + TotalPage int // Total page, which is automatically calculated. + CurrentPage int // Current page number >= 1. + UrlTemplate string // Custom url template for page url producing. + LinkStyle string // CSS style name for HTML link tag . + SpanStyle string // CSS style name for HTML span tag , which is used for first, current and last page tag. + SelectStyle string // CSS style name for HTML select tag +
+ + + + + + +``` + +Note that the server connection address here is: `ws://127.0.0.1:8199/ws`. + +The client's functionality is quite simple, mainly implementing these features: + +- Maintaining the connection status with the server's `websocket` and information display; +- Inputting content in the API and sending information to the `websocket` server; +- Echoing the returned information from the `websocket` on the API; + +## WebSocket Server + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +var ctx = gctx.New() + +func main() { + s := g.Server() + s.BindHandler("/ws", func(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + glog.Error(ctx, err) + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } + }) + s.SetServerRoot(gfile.MainPkgPath()) + s.SetPort(8199) + s.Run() +} + +``` + +As you can see, the server code is quite simple. Here are a few points worth noting: + +1. **WebSocket Method** + +The route registration method for a `websocket` server is the same as that for a regular `http` callback function. However, in handling the API, we need to convert the request into a `websocket` operation using the `ghttp.Request.WebSocket` method (using the pointer object `r.WebSocket()`) and return a `WebSocket object`, which is used for subsequent `websocket` communication operations. Of course, if the client's request is not a `websocket` operation, the conversion will fail. The method will return an error message, so please note to check the `error` return value when using this method. + +1. **ReadMessage & WriteMessage** + +Reading and writing messages correspond to the data reading and writing operations of `websocket` (`ReadMessage & WriteMessage`). It's important to note that both of these methods have a `msgType` variable that indicates the type of data to be read and written. The two common data types are: string data or binary data. During usage, since both sides of the API will agree on a unified data format, the `msgType` for reading and writing is almost always the same. Therefore, in this example, when returning a message, the data type parameter directly uses the read `msgType`. + +## HTTPS WebSocket + +If `HTTPS` WebSocket support is needed, all that is required is for the `WebServer` to support `HTTPS`, and the `WebSocket` access address needs to use the `wss://` protocol. In the client `HTML5` page above, the `WebSocket` access address needs to be changed to: `wss://127.0.0.1:8199/wss`. Server example code: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +var ctx = gctx.New() + +func main() { + s := g.Server() + s.BindHandler("/wss", func(r *ghttp.Request) { + ws, err := r.WebSocket() + if err != nil { + glog.Error(ctx, err) + r.Exit() + } + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + return + } + if err = ws.WriteMessage(msgType, msg); err != nil { + return + } + } + }) + s.SetServerRoot(gfile.MainPkgPath()) + s.EnableHTTPS("../../https/server.crt", "../../https/server.key") + s.SetPort(8199) + s.Run() +} +``` + +## Example Result Display + +First, execute the example code `main.go`, then visit the page [http://127.0.0.1:8199/](http://127.0.0.1:8199/). Enter the request content and submit it, then close the program on the server side. You will see that the page echoes the submitted content and instantly displays the change in `websocket` connection status. When the server is closed, the client will instantly print out the closure message. + +![](/markdown/670be5bdaae78e5cd183fade39dc20e7.png) + +## Websocket Security Validation + +The `websocket` module in the `GoFrame` framework does not perform same-origin checks (`origin`), which means that websockets under these conditions allow complete cross-origin access. + +Security validation needs to be handled at the business layer, which mainly includes the following aspects: + +1. Validation of `origin`: Before executing `r.WebSocket()`, the business layer needs to validate `origin` for the same-origin request, or perform custom checks on the request (if request parameters are submitted). If the validation fails, call `r.Exit()` to terminate the request. +2. Validation of `websocket` communication data: Data communication often involves some custom data structures, and authentication logic should be added to these communication data; + +## WebSocket Client + +``` + package main + +import ( + "crypto/tls" + "fmt" + "net/http" + "time" + + "github.com/gogf/gf/v2/net/gclient" + "github.com/gorilla/websocket" +) + +func main() { + client := gclient.NewWebSocket() + client.HandshakeTimeout = time.Second // Set timeout + client.Proxy = http.ProxyFromEnvironment // Set proxy + client.TLSClientConfig = &tls.Config{} // Set TLS configuration + + conn, _, err := client.Dial("ws://127.0.0.1:8199/ws", nil) + if err != nil { + panic(err) + } + defer conn.Close() + + err = conn.WriteMessage(websocket.TextMessage, []byte("hello word")) + if err != nil { + panic(err) + } + + mt, data, err := conn.ReadMessage() + if err != nil { + panic(err) + } + fmt.Println(mt, string(data)) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..5228f686c69 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" @@ -0,0 +1,242 @@ +--- +slug: '/docs/web/senior-hot-reload' +title: 'Graceful Restart' +sidebar_position: 3 +hide_title: true +keywords: [Graceful Restart, Hot Reload, GoFrame, WebServer, Service Management, Framework Support, Command Line Terminal, Feature Activation, Multi-Service Management, HTTPS Support] +description: "Enable and manage graceful restart features while using the GoFrame framework. This feature allows the WebServer to update or restart without interrupting existing requests, improving service stability and user experience. It supports graceful restart on *nix systems and complete restart on Windows systems, and provides management methods via web and command line. It also demonstrates specific usage examples, including basic usage, HTTPS support, and smooth management in multi-service and multi-port scenarios." +--- + +`Smooth restart` (`hot reload`) means the `WebServer` can restart without interrupting existing request execution. This feature is especially useful during different project version releases. For instance, when needing to release two versions: `A` and `B`, during the execution of `A`, we can directly overwrite `A`'s program with `B` and use the graceful restart feature (using `Web` or `command line`) to seamlessly transition requests to the new version of the service. + +The `GoFrame` framework supports very convenient `web management features`, which means we can directly manage `Server` restart/shutdown operations through a web page/API. Additionally, the framework also supports `Server` restart/shutdown operations through `command line terminal commands` (limited to `*nix` systems). + +## Feature Activation + +By default, the graceful restart feature is disabled, and it can be turned on via the `graceful` configuration option. Please refer to the `WebServer` configuration management section for details: [Configuration - File Template](../服务配置/服务配置-配置文件模板.md) +:::tip +Currently, the graceful restart feature requires randomly opening a TCP listening service on a local port for communication and state information exchange between new and old processes. +::: +## Notes + +- This feature is limited to `*nix` systems (`Linux/Unix/FreeBSD`, etc.), and on `Windows`, it only supports complete restart (requests cannot transition smoothly). +- Please do not use `IDE run` (e.g., `Goland`) or the `go run` command to run processes when testing the graceful restart feature, as these methods create a parent process to manage the running `Go` process, which can cause failures in state exchange between child and parent processes during a graceful restart. +- The `SetGraceful` configuration method in the subsequent examples is newly added after version `v2.7.4`. For versions below `v2.7.4`, please use the configuration management method to enable the graceful restart feature. + +## Management Methods + +First, let's look at the management methods involved in the WebServer: + +```go + + func (s *Server) Restart + (newExeFilePath... + string + ) error +func (s *Server) Shutdown + ( + ) error + +func (s *Server) EnableAdmin + (pattern ... + string + ) + + +``` + +`Restart` is used to restart the service (graceful restart on `*nix` systems, complete restart on `Windows`), `Shutdown` is used to close the service, and `EnableAdmin` is used to register the management page with the specified routing rules. The default address is `/debug/admin` (we can specify a private management address or use middleware to authenticate the page). + +The following details two of these methods. + +### Restart + +The `Restart` parameter can specify the custom executable file path for restart (`newExeFilePath`). If not provided, it defaults to the original executable file path. Especially under the Windows system, when the executable file is in use, it cannot be replaced or updated (new version file replacing the old version file). By specifying a custom executable file path, when the `Server` restarts, it will execute the new version of the executable file instead of the old one, simplifying the version update process on some systems. + +### EnableAdmin + +- Firstly, this method provides users with a convenient page and API for managing the `Server`, which is very convenient for managing a single `Server`. By directly accessing and clicking the corresponding links on the management page, operations can be performed. It is important to note that, due to the management features, if used in production environments, it is recommended to customize the management address to a private address. +- Additionally, the `restart` API provided by `EnableAdmin` also supports custom executable file paths, directly passing the `newExeFilePath` variable to the restart API through GET parameters, e.g., [http://127.0.0.1/debug/admin/restart?newExeFilePath=xxxxxxx](http://127.0.0.1/debug/admin/restart?newExeFilePath=xxxxxxx) +- Furthermore, in most cases, `Server` often has more than 1 node, so in most service management operations, such as restart operations, it is not directly accessing the `admin` page of each `Server` to manually execute the restart operation. Instead, it fully utilizes the functional APIs provided by the `admin` page to achieve unified `Server` management control through API control. + +### Example 1: Basic Usage + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writeln("Hello!") + }) + s.BindHandler("/pid", func(r *ghttp.Request) { + r.Response.Writeln(gproc.Pid()) + }) + s.BindHandler("/sleep", func(r *ghttp.Request) { + r.Response.Writeln(gproc.Pid()) + time.Sleep(10 * time.Second) + r.Response.Writeln(gproc.Pid()) + }) + s.SetGraceful(true) + s.EnableAdmin() + s.SetPort(8199) + s.Run() +} +``` + +We test graceful restart through the following steps: + +1. Visit [http://127.0.0.1:8199/pid](http://127.0.0.1:8199/pid) to view the current process pid + +![](/markdown/7b28a1e44a96f3410e3e9a6d53a59523.png) + +2. Visit [http://127.0.0.1:8199/sleep](http://127.0.0.1:8199/sleep), this page will execute for 10 seconds, used for testing whether the page request execution will be interrupted during a restart + +![](/markdown/1f7d22a5a51104abc7ad80b2181e3fc6.png) + +3. Visit [http://127.0.0.1:8199/debug/admin](http://127.0.0.1:8199/debug/admin), which is a WebServer management page registered by default after `s.EnableAdmin` + +![](/markdown/8e54c1d520f7856a88e951e3391e1f3b.png) + +4. Then we click the `restart` management link, and the WebServer will immediately smoothly restart (on `*nix` systems) + +![](/markdown/67eeff6f5cd3e726e24dfc17a5128db4.png) + +Meanwhile, the terminal will output the following information: + +```shell + 2018-05-18 11:02:04.812 11511: http server started listening on [:8199] + 2018-05-18 11:02:09.172 11511: server reloading + 2018-05-18 11:02:09.172 11511: all servers shutdown + 2018-05-18 11:02:09.176 16358: http server restarted listening on [:8199] + + +``` + +5. We can observe that during the entire operation, the execution of the `sleep` page was not interrupted, and after waiting a few seconds, when the `sleep` execution is finished, the page output is: + +![](/markdown/e21dfd1e4d9c4cd0a8e4ce042dc5dcaf.png) + +6. It can be found that the process pid output on the `sleep` page is different from before, indicating that the request's execution was smoothly taken over by the new process, and the old service process was subsequently destroyed; + +### Example 2: HTTPS Support + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("Hello!") + }) + s.SetGraceful(true) + s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") + s.EnableAdmin() + s.SetPort(8200) + s.Run() +} +``` + +The graceful restart feature of the `GoFrame` framework is also very user-friendly and simple for HTTPS support. The operation steps are as follows: + +1. Visit [https://127.0.0.1:8200/debug/admin/restart](https://127.0.0.1:8200/debug/admin/restart) to smoothly restart the HTTPS service; +2. Visit [https://127.0.0.1:8200/debug/admin/shutdown](https://127.0.0.1:8200/debug/admin/shutdown) to smoothly shut down the WebServer service; + +The following output information can be seen in the command line terminal: + +```shell + 2018-05-18 11:13:05.554 17278: https server started listening on [:8200] +2018-05-18 11:13:21.270 17278: server reloading +2018-05-18 11:13:21.270 17278: all servers shutdown +2018-05-18 11:13:21.278 17319: https server reloaded listening on [:8200] +2018-05-18 11:13:34.895 17319: server shutting down +2018-05-18 11:13:34.895 17269: all servers shutdown + + +``` + +### Example 3: Multi-Service and Multi-Port + +The graceful restart feature of the `GoFrame` framework is quite powerful and stable, supporting not only single service and single port listening management but also complex scenarios involving multi-service and multi-port listening management. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + s1 := g.Server("s1") + s1.SetGraceful(true) + s1.EnableAdmin() + s1.SetPort(8100, 8200) + s1.Start() + + s2 := g.Server("s2") + s2.SetGraceful(true) + s2.EnableAdmin() + s2.SetPort(8300, 8400) + s2.Start() + + g.Wait() +} +``` + +The example above demonstrates two WebServers, `s1` and `s2`, respectively listening on `8100`, `8200`, and `8300`, `8400`. We then visit [http://127.0.0.1:8100/debug/admin/reload](http://127.0.0.1:8100/debug/admin/reload) to smoothly restart the service, and then `http://127.0.0.1:8100/debug/admin/shutdown](http://127.0.0.1:8100/debug/admin/shutdown) to smoothly shut down the service. The final information printed in the terminal is as follows: + +```html +2018-05-18 11:26:54.729 18111: http server started listening on [:8400] +2018-05-18 11:26:54.729 18111: http server started listening on [:8100] +2018-05-18 11:26:54.729 18111: http server started listening on [:8300] +2018-05-18 11:26:54.729 18111: http server started listening on [:8200] +2018-05-18 11:27:08.203 18111: server reloading +2018-05-18 11:27:08.203 18111: all servers shutdown +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8300] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8400] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8200] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8100] +2018-05-18 11:27:19.379 18124: server shutting down +2018-05-18 11:27:19.380 18102: all servers shutdown +``` + +## Command Line Management + +Apart from providing `Web` management capabilities, the `GoFrame` framework also supports management via command line, as the command line uses `signals` for management. + +### Restart Service + +Use the `SIGUSR1` signal, usage: + +```shell + + kill -SIGUSR1 process ID + +``` + +### Shut Down Service + +Use any one of the `SIGINT/SIGQUIT/SIGKILL/SIGHUP/SIGTERM` signals, usage: + +```shell + + kill -SIGTERM process ID + +``` + +## Other Management Methods + +Since the `WebServer` of the `GoFrame` framework adopts a singleton design, the corresponding `Server` singleton object can be obtained anywhere through `g.Server(name)`, and then managed using the `Restart` and `Shutdown` methods. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..79c9fe54c06 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/web/senior-logging' +title: 'Logging' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Server Log Management, access log, error log, log configuration, log format, error log, request log, custom log handling, glog] +description: "Using the GoFrame framework for service log management, including the configuration and usage of access logs and error logs. It explains in detail the log configuration objects and attributes, such as Logger, LogPath, ErrorStack, etc., and provides detailed explanations of log formats and custom log handling methods. Additionally, it covers how to set up logs through configuration files and code methods, and provides examples of log formats and error log recording." +--- + +The `GoFrame` framework provides comprehensive `Server` log management features, including `access log` and `error log`. It is recommended to use configuration files for unified configuration management. + +## Log Configuration + +### Configuration Object + +Please refer to the API documentation: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig) + +### Configuration Attributes + +The log-related configuration attributes are as follows: + +```go +Logger *glog.Logger // Logger for server. +LogPath string // Directory for storing logging files. +LogStdout bool // Printing logging content to stdout. +ErrorStack bool // Logging stack information when error. +ErrorLogEnabled bool // Enable error logging files. +ErrorLogPattern string // Error log file pattern like: error-{Ymd}.log +AccessLogEnabled bool // Enable access logging files. +AccessLogPattern string // Error log file pattern like: access-{Ymd}.log +``` + +Brief description: + +1. By default, logs are not output to files and are printed directly to the terminal. By default, the terminal output for the `access` log is disabled, while the `error` log is enabled. +2. All options can be set using the `Server.Set*` methods, and most options can be retrieved using the `Server.Get*` methods. +3. `Logger` is a custom log management object, and developers can also pass a complete log management object to ignore other log option configurations. +4. The `LogPath` attribute is used to set the log directory, and logs will be output to log files only when a log directory is set. +5. `ErrorLogPattern` and `AccessLogPattern` are used to configure the log file name format, defaulting to `error-{Ymd}.log` and `access-{Ymd}.log`, such as `error-20191212.log`, `access-20191212.log`. +6. For descriptions of other configuration options, please refer to the comments and API documentation. + +### Configuration File + +It is officially recommended to use configuration files to manage service and log configurations. Here is an example of a log configuration (in `yaml` format): + +```yaml +server: + LogPath: "/var/log/gf-demos/server" + LogStdout: false + ErrorStack: true + ErrorLogEnabled: true + ErrorLogPattern: "error.{Ymd}.log" + AccessLogEnabled: true + AccessLogPattern: "access.{Ymd}.log" +``` + +When the `Server` starts, it will automatically read the `server` node configuration from the default configuration file `config.yaml`. + +### Configuration Method + +Log configuration can also be done through the `Set*` methods of the `Server` object. Refer to the [Configuration](../服务配置/服务配置.md) section. + +## Log Format + +The configuration file method is simpler, so it will not be demonstrated here. The following example uses the configuration method to configure the `Server`. + +### Request Log + +Request log: + +```html +2018-04-20 18:11:57.344 200 "GET http 127.0.0.1:8199 /log/access HTTP/1.1" 0.120, 127.0.0.1, "", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36" +``` + +Log format: + +```html +Request Time (to the millisecond) HTTP Status Code "Request Method Request Prefix Request Address Request Protocol" Execution Time (seconds) Client IP "Referrer URL", "UserAgent" +``` + +Where `Request Prefix` can be `http` or `https`, and `Request Protocol` is typically `HTTP/1.0` or `HTTP/1.1`. +:::warning +Note that the `Execution Time` recorded in the logs is in `seconds`, and in most cases, the time you see is almost `0.xxx` seconds, meaning execution times are in milliseconds, less than 1 second. +::: + +### Error Log + +Error log: + +```html +2019-12-20 20:10:56.484 [ERRO] 500, "GET http 127.0.0.1:8199 /log/error HTTP/1.1" 0.210, 127.0.0.1, "", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" +Stack: +1. OMG + 1). main.main.func1 + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/net/ghttp/server/log/log_error.go:10 +``` + +Error information will print the corresponding stack information where the error occurred (excluding internal framework call information) to facilitate error location and developer analysis. +:::tip +Any `panic` errors generated by the `Server` will be automatically captured in the error logs. Therefore, for business programs, regardless of whether it's in the controller, business encapsulation layer, or data model, if an error occurs and you want to immediately exit the business request handling, just `panic`. +::: + +## Custom Log Handling + +Developers can customize the handling of `Server` request logs in two ways: + +1. Custom `*glog.Logger` objects can be passed through log configuration options. +2. Interception can be handled uniformly through middleware, as described in the [Middleware - Intro](../路由管理/路由管理-中间件拦截器/中间件拦截器-基本介绍.md) section. + +## `Server` Logs and Business Logs + +This is a `FAQ`. + +It is important to note that the logs mentioned here are the `Server` logs, similar to a series of logs from `Web Server` services like `nginx`, `apache`, `tomcat`, etc. Only the `Server` is allowed to output content, and developers cannot write log content into the `Server` log files, and the log types and formats are completely fixed. + +The `GoFrame` framework also provides a log module, implemented by the `glog` log component. Logs printed by developers through the `glog` component are considered business logs, and the program's business code can decide what content to output, where to output it, and what the output format should be like. The commonly used `g.Log()` method is used to output business logs, and this method supports automatically reading the `logger` configuration items from the configuration file. Please refer to the [Logging](../../核心组件/日志组件/日志组件.md) section for more details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..bae5fb31b8c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" @@ -0,0 +1,100 @@ +--- +slug: '/docs/web/senior-status-handler' +title: 'Status Code Handling' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame framework, status code handling, custom status codes, WebServer, error page, page redirect, status code callback, r.RedirectTo, BindStatusHandler] +description: "Implement custom status code handling in the GoFrame framework. By using the BindStatusHandler method, developers can customize handling for specified status codes such as 404, 403, 500 for a WebServer, including displaying custom error messages or page content, and implementing error page redirection. Sample code demonstrates basic settings and batch status code handling." +--- + +We can customize the handling of status codes specified by WebServer, for example, for common errors like `404/403/500`, we can display custom error messages, page content, or redirect to a specific page. + +The related methods are as follows: + +```go +func (s *Server) BindStatusHandler(status int, handler HandlerFunc) +func (s *Server) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) + +func (d *Domain) BindStatusHandler(status int, handler HandlerFunc) +func (d *Domain) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) +``` + +As we can see, we can use `BindStatusHandler` or `BindStatusHandlerByMap` to implement custom callback handling for specified status codes, and this feature also supports binding to specific domain names. Let's look at a few simple examples. + +## Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("halo world!") + }) + s.BindStatusHandler(404, func(r *ghttp.Request){ + r.Response.Writeln("This is customized 404 page") + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, when we visit an unbound route page, such as [http://127.0.0.1:8199/test](http://127.0.0.1:8199/test), we can see the page shows our expected return result: `This is customized 404 page`. + +Moreover, the common way of handling web page request error status codes is to guide users to a specific error page. Therefore, in the status code callback handling function, we can use the `r.RedirectTo` method to perform page redirection, as shown in the following example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/status/:status", func(r *ghttp.Request) { + r.Response.Write("woops, status ", r.Get("status"), " found") + }) + s.BindStatusHandler(404, func(r *ghttp.Request){ + r.Response.RedirectTo("/status/404") + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, when we manually access a non-existent page through the browser, such as [http://127.0.0.1:8199/test](http://127.0.0.1:8199/test), we can see the page is redirected to [http://127.0.0.1:8199/status/404](http://127.0.0.1:8199/status/404), and the page returns the content: `woops, status 404 found` + +## Batch Settings + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindStatusHandlerByMap(map[int]ghttp.HandlerFunc { + 403 : func(r *ghttp.Request){r.Response.Writeln("403")}, + 404 : func(r *ghttp.Request){r.Response.Writeln("404")}, + 500 : func(r *ghttp.Request){r.Response.Writeln("500")}, + }) + s.SetPort(8199) + s.Run() +} +``` + +As we can see, we can use the `BindStatusHandlerByMap` method for batch settings of custom status codes. After the example program is executed, when the service API returns status codes `403/404/500`, the API will return the corresponding status code number. + +## Precautions + +If content output is involved in custom status code handling methods, it's often necessary to use `r.Response.ClearBuffer()` to clear the original buffered output content. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..cc130570c14 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" @@ -0,0 +1,177 @@ +--- +slug: '/docs/web/senior-static-server' +title: 'Static File Service' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Static File Service, Static File Configuration, File Service Enablement, Static File Directory, Static File Mapping, URI Rewrite, Cross-Origin Support, Static Directory Priority, Directory Listing] +description: "Learn how to configure and use static file services in the GoFrame framework, including setting up static file directories, enabling static file service conditions, mapping URIs to static directories, customizing URI rewrite rules, and cross-origin configuration examples, to help developers effectively manage and optimize static resource access in their projects." +--- + +## Static File Service Configuration + +By default, the `GoFrame Server` disables the static file service feature. If the developer configures a **static file directory**, the static file service will be automatically enabled. + +Common configuration methods involving the static file service are as follows: + +```go +// Set HTTP server parameter - ServerRoot +func (s *Server) SetServerRoot(root string) + +// Add static file search directories; the absolute path of the directory must be specified +func (s *Server) AddSearchPath(path string) + +// Set HTTP server parameter - IndexFiles, default displayed files, such as: index.html, index.htm +func (s *Server) SetIndexFiles(index []string) + +// Allow or disallow displaying the directory listing of accessible directories +func (s *Server) SetIndexFolder(enabled bool) + +// Add mapping of URI to static directory +func (s *Server) AddStaticPath(prefix string, path string) + +// Main switch for static file service: enable/disable static file service +func (s *Server) SetFileServerEnabled(enabled bool) + +// Set URI rewrite rule +func (s *Server) SetRewrite(uri string, rewrite string) + +// Set URI rewrite rules (batch) +func (s *Server) SetRewriteMap(rewrites map[string]string) +``` + +Brief introduction: + +1. `IndexFiles` is the list of default file names searched when accessing directories (searched in the order of the slice). If the searched file exists, the file content is returned; otherwise, the directory listing is displayed (when `SetIndexFolder` is `true`). The default `IndexFiles` are: `index.html, index.htm`. +2. `SetIndexFolder` is set to allow displaying the file list in the directory when users access a file directory and no `IndexFiles` are found in the directory. It is disabled by default. +3. `SetServerRoot` sets the default static file directory for services. This directory is automatically added as the first search path in `SearchPath`. +4. `AddSearchPath` adds static file search directories, which can be multiple, and priority searches are performed in the order of added file directories. +5. `AddStaticPath` adds mapping relationships between `URI` and directory paths, allowing customization of static file directory access URI rules. +6. `SetRewrite`/`SetRewriteMap` sets rewrite rules (similar to `nginx` `rewrite`), which technically apply not only to static file services but also support dynamic route registration `rewrite`. + +:::tip +When setting the directory path for the static file service, you can use either absolute or relative paths. For example, to set the current running directory to provide static file services, use `SetServerRoot(".")`. + +Developers can set multiple file directories to provide static file services and prioritize directories and URIs. However, if the static service is disabled via `SetFileServerEnabled`, all static file/directory access will become invalid. +::: + +## Example 1, Basic Usage + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// Basic usage of static file server +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.SetPort(8199) + s.Run() +} +``` + +## Example 2, Static Directory Mapping + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// Static file server supports custom static directory mapping +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.SetPort(8199) + s.Run() +} +``` + +## Example 3, Static Directory Mapping, Priority Control + +The priority of static directory mapping is controlled according to the precision of the bound `URI`. The more precise the bound URI (depth-first matching), the higher the priority. + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// Static file server supports custom static directory mapping +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.AddStaticPath("/my-doc/test", "/Users/john/Temp") + s.SetPort(8199) + s.Run() +} +``` + +Here, accessing `/my-doc/test` has a higher priority than `/my-doc`. Therefore, if there is a `test` directory under `/Users/john/Documents` (conflicting with the custom `/my-doc/test`), it cannot be accessed. + +## Example 4, URI Rewrite + +The static file service of the `GoFrame` framework supports rewriting any `URI` to replace it with a designated `URI`, using `SetRewrite/SetRewriteMap` methods. + +Example: In the `/Users/john/Temp` directory, there are only two files `test1.html` and `test2.html`. + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +func main() { + s := g.Server() + s.SetServerRoot("/Users/john/Temp") + s.SetRewrite("/test.html", "/test1.html") + s.SetRewriteMap(g.MapStrStr{ + "/my-test1": "/test1.html", + "/my-test2": "/test2.html", + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, + +1. When accessing `/test.html`, it is eventually rewritten to `test1.html`, and the content of this file is returned. +2. When accessing `/my-test1`, it is eventually rewritten to `test1.html`, and the content of this file is returned. +3. When accessing `/my-test2`, it is eventually rewritten to `test2.html`, and the content of this file is returned. + +## Example 5, Cross-Origin + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/glog" +) + +func beforeServeHook(r *ghttp.Request) { + glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI) + r.Response.CORSDefault() +} + +// Use hooks to inject cross-origin configuration +func main() { + s := g.Server() + s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook) + s.SetServerRoot(".") + s.SetFileServerEnabled(true) + s.SetAddr(":8080") + s.Run() +} +``` + +``` + +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..858fa246d05 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/web/senior' +title: 'Advanced Features' +sidebar_position: 11 +hide_title: true +keywords: [Advanced Features, GoFrame, GoFrame Framework, Web Development, Backend Framework, Programming Tips, API Design, Performance Optimization, Modularization, Code Reuse] +description: "Advanced features in the GoFrame framework, suitable for developers with some programming experience. By learning these features, you will master more flexible API design techniques, improve code performance and maintainability, and achieve best practices in modularization and code reuse." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" new file mode 100644 index 00000000000..243e54dd230 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/other' +title: 'Other Documents' +hide_title: true +sidebar_position: 999 +keywords: [Other Materials, GoFrame, GoFrame Framework, Documentation, Materials, Development, Guide, Programming, Technology, Reference] +description: "Various other materials related to the GoFrame framework, providing developers with comprehensive usage guides and technical references to help them deeply understand and efficiently use the GoFrame framework for programming development." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" new file mode 100644 index 00000000000..c93fde80fff --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/install-go/go-module' +title: 'Go Module' +sidebar_position: 1 +hide_title: true +keywords: [Go Module,GoFrame,package management tool,dependency management,go.mod,Goland IDE,vgo,package,GOPROXY,go get] +description: "The usage of the Go Module package management tool, covering how to perform dependency management through Goland IDE and the command line, and providing practical guidance on setting up the go.mod file and using a proxy to download the GoFrame framework. By enabling the Go Module feature and choosing the appropriate proxy address, you can efficiently manage project package dependencies, thereby improving development efficiency." +--- + +`Go Module` is an official package management tool provided since Go version `1.11.1`, used for package management and dependencies in Go projects, similar to PHP's `composer` and Node.js's `npm`. This chapter introduces some commonly used commands/settings for `Go Module`. For more detailed information, please refer to the official documentation: [https://github.com/golang/go/wiki/Modules](https://github.com/golang/go/wiki/Modules) + +## About `go.mod` + +`go.mod` is the dependency description file for a Go project. The file primarily describes two things: + +1. What is the current project name (`module`). Each project should set a name, and packages in the current project can use this name to call each other. +2. The names of third-party packages that the current project depends on. When the project runs, it will automatically analyze the code dependencies in the project, generate a `go.sum` dependency analysis result, and then the Go compiler will download these third-party packages before compiling and running. + +We've made some changes to the previous `hello world` project, adding a `go.mod` file (which can also be generated by using the `go mod init project_name` command in the project's root directory) with the following content: + +```go +module my-hello +``` + +Here, `my-hello` is the name of the current project and can be set arbitrarily. + +Thus, the project's `module` initialization is completed simply. + +Generally, the `go.sum` dependency analysis file should be added to version control and committed along with the `go.mod` file. + +## Using `go.mod` + +Using `go.mod` means managing project dependencies with `go.mod`. We have two **ways** to use `go.mod`: `IDE-vgo` and `command line`. Below, we demonstrate how to use these two methods to manage dependencies with the `GoFrame` framework. + +> To have `Goland` IDE support `go.mod`, vgo support (including code dependency detection) must be enabled. The difference between these two methods lies only in the way of downloading dependency packages. + +### Using Goland IDE vgo (recommended) + +`vgo` is a package management tool based on the `Go Module` specification, similar to the official `go mod` command tool. + +1. Set `Goland` to enable `vgo` + +![](/markdown/f3f9552ca0703fb4e88ae2958b58815c.png) + +If your local environment already has `VPN` functionality, you can ignore setting the `proxy`. + +Enter the proxy address to download dependency packages, or choose `direct` to not use a proxy. Available reverse proxy addresses include: + +- `https://goproxy.cn` +- `https://goproxy.io` +- `https://mirrors.aliyun.com/goproxy/` + +See the Go official website for more information: [https://github.com/golang/go/wiki/Modules#are-there-always-on-module-repositories-and-enterprise-proxies](https://github.com/golang/go/wiki/Modules#are-there-always-on-module-repositories-and-enterprise-proxies) + +Make sure to select an input proxy address here. + +2. Manually modify the `go.mod` file as follows: + +```go +module my-hello + + +require github.com/gogf/gf latest +``` + +Adding the dependency on the `GoFrame` framework, where `latest` means using the latest version, the IDE will immediately update and download the framework code. Upon success, the IDE will modify the `go.mod` file and generate a `go.sum` dependency analysis file. + +![](/markdown/cb698537b6d68707fb4c1284530d9f90.png) + +3. Subsequently, the `go.mod` file is automatically updated to: + +```go +module my-hello + + +require github.com/gogf/gf v1.6.13 +``` + +Where `v1.6.13` represents the latest framework version detected by vgo. + +4. If the following situation occurs after downloading the latest code framework: [https://www.jetbrains.com/help/go/create-a-project-with-vgo-integration.html](https://www.jetbrains.com/help/go/create-a-project-with-vgo-integration.html) + +![](/markdown/6c6bad791c9e0eee3c740f9cda0ea5c4.png) + +5. Press the shortcut `⌥(option)+↩(return)` or right-click and choose `Sync packages of my-hello` + +![](/markdown/cf02717043547f5e1bf0a14b31d40b1c.png) + +6. After waiting a few seconds, you can see the left `Go Module` with content, and the terminal automatically outputs the downloaded framework version. + +![](/markdown/955367cd46f617411d664c5baa8af9ce.png) + +### Using the Command Line + +1. Open `Terminal` and execute in the project root directory: + +```bash +export GO111MODULE=on GOPROXY=https://goproxy.cn; go get -u github.com/gogf/gf +``` + +This command will immediately download the latest stable version of the `GoFrame` framework. The `export GO111MODULE=on;` indicates enabling the `Go Module` feature (Go `1.11.x` versions are disabled by default and need to be manually enabled), and `export GOPROXY=https://goproxy.cn` indicates using a proxy for download, for obvious reasons, and can also considerably increase the download speed of dependency packages. Other proxy addresses that can be used are: + +- `https://goproxy.cn` +- `https://goproxy.io` +- `https://mirrors.aliyun.com/goproxy` + +![](/markdown/2274104a3ec3a6d2ac7ea35ad374c85c.png) + +2. Subsequently, the `go.mod` file content is automatically updated to: + +```go +module my-hello + + +require github.com/gogf/gf v1.6.13 // indirect +``` + +And generates a new `go.sum` dependency analysis file, which is essentially a temporary file and not very meaningful for our daily development work. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" new file mode 100644 index 00000000000..3dc968c0dc5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/install-go' +title: 'Environment Preparation' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Install GoFrame, GoFrame Quick Start, GoFrame Tutorial, GoFrame Documentation, Development Environment Configuration, Installation Guide, Go Language, GoFrame Development] +description: "Preparation work for the GoFrame framework, including how to install GoFrame and configure the development environment. This guide provides beginners with quick start steps and basic knowledge of GoFrame, helping you quickly set up the GoFrame application development environment." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..47e09dd1967 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/install-go/config-env' +title: 'Configuration' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Development Environment Configuration, Go Environment Variables, Golang, go build, Goland, IDE Tool Configuration, golint, golangci-lint] +description: "Configuration of the development environment under the GoFrame framework, mainly including the environment variable settings of Go language and tool configuration in Goland. It provides detailed instructions on how to set environment variables like GOROOT, GOPATH, and PATH, along with specific configuration steps for both *nix and Windows systems. Additionally, it introduces methods for integrating and configuring tools like go fmt, golangci-lint, goimports, and golint in Goland to help developers improve code quality and development efficiency." +--- + +## Go Environment Variables + +For convenience in development, three environment variables often need to be set in the development environment: + +1. `$GOROOT`: The installation directory of `go`, which remains unchanged after configuration; +2. `$GOPATH`: The project root path of the `go` project in the local development environment (for project compilation, `go build`, `go install`), this environment variable can differ for different projects during compilation; +3. `$PATH` (important): The `bin` directory of `go` needs to be added to the system `$PATH` to conveniently use related go commands, and it also remains unchanged after configuration; + +There is also a detailed explanation of Go's environment variables in the official documentation, please refer to the link: [https://golang.google.cn/doc/install/source](https://golang.google.cn/doc/install/source) + +> The `$GOOS` and `$GOARCH` environment variables are two very practical variables that can be used in cross-compilation for different platforms. You only need to set these two variables before `go build`, which is one of the advantages of the go language: it can compile and generate executable files that run across platforms. It feels more efficient and lightweight compared to QT, although the generated executable files are a bit larger, they are still within an acceptable range. For example, to compile an executable file for `Windows x86` under the `Linux amd64` architecture, you can use the following command: +> +> ``` +> CGO_ENABLED=0 GOOS=windows GOARCH=386 go build hello.go +> ``` +> +> Unfortunately, cross-compilation temporarily does not support the `cgo` method, so you need to set the environment variable `$CGO_ENABLED` to 0. This will generate a `hello.exe` executable file for the `windows x86` architecture in the current directory after execution. + +### Environment Variable Settings + +Other than the `$PATH` environment, other environment variables are optional. + +Why is this step optional? Because future versions of `Go` are gradually beginning to remove support for `$GOPATH`/`$GOROOT`. In addition, there is a `Terminal` function integrated into the `Goland` IDE where the environment variables are already set. + +![Image](/markdown/ba5f3276cff792caf62056ba0ee5987d.png) + +### Setting Environment Variables on `*nix` + +On `*nix` systems (such as `Linux/Unix/MacOS/*BSD`, etc.), you need to add the following environment variable settings to `/etc/profile` and then execute the command `#source /etc/profile` to reload the profile configuration file (or log in again) to add the following variables to the user's environment variables: + +```bash +export GOROOT=/usr/local/go +export GOPATH=/Users/john/Workspace/Go/GOPATH +export PATH=$GOPATH/bin:$GOROOT/bin:$PATH +``` + +### Setting Environment Variables on `Windows` + +For how to modify system environment variables and modify the `PATH` environment variable on Windows, please refer to online tutorials ([Baidu](https://www.baidu.com/s?wd=Windows%20%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%20PATH) or [Google](https://www.google.com/search?q=Windows+修改系统环境变量+PATH)). + +## IDE Tool Configuration + +This article uses the `Goland` development tool as a base to introduce the configuration of common tools in this IDE. + +Commonly used tools include: + +1. `go fmt`: A unified code formatting tool (mandatory). +2. `golangci-lint`: A static code quality inspection tool used for package quality analysis (recommended). +3. `goimports`: An automatic `import` dependency package tool (optional). +4. `golint`: A code convention inspection tool, which also detects the quality of single-file code and is used by the well-known Go quality assessment site [Go Report](https://goreportcard.com) (optional). + +### `go fmt`, `goimports`, `golangci-lint` + +Since these three tools are built into `Goland`, the configuration is relatively simple. Refer to the following pictorial operation example: + +1. In the `Goland` settings, select `Tools` - `File Watchers`, then choose to add + +![Image](/markdown/beffdaf59725b7091d27c05db1cbef06.jpg) + +2. Add these 3 tools in turn by clicking, and use the default configuration + +![Image](/markdown/23d9056527febe75f82dcc8117f086fd.jpg) + +3. Subsequently, while writing code, these 3 tools will be automatically triggered for detection when you save the code files. + + +### Installing and Configuring `golint` Tool (Optional) + +#### Installing `golint` + +Since `Goland` does not come with the `golint` tool, you need to download and install it yourself first. + +**If you have configured goproxy**, you can directly use `go install golang.org/x/lint/golint@latest` to install, without needing the command below. + +Use the following commands to install: + +```bash +mkdir -p $GOPATH/src/golang.org/x/ +cd $GOPATH/src/golang.org/x/ +git clone https://github.com/golang/lint.git +git clone https://github.com/golang/tools.git +cd $GOPATH/src/golang.org/x/lint/golint +go install +``` + +After a successful installation, you will see the automatically generated `golint` binary tool file in the `$GOPATH/bin` directory. + +#### Configuring `golint` + +1. Then in the `Tools` - `File Watchers` configuration of `Goland`, by copying the configuration of `go fmt` + +![Image](/markdown/d6e625d79c63024347705acfc013463c.jpg) + +2. Modify the three settings `Name`, `Program`, and `Arguments`, where `Arguments` needs to add the `-set_exit_status` parameter, as shown in the figure: + +![Image](/markdown/219fe697e559aa6980100557996686a0.jpg) + +3. Save, and the `golint` tool will be automatically triggered for detection when saving the code during writing. + + +### Configuring `golangci-lint` (Optional) + +1. Then in the `Tools` - `File Watchers` configuration of `Goland`, by copying the `go fmt` configuration![](/markdown/267c777a8db90758dd8bad6013f60d7e.png) +2. Modify the `Name`, `Program`, and `Arguments` settings, where `Arguments` needs to add the `run $FileDir$` parameter. Note: The options in `\`Advanced Options\`` can be deselected if the machine is relatively slow, as shown in the figure:![](/markdown/5bf774ae9e6d123efa9010dd223a618a.png) +3. Save, and the `golangci-lint` tool will be automatically triggered for detection when saving the code during writing. +4. Manage the configuration of `golangci-lint` tools through the `go Linter` plugin. Below is the installation and configuration of `go Linter`.![](/markdown/0f0fbd2a3a937573cdc317bbe005a6cf.png)![](/markdown/1c10390d9bfca56c0528f43f122b9ebd.png) + +## IDE Code Style Configuration + +![Image](/markdown/b63649f6d3ac9d3a9eaadb2c94d00cb8.png)![Image](/markdown/1f470b73547b12cd36ff1d4c7328f847.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" new file mode 100644 index 00000000000..7d729ab101e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/install-go/index' +title: 'Installation' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Go development, Golang installation, Go environment setup, Goland, VSCode, IDE settings, JetBrains, Go program, GoFrame framework] +description: "Step-by-step guide for setting up Golang development environment and IDE configuration, suitable for Golang beginners. Detailed instructions on downloading and installing the Go development package, with a recommendation to use JetBrains' Goland as the development IDE, supporting GoFrame framework development. Additionally, includes installation and usage steps for VSCode to help users quickly build their first Go program." +--- + +This section is a hands-on tutorial for installing the `Golang` development environment and `IDE` configuration, specifically for Golang newbies. Experienced users may skip this. + +## Go Development Environment Installation + +### Step 1 - Download Go Development Package + +Visit the Go domestic mirror site download page [https://golang.google.cn/dl/](https://golang.google.cn/dl/), and select your current system version from the versions at the top of the page to download the latest version of the Go development package: + +![](/markdown/d3ce7f0e43ebf678adea8db4c46662d5.png) + +### Step 2 - Installation Guide + +Visit the official installation introduction page [https://golang.google.cn/doc/install](https://golang.google.cn/doc/install), and follow the corresponding installation process according to your current system version. + +For Windows (`msi`) and MacOS (`pkg`), it is recommended to install using the package. The author's current installation process for the MacOS package (`pkg`) is shown below: + +![](/markdown/80729ac6360ac646a39b696d32778d66.png) + +![](/markdown/afc21d8598a0bef86c1a53c8e6784bb6.png) + +![](/markdown/f3f59daf118e34e16a920bcdcf6391de.png) + +The upgrade process for Go development package is the same. + +## IDE Development Environment Installation + +Currently, there are two popular `Go` IDEs: `VSCode+Plugins` (free) and `JetBrains`'s `Goland` (paid). As `JetBrains` is also a sponsor of the `GoFrame` framework, we recommend using `Goland` as the development IDE. For download and registration, please refer to online tutorials ([Baidu](https://www.baidu.com/s?wd=goland%20安装) or [Google](https://www.google.com/search?q=goland+安装)). + +`JetBrains` official website: [https://www.jetbrains.com](https://www.jetbrains.com/?from=GoFrame) + +### Using Goland + +Let's create the first `Go` program, following the usual convention with `hello world`. + +#### Step 1. Open IDE + +![](/markdown/53e952d14b92225b865b2bca6aab7cd2.png) + +#### Step 2. Create Project + +Note the `Go` installation file path (`SDK`), which is thoroughly explained in the [official installation documentation](https://golang.google.cn/doc/install), so please read it carefully. + +The `Location` can be any local path you choose. + +![](/markdown/0520c06f4ba6cb8411ffe09eb0713a26.png) + +#### Step 3. Create Program + +Create a new `go` file named `hello.go` and input the following code: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("hello world!") +} +``` + +![](/markdown/c3c0ce22f357637b39e7656733d91983.png) + +#### Step 4. Run Execution + +Menu `Run` \- `Run` \- `go build hello.go`. + +![](/markdown/a4dc00babf5b34dcc081b916b83713b8.png) + +![](/markdown/80d5fbefe18582fcbbf6f7c34cdff35a.png) + +![](/markdown/ecb56b2f0bb37809e2fd11f89e667566.png) + +Congratulations, your first `Go` program is successful! + +### Using VSCode + +#### [Step 1. Download and Install](https://code.visualstudio.com/) + +#### [Step 2. Install Go Extension](https://docs.microsoft.com/zh-cn/learn/modules/go-get-started/4-install-visual-studio-code?ns-enrollment-type=learningpath&ns-enrollment-id=learn.languages.go-first-steps) + +#### [Step 3. Hello World](https://docs.microsoft.com/zh-cn/learn/modules/go-get-started/5-hello-world) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..8784c4b7073 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" @@ -0,0 +1,42 @@ +--- +slug: '/docs/install-go/private-go-module' +title: 'Private Modules' +sidebar_position: 3 +hide_title: true +keywords: [private dependency management, Go Modules, minimal version selection algorithm, GoFrame, third-party packages, GOPRIVATE, module dependencies, private library, Go Module installation, Golang] +description: "Methods for managing private dependencies when developing projects with the GoFrame framework. It explains how to solve common issues like difficulty in downloading private libraries and version inconsistencies, and provides solutions through the GOPRIVATE setting to configure valid domains for private packages. This is especially important for developers managing dependencies with Go Modules." +--- + +## Version Selection Algorithm + +When a project has dependencies on the same third-party package with inconsistent versions, `Go Modules` uses the "minimal version selection algorithm" (`The minimal version selection algorithm`: [https://github.com/golang/go/wiki/Modules#version-selection](https://github.com/golang/go/wiki/Modules#version-selection)). + +For example, if your module depends on module A with `require D v1.0.0`, and also depends on module B with `require D v1.1.1`, the minimal version selection will choose version `v1.1.1` of D for building (using the highest version). + +> Please don't ask why this algorithm is called the "minimal version selection algorithm," yet it resembles a "highest version selection algorithm." If you have any concerns about this, feel free to raise an issue with the official: [https://github.com/golang/go/issues](https://github.com/golang/go/issues) + +## Private Dependency Management + +If you can perfectly manage your project package dependencies via `go.mod`, you may skip this section. If you encounter issues with package dependency management in projects, it is suggested that you continue reading for inspiration on problem-solving. + +In the preceding sections, we provided a very detailed, illustrated guide on the installation/configuration of the development environment and the installation/use of `Go Module`. In actual project development, you may come across more issues, commonly: + +1. Although `GoFrame` is powerful, most of the time the dependencies include not only `GoFrame` but also some additional third-party packages, especially packages from `golang.org`, which may require a proxy for downloading. This can be handled locally more easily, but may cause some inconvenience on automated deployment systems; +2. Some self-developed third-party packages, particularly business-specific packages, cannot be publicly downloaded (private libraries), and the version control system might not support the `HTTPS` protocol, making it impossible to use `go get` or `go.mod` for downloading and managing; +3. Etc. + +If you encounter the above issues, we recommend setting valid domains for private packages through `GOPRIVATE`. + +For example, the following command-line approach: + +```bash +export GOPROXY=https://goproxy.cn +export GOPRIVATE=git.xxx.com +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go +``` + +> This feature requires `Go v1.13` or higher. + +Set it up in `Goland` as follows: + +![](/markdown/9bab70ea1f17890c926592e79ca4a929.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" new file mode 100644 index 00000000000..7cfeddf04bb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/other/happy-upgrading-from-v1-to-v2' +title: 'Happily Upgrade from v1 to v2' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Upgrade Guide, Compatibility, Import Paths, gredis, v1, v2, Project Directory, CLI Tool] +description: "GoFrame framework has released version v2, and it is recommended to upgrade from v1 to v2 for new features and increased stability. Be sure to adjust import paths and handle potential code changes. The gredis component now supports clustering, leading to configuration changes. The CLI tool has also undergone significant updates and simplifications; refer to the guide for a smooth transition." +--- + +## Preliminary Remarks + +The `GoFrame` framework has released version `v2`, marking a milestone with numerous new features and significant improvements, as well as some pioneering features. + +For new users, please start directly with `v2`. For existing users, it's highly recommended to upgrade from `v1` to `v2`, for greater stability and reliability. + +Key points to note for the `v2` upgrade: + +1. To ensure compatibility and in accordance with the official `Golang` module management norms, we have changed the `import` paths, hence a global replacement of `import` paths is required. +2. Given this is a major upgrade, some methods have been removed or updated, rest assured that better alternatives are provided. +3. Generally, after upgrading, simply recompile and modify the code according to the compilation error messages to complete the upgrade. +4. The `gredis` component supports clustering, thus changes in configuration need attention. + +We have not provided an upgrade tool as we believe that a guide is sufficient. + +## Compatibility Between v1 and v2 + +To ensure project compatibility, it is possible to simultaneously depend on both `v1` and `v2` versions. Therefore, we have released the last version `v1.16.7` of `v1`, which you can upgrade to if needed. This release addresses the common issue of `client_tracing.go:73:3: undefined: attribute.Any`. However, depending on both versions of `GoFrame` might reduce project maintainability, and it's suggested to upgrade to `v2` as soon as possible. + +## Replace Dependencies with v2 + +Globally replace the source code as follows: + +```go +"github.com/gogf/gf/ => "github.com/gogf/gf/v2/ +``` + +Like this: + +![](/markdown/6e0a32d42cc581bd2f4220d721714f41.png) + +## Download the Latest v2 Version + +```bash +go get -u github.com/gogf/gf/v2@latest +``` + +## Project Directory Adjustments + +If you are using the officially recommended project directory structure of `GoFrame`, you can manually adjust to the latest directory structure: [Project Structure🔥](../框架设计/工程开发设计/工程目录设计.md) + +Note that the latest `cli` tool no longer supports the creation of projects with the old directory structure. + +## Compilation and Running Modifications + +Run your project, and if you encounter compilation problems, manually modify them according to the error messages, iterating as necessary. + +If you're unsure how to modify, leave a comment on this document, and our community team will assist you with the upgrade. + +## Important Changes to the CLI + +1. The `swagger` command has been removed. In the `v1` version, the `gf swagger` command automatically installed a third-party `swag` tool to parse annotations in source code to generate `swagger` documents. This method of document management and maintenance has issues: it only supports the `Swagger2.0` protocol, has a poor usage experience, and maintaining annotations in sync with code is difficult, leading to inconsistencies between interface documentation and code. With standard routes in the new version, this command is discarded. If you need to continue using this function, manually install the third-party `swag` tool: [https://github.com/swaggo/swag](https://github.com/swaggo/swag) +2. The `update` command has been removed. Starting from version `v2`, the installation and download of the `CLI` tool are unified via `github`, reducing maintenance workload. The command may be reintroduced in the future. + +## Some Important Notes + +1. The `gf-cli` repository has been moved to the main `gf` repository for maintenance, ensuring tools and framework versions are in sync. The original repository is no longer maintained. For details, see: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) +2. Core framework components adopt an interface design to ensure wide applicability of interfaces, with some minor reductions in exposed methods. +3. Extensive use of the `gvar` generic in core framework components enhances usability, masking specific type implementations at the lower level. For stability and usability, the framework will not consider using the official `Golang` generics within the next `2-3` years. The official generics have value for modification in certain features of some framework components. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" new file mode 100644 index 00000000000..dedc1ee58ab --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/other/printing-format' +title: 'Printing Placeholder Format' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame framework, formatted printing, printf, placeholder, data type, Go syntax, boolean, integer, float] +description: "How to use the printf series of functions with placeholders in GoFrame framework for formatted printing according to data types, including general placeholders, booleans, integers, floats, strings, pointers, etc., to help developers code more efficiently in Go and achieve formatted output." +--- + +`*printf` series functions support `format` formatting parameters. Here, we categorize according to the data type of the variables replaced in the placeholders for easy lookup and memorization. + +## Placeholder List + +| Category | Placeholder | Description | +| --- | --- | --- | +| **General Placeholder** | `%v` | Default format representation of the value | +| `%+v` | Similar to `%v`, but adds field names when printing structs | +| `%#v` | Go-syntax representation of the value | +| `%T` | Print the type of the value | +| `%%` | Percent sign | +| **Boolean** | `%t` | `true` or `false` | +| **Integer** | `%b` | Represented in binary | +| `%c` | The Unicode code point for the value | +| `%d` | Represented in decimal | +| `%o` | Represented in octal | +| `%x` | Represented in hexadecimal (using `a-f`) | +| `%X` | Represented in hexadecimal (using `A-F`) | +| `%U` | Represented in Unicode format: `U+1234`, equivalent to `U+%04X` | +| `%q` | Quoted Go-syntax character literal of the value, with possible escapes for safety | +| **Floating Point and Complex** | `%b` | Scientific notation with binary exponent, e.g.: `-123456p-78` | +| `%e` | Scientific notation, e.g.: `-1234.456e+78` | +| `%E` | Scientific notation, e.g.: `-1234.456E+78` | +| `%f` | Decimal point but no exponent, e.g.: `123.456` | +| `%F` | Equivalent to `%f` | +| `%g` | Uses `%e` or `%f` format depending on the situation for more concise and accurate output | +| `%G` | Uses `%E` or `%F` format depending on the situation for more concise and accurate output | +| **String and \[\]byte** | `%s` | Directly outputs the string or `[]byte` | +| `%q` | Double-quoted Go-syntax string literal, with possible escapes for safety | +| `%x` | Represented in two-character hexadecimal for each byte (using `a-f`) | +| `%X` | Represented in two-character hexadecimal for each byte (using `A-F`) | +| **Pointer** | `%p` | Hexadecimal representation with `0x` prefix | +| **Width Specifier** | `%f` | Default width, default precision | +| `%9f` | Width 9, default precision | +| `%.2f` | Default width, precision 2 | +| `%9.2f` | Width 9, precision 2 | +| `%9.f` | Width 9, precision 0 | +| **Placeholder Modifiers** | `+` | Always show sign for numeric values; for `%q` (`%+q`) generates ASCII-only output (with escapes) | +| ` ` | Space, prefixes positive numbers with a space and negative numbers with a sign; for `%x` or `%X` (`% x` or `% X`), inserts spaces between printed bytes | +| `-` | Pads the right instead of the default left (right-align to left-align switch) | +| `#` | Prefixes octal with `0` (`%#o`), hexadecimal with `0x` (`%#x`) or `0X` (`%#X`), strips `0x` for pointers (`%#p`); for `%q` (`%#q`) and `%U` (`%#U`), outputs Go literal with quotes and spaces | +| `0` | Pads with `0` instead of spaces, placed after sign for numeric types | +| | | | + +## References + +- [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt) +- [https://www.liwenzhou.com/posts/Go/go_fmt/](https://www.liwenzhou.com/posts/Go/go_fmt/) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..80e14d4aaff --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/other/system-signals' +title: 'Appendix: System Signals List' +sidebar_position: 7 +hide_title: true +keywords: [system signals,Linux signals,Windows signals,signal handling,SIGHUP,SIGINT,SIGTERM,GoFrame,signal programming,signal list] +description: "This document lists common signals in Linux and Windows systems and their meanings, including how to handle these signals. For developers, understanding the role and reaction mechanism of each signal is crucial, especially when programming with the GoFrame framework. This article provides a convenient reference to quickly locate and understand the use of signals." +--- + +## Linux + +| Signal | Value | Default Action | Meaning (Reason for Signal) | +| --- | --- | --- | --- | +| `SIGHUP` | 1 | Term | Hangup of terminal or process death | +| `SIGINT` | 2 | Term | Interrupt signal from keyboard | +| `SIGQUIT` | 3 | Core | Quit signal from keyboard | +| `SIGILL` | 4 | Core | Illegal instruction | +| `SIGABRT` | 6 | Core | Abort exception signal | +| `SIGFPE` | 8 | Core | Floating-point exception | +| `SIGKILL` | 9 | Term | Kill | +| `SIGSEGV` | 11 | Core | Invalid memory reference (segmentation fault) | +| `SIGPIPE` | 13 | Term | Broken pipe: write to pipe with no readers | +| `SIGALRM` | 14 | Term | Timer signal from alarm | +| `SIGTERM` | 15 | Term | Termination | +| `SIGUSR1` | 30,10,16 | Term | User-defined signal 1 | +| `SIGUSR2` | 31,12,17 | Term | User-defined signal 2 | +| `SIGCHLD` | 20,17,18 | Ign | Child process stopped or terminated | +| `SIGCONT` | 19,18,25 | Cont | Continue if stopped | +| `SIGSTOP` | 17,19,23 | Stop | Stop signal not from terminal | +| `SIGTSTP` | 18,20,24 | Stop | Stop signal from terminal | +| `SIGTTIN` | 21,21,26 | Stop | Background process attempting read | +| `SIGTTOU` | 22,22,27 | Stop | Background process attempting write | +| `SIGBUS` | 10,7,10 | Core | Bus error (memory access error) | +| `SIGPOLL` | | Term | Pollable event (Sys V), synonym for SIGIO | +| `SIGPROF` | 27,27,29 | Term | Profiling timer expired | +| `SIGSYS` | 12,-,12 | Core | Bad system call (SVr4) | +| `SIGTRAP` | 5 | Core | Trace/breakpoint trap | +| `SIGURG` | 16,23,21 | Ign | Urgent condition on socket (4.2BSD) | +| `SIGVTALRM` | 26,26,28 | Term | Virtual timer expired (4.2BSD) | +| `SIGXCPU` | 24,24,30 | Core | CPU time limit exceeded (4.2BSD) | +| `SIGXFSZ` | 25,25,31 | Core | File size limit exceeded (4.2BSD) | +| `SIGIOT` | 6 | Core | IOT trap, synonym for SIGABRT | +| `SIGEMT` | 7,-,7 | | Term | +| `SIGSTKFLT` | -,16,- | Term | Coprocessor stack fault (unused) | +| `SIGIO` | 23,29,22 | Term | I/O possible on descriptor | +| `SIGCLD` | -,-,18 | Ign | Synonym for SIGCHLD | +| `SIGPWR` | 29,30,19 | Term | Power failure/restart (System V) | +| `SIGINFO` | 29,-,- | | Synonym for SIGPWR | +| `SIGLOST` | -,-,- | Term | File lock lost | +| `SIGWINCH` | 28,28,20 | Ign | Window size change (4.3BSD, Sun) | +| `SIGUNUSED` | -,31,- | Term | Unused signal (will be SIGSYS) | + +## Windows + +| Signal | Description | +| --- | --- | +| **`SIGABRT`** | Abnormal termination | +| **`SIGFPE`** | Arithmetic error | +| **`SIGILL`** | Illegal instruction | +| **`SIGINT`** | Ctrl+C signal | +| **`SIGSEGV`** | Illegal storage access | +| **`SIGTERM`** | Termination request | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" new file mode 100644 index 00000000000..25b4cfa3eae --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" @@ -0,0 +1,191 @@ +--- +slug: '/docs/faq' +title: 'Frequently Asked Questions (FAQ)' +sidebar_position: 10 +hide_title: true +description: "Common questions and answers when developing with the GoFrame framework and Golang, including how to handle program exceptions, create new goroutines, shield json output fields, solve compatibility issues, configure the environment, and fix glog errors, to help developers optimize and debug applications." +keywords: [GoFrame,GoFrame framework,Golang,program exception handling,goroutine,json output,compatibility issues,environment configuration,glog errors,multi-environment configuration] +--- + +Message: + +- Please use the search function in the upper right corner of the page to quickly search for frequently asked questions. +- Everyone is welcome to actively participate in editing and record how they filled the pits they encountered. **Many hands make light work** + +## I. Golang Basics + +### 1. Program throws an exception, but the program crashes directly and is not automatically captured by the framework + +Using the `GoFrame` framework is rigorous and safe. If a program throws an exception, it will be captured by the framework by default. If it is not automatically captured, it may be due to the program logic opening new `goroutine`s independently, causing an exception in the new `goroutine`. So there are two options for you to choose from: + +- It is not recommended to open `goroutine` in a request to handle the request, as this may cause `goroutine` to expand rapidly. When there are too many `goroutine`s, it will also affect the overall scheduling of the program at the `Go` engine level. +- If it is really necessary to start a new `goroutine`, consider using `grpool.AddWithRecover` to create a new `goroutine`, which captures exceptions automatically. For more detailed information, please refer to: [Goroutine](组件列表/系统相关/协程管理-grpool.md). + +### 2. Shield some fields in `json` output + +You can achieve this through structure nesting using `*struct{}` type with no space usage and the `omitempty` feature, which does not output fields if they are empty. + +```go +type User struct { + Pwd string `json:"pwd"` + Age int `json:"age"` +} + +type UserOut struct { + User + Pwd *struct{} `json:"pwd,omitempty"` // The json name of this field must match the nested field json name, or it will be invalid +} + +func TestJson(t *testing.T) { + u := User{Pwd: "123", Age: 1} + bb := UserOut{User: u} + b, _ := json.MarshalIndent(bb, "", " ") + t.Log(string(b)) +} +``` + +## II. Compatibility Issues + +### 1. `client_tracing.go:73:3: undefined: attribute.Any` + +The following error: + +```bash +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing.go:73:3: undefined: attribute.Any +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing_tracer.go:150:3: undefined: attribute.Any +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing_tracer.go:151:3: undefined: attribute.Any +``` + +This error is caused by the `otel` package that `goframe` depends on having too low a version (the `otel` package is a third-party package used by `OpenTelemetry` implemented in `Golang`, commonly used, with many third-party basic components relying on it), while other third-party dependencies in the project have higher versions of the `otel` package. According to the `Golang module` management strategy, the project will use the latest `otel` package, which leads to version incompatibility. + +The root cause is the `otel` package having made incompatible upgrades during its iteration. However, the `otel` package has now stabilized, reducing the likelihood of incompatibilities. + +The solution is to upgrade the `goframe` version. The latest version of `goframe` has updated to use a stable `otel` package. If you are already using the latest version of `v1` ( `v1.16`), then upgrade to `v2` to resolve this. + +### 2. `go mod tidy` failure while using `gf` dependency `v1.16.2` + +`found (v0.36.0), but does not contain package go.opentelemetry.io/otel/metric/registry` + +![](/markdown/08e4b24634f2819f4e6439c9cf9e08a8.png) + +Solution, upgrade `gf` dependency to `v1.16.9` then `go mod tidy` + +## III. Database Issues + +Please refer to the section: [ORM - FAQ](核心组件/数据库ORM/ORM常见问题.md) + +## IV. Usage Issues + +### 1. How to load different configuration files for different environments? + +Different environments refer to: Development environment / Testing environment / Pre-production environment / Production environment, etc. + +- First of all, in some internet projects, especially under distributed or microservice architecture, a configuration management center is often used, corresponding to different environments, so such a problem will not arise. +- Secondly, if it is under the traditional project management method, the configuration files may be managed together in the code repository, which is not recommended. If you still want to do this, you can let the program automatically select the configuration file or specify the configuration directory through system environment variables or command-line startup parameters, reference [Configuration](核心组件/配置管理/配置管理.md) section. For example: `./app --gf.gcfg.file config-prod.toml ` then the default configuration file is modified to `config-prod.toml` file by command-line startup parameters. + +We do not recommend distinguishing and reading different environment configuration files through code logic in the program. + + +### 2. `glog with "ERROR: logging before flag.Parse"` + +There is a simple logging library package from `Golang` also called `glog`, check the package name at the top of your file `import`, change `github.com/golang/glog` to the framework's logging component, please refer to: [Logging](核心组件/日志组件/日志组件.md) + +### 3. How to use `gcron` and `http` at the same time? + +```go +func main() { + // Scheduled task 1 + gcron.AddSingleton("*/5 * * * * *", func() { + task.Test() + glog.Debug("gcron1") + }) + + // Scheduled task 2 + gcron.AddSingleton("*/10 * * * * *", func() { + glog.Debug("gcron2") + }) + + // Receive http requests + g.Server().Run() +} +``` + +Note, `gcron` must be before `g.Server().Run`. + +### 4. What `struct tag`s does `GoFrame` have? + +Parameter requests, data validation, `OpenAPIv3`, command management, database ORM. + +| Tag (Abbreviation) | Full Name | Description | Documents | +| --- | --- | --- | --- | +| `v` | `valid` | Data validation tag. | [Struct Validation - Example](核心组件/数据校验/数据校验-参数类型/数据校验-Struct校验/Struct校验-基本使用.md) | +| `p` | `param` | Custom request parameter matching. | [Request - Parameter Binding](WEB服务开发/请求输入/请求输入-对象处理.md) | +| `d` | `default` | Default value binding for request parameters. | [Request - Default Value](WEB服务开发/请求输入/请求输入-默认值绑定.md) | +| `orm` | `orm` | ORM tag, used to specify table name, association relationships. | [Dao/Do/Entity Generating](开发工具/代码生成-gen/数据规范-gen%20dao.md)
[Model Association - With](核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) | +| `dc` | `description` | Generic struct property description, used by both ORM and interfaces. Belongs to the framework's default property description tag. | | + +Others: + +- Command-line structured management parameters: [Command - Structure](核心组件/命令管理/命令管理-结构化参数.md) +- Framework commonly used tag labels are centrally managed under the `gtag` component: [https://github.com/gogf/gf/blob/master/util/gtag/gtag.go](https://github.com/gogf/gf/blob/master/util/gtag/gtag.go) +- In the interface documentation section, as the label form is used to generate the `OpenAPI` documentation, there are many tags, please refer to the section: [API Document](WEB服务开发/接口文档/接口文档.md) + +### 5. `HTTP Server` throws `context cancel` error + +From version `v2.5` of the framework, the `Request` object of the framework's `HTTP Server` will directly inherit from the standard library's `http.Request` object, which includes the `context` object. When the client, such as a browser or `HTTP Client`, cancels the request, the server will receive a `context cancel` operation (`context.Done`), but the server will not directly report a `context cancel` error. This error often occurs when business logic calls underlying components such as databases or messaging, where these components recognize the `context cancel` operation, stop execution, and pass the `context cancel` error up to notify the upper layer that execution has already terminated. + +This behavior is in line with the standard library design. There is no need for the server to continue execution after the client terminates the request. + +[Frequent server context cancel errors](../docs/WEB服务开发/常见问题.md) + +## V. Environment Related + +### 1. `go build main.go` on `Linux` hints connection time out `connection timed out` + +```bash +go: github.com/gogf/gf@v1.14.6-0.20201214132204-c685876e6f67: Get "https://proxy.golang.org/github.com/gogf/gf/@v/v1.14.6-0.20201214132204-c685876e6f67.mod": +dial tcp 172.217.160.113:443: +connect: connection timed out +``` + +Solution: + +```bash +export GO111MODULE=on +export GOPROXY=https://goproxy.cn +``` + +Please refer to: + +- [Go Module](其他资料/准备开发环境/Go%20Module.md) +- [https://goproxy.cn](https://goproxy.cn) + +### 2. `gf` command not found on `Linux` + +```bash +./gf install +After installation +execute gf -v +gf: command not found +and there is no gf file in the /usr/bin directory + +Solution: +Copy the sh file to the /usr/bin directory +cp gf /usr/bin + +Then execute +gf -v + +You will see +GoFrame CLI Tool v1.15.4, https://goframe.org +Install Path: /bin/gf +Build Detail: +Go Version: go1.16.2 +GF Version: v1.15.3 +Git Commit: 22011e76dc3e14006936164cc89e2d4c9190a36d +Build Time: 2021-03-30 15:43:22 +``` + +### 3. `gf` command not found on `Win10` + +Solution: Install `gf.exe` Refer to: [CLI Tool](开发工具/开发工具.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" new file mode 100644 index 00000000000..92f21ab6a1d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/cli/build' +title: 'Cross-Compiling' +sidebar_position: 4 +hide_title: true +keywords: [Cross-Compile, GoFrame, Compile Variables, Compilation Configuration, Built-in Variables, Build Information, gf build, Compilation Options, Built-in Compilation, Project Ecosystem] +description: "Cross-compilation using the GoFrame framework. With the gf build command, you can quickly generate an executable file that includes information such as the current Go version, GoFrame version, Git Commit, and more. Supports specifying parameters from both the command line and configuration files, meeting the compilation needs for different operating systems and platforms, providing developers with a convenient build solution." +--- + +## Usage + +For specific parameters, use `gf build -h` to view help. + +Limited to cross-compiling projects using the `GoFrame` framework, supporting direct cross-compilation for most common systems. + +## Built-in Compile Variables + +The `build` command automatically embeds compilation variables, which users can customize and obtain at runtime through the `gbuild` component. Projects using `gf build` will have the following variables embedded by default (refer to `gf -v`): + +- Current `Go` version +- Current `GoFrame` version +- Current `Git Commit` (if it exists) +- Current compile time + +## Compilation Configuration File + +The `build` supports specifying compilation parameters and options from both the command line and configuration file. All components and ecosystem projects of the `GoFrame` framework use the same configuration management component. For the default configuration file and usage, refer to the chapter [Configuration](../核心组件/配置管理/配置管理.md). Below is a simple configuration example for reference: + +```yaml +gfcli: + build: + name: "gf" + arch: "all" + system: "all" + mod: "none" + packSrc: "resource,manifest" + version: "v1.0.0" + output: "./bin" + extra: "" +``` + +The definitions of configuration options are the same as the command line options with the same name. + +| Name | Default Value | Meaning | Example | +| --- | --- | --- | --- | +| `name` | Same as the program entry `go` file | The name of the generated executable file. If it's the `windows` platform, `.exe` is added by default | `gf` | +| `arch` | Current system architecture | Compile architecture, separated by `,`, `all` indicates compiling for all supported architectures | `386,amd64,arm` | +| `system` | `Current system platform` | Compilation platform, separated by `,`, `all` indicates compiling for all supported platforms | `linux,darwin,windows` | +| `path` | `./bin` | **Directory address** where the compiled executable file is stored | `./bin` | +| `mod` | | Same as the `go build -mod` compilation option, not commonly used | `none` | +| `cgo` | `false` | Whether to enable `CGO`, disabled by default. If enabled, cross-compilation might have issues. | | +| `packSrc` | | Directories to package, separated by `,`, generated to `internal/packed/build_pack_data.go` | `public,template,manifest` | +| `packDst` | `internal/packed/build_pack_data.go` | Path for the generated `Go` file after packaging, generally specified relative to the project's directory | | +| `version` | | Program version, if specified, an additional directory with the version name is included in the generated path | `v1.0.0` | +| `output` | | Path of the output executable file. When specified, `name` and `path` parameters are invalid, commonly used to compile a single executable file. | `./bin/gf.exe` | +| `extra` | | Additional custom compile parameters, directly passed to the `go build` command | | +| `varMap` | | Custom built-in variable key-value pairs, can be obtained from the built binary through the `gbuild` package for compile information. | ```
gfcli:
build:
name: "gf"
arch: "all"
system: "all"
mod: "none"
cgo: 0
varMap:
k1: v1
k2: v2
``` | +| `exitWhenError` | `false` | Immediately stop and exit the compilation process (using `os.Exit(1)`) on errors during compilation | | +| `dumpEnv` | `false` | Prints the environment variable information of the current compilation environment in the terminal before each compilation | | +:::tip +The built-in variables during compilation can be obtained at runtime through the `gbuild` package [Build Information](../组件列表/系统相关/构建信息-gbuild.md). +::: +## Usage Example + +```text +$ gf build +2020-12-31 00:35:25.562 start building... +2020-12-31 00:35:25.562 go build -o ./bin/darwin_amd64/gf main.go +2020-12-31 00:35:28.381 go build -o ./bin/freebsd_386/gf main.go +2020-12-31 00:35:30.650 go build -o ./bin/freebsd_amd64/gf main.go +2020-12-31 00:35:32.957 go build -o ./bin/freebsd_arm/gf main.go +2020-12-31 00:35:35.824 go build -o ./bin/linux_386/gf main.go +2020-12-31 00:35:38.082 go build -o ./bin/linux_amd64/gf main.go +2020-12-31 00:35:41.076 go build -o ./bin/linux_arm/gf main.go +2020-12-31 00:35:44.369 go build -o ./bin/linux_arm64/gf main.go +2020-12-31 00:35:47.352 go build -o ./bin/linux_ppc64/gf main.go +2020-12-31 00:35:50.293 go build -o ./bin/linux_ppc64le/gf main.go +2020-12-31 00:35:53.166 go build -o ./bin/linux_mips/gf main.go +2020-12-31 00:35:55.840 go build -o ./bin/linux_mipsle/gf main.go +2020-12-31 00:35:58.423 go build -o ./bin/linux_mips64/gf main.go +2020-12-31 00:36:01.062 go build -o ./bin/linux_mips64le/gf main.go +2020-12-31 00:36:03.502 go build -o ./bin/netbsd_386/gf main.go +2020-12-31 00:36:06.280 go build -o ./bin/netbsd_amd64/gf main.go +2020-12-31 00:36:09.332 go build -o ./bin/netbsd_arm/gf main.go +2020-12-31 00:36:11.811 go build -o ./bin/openbsd_386/gf main.go +2020-12-31 00:36:14.140 go build -o ./bin/openbsd_amd64/gf main.go +2020-12-31 00:36:17.859 go build -o ./bin/openbsd_arm/gf main.go +2020-12-31 00:36:20.327 go build -o ./bin/windows_386/gf.exe main.go +2020-12-31 00:36:22.994 go build -o ./bin/windows_amd64/gf.exe main.go +2020-12-31 00:36:25.795 done! +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" new file mode 100755 index 00000000000..b1eff1daf7b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" @@ -0,0 +1,50 @@ + + + + + + + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" new file mode 100755 index 00000000000..c48288b1d52 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" @@ -0,0 +1,50 @@ + + + + + + + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" new file mode 100755 index 00000000000..b600b17906f Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" new file mode 100644 index 00000000000..ae008be39ed --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/cli/gen' +title: 'Code Generating' +sidebar_position: 5 +hide_title: true +keywords: [Code Generation, GoFrame, CLI Tool, Project Development, Enterprise-Level Project, Code Standards, Team Collaboration, Development Efficiency, ORM Models, Protobuf Files] +description: "Starting from version v2, the CLI tool is integrated with the latest version of the GoFrame framework, providing developers with code generation features to standardize project code writing and simplify development complexity. Especially in enterprise-level and team projects, the CLI tool can significantly enhance development efficiency, allowing developers to focus on business logic." +--- +:::info +Starting from version `v2`, the latest version of the `CLI` tool features will be compiled with the latest version of the `GoFrame` framework. If there is a compatibility issue between the auto-generated code of the local `CLI` tool and the project's `GoFrame` framework version, it is recommended to upgrade the project framework version or customize the installation of an older version of the `CLI` tool. For the installation method of the old version of the CLI tool, refer to the repository homepage introduction: [https://github.com/gogf/gf-cli](https://github.com/gogf/gf-cli) +::: +## Important Note🔥 + +- The code generation function provided by the `CLI` tool aims to **standardize project code writing**, **simplify project development complexity**, and **allow developers to focus on business logic itself**. +- The `CLI` tool itself requires a certain degree of prior learning and understanding cost (try to understand why), but once proficient, everyone's development work will be twice as effective with half the effort. +- The code generation function of the `CLI` tool will be highly beneficial for enterprise-level projects and multi-member team projects. However, for small single-person projects, developers can evaluate whether to use it based on personal preference. The `GoFrame` framework itself only provides basic components and adopts a modular and flexible design without strict requirements on project code; however, the `CLI` tool will have some restrictions to ensure that each member of the team maintains consistent pace and style, preventing developers from writing code too freely. + +## Usage + +```text +$ gf gen -h +USAGE + gf gen COMMAND [OPTION] + +COMMAND + ctrl parse api definitions to generate controller/sdk go files + dao automatically generate go files for dao/do/entity + enums parse go files in current project and generate enums go file + pb parse proto files and generate protobuf go files + pbentity generate entity message files in protobuf3 format + service parse struct and associated functions from packages to generate service go file + +DESCRIPTION + The "gen" command is designed for multiple generating purposes. + It's currently supporting generating go files for ORM models, protobuf and protobuf entity files. + Please use "gf gen dao -h" for specified type help. +``` + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" new file mode 100644 index 00000000000..38bced7c30f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/cli/gen-pb' +title: 'Protobuf Compilation' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Protocol Compilation, protobuf, GoFrame gen pb, Protocol File, Controller File, Command Line Tool, Generate Go File, CLI Tool] +description: "Use the command line tool in the GoFrame framework to compile proto files, generating the corresponding protobuf Go files and controller files. With the gf gen pb command, users can set different paths to store the generated interface and controller files, meeting the needs of project engineering. Additionally, this article lists the usage guide and precautions for this command, allowing developers to better use this feature." +--- +:::tip +This feature is available starting from version `v2.4`. +::: +## Introduction + +This command is used to compile `proto` files, generating the corresponding `protobuf go` files and corresponding controller files. + +## Command Usage + +```text +$ gf gen pb -h +USAGE + gf gen pb [OPTION] + +OPTION + -p, --path protobuf file folder path + -a, --api output folder path storing generated go files of api + -c, --ctrl output folder path storing generated go files of controller + -h, --help more information about this command + +EXAMPLE + gf gen pb + gf gen pb -p . -a . -p . +``` +:::tip +If you are using the framework's recommended project scaffold and have the `make` tool installed, you can also use the `make pb` shortcut command. +::: +Parameter Description: + +| Name | Required | Default Value | Meaning | +| --- | --- | --- | --- | +| `path` | No | `manifest/protobuf` | Points to the `proto` protocol definition file | +| `api` | No | `api` | Points to the directory where the generated interface files are stored | +| `ctrl` | No | `internal/controller` | Points to the directory where the generated controller files are stored | + +## Precautions + +- When generating controller files, it will automatically detect whether there is already a corresponding interface implementation method. If it already exists, the corresponding interface method will not be regenerated to prevent overwriting. +- If this command is executed in the `proto` directory and the specified `path` directory does not exist, the local `proto` files will be automatically compiled, and the compiled files will be generated in the current directory with the controller file generation feature automatically disabled. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" new file mode 100644 index 00000000000..14223ac447f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" @@ -0,0 +1,161 @@ +--- +slug: '/docs/cli/gen-ctrl' +title: 'Controller Generating' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,API interface,HTTP development,GRPC,controller generation,SDK automation,code specification,collaborative development,code generation,project management] +description: "Generate API interface controllers and SDK code using GoFrame to help developers reduce repetitive code work, standardize API and controller code structure, and improve collaborative development efficiency. Additionally, it offers the functionality to generate HTTP SDK code for easy internal and external service calls. Code generation can be performed using command-line mode and automatic generation mode, and detailed command parameter instructions and usage examples are provided." +--- +:::tip +This feature is available from version `v2.5`. Currently, this command only supports `HTTP` interface development; for `GRPC` parts, please refer to the `gen pb` command. In the future, `HTTP` and `GRPC` may be unified to use this command to generate controller and `SDK` source code. +::: +## Introduction + +### Pain Points Solved + +When developing a project, it often starts with designing the `API` interface based on business requirements and scenarios, using `proto` or `golang struct` to design the `API` input and output. Then, a corresponding controller implementation is created, and an `SDK` (under the condition of the Golang language) may also be provided for internal/external service calls. The following pain points may arise during development: + +- **Repetitive code work is cumbersome**. After creating input and output definition files in the `API`, corresponding files need to be created in the controller directory, along with controller initialization code. The operation of repeatedly copying input and output structure names from the `API` code is cumbersome. +- **No reliable normative constraints between the API and controller**. Apart from certain naming constraints for the `API`, there are no constraints for controller creation and method naming. The high flexibility makes it difficult to enforce consistency between API structure names and controller method names, leading to potential maintenance costs as interfaces increase. +- **High probability of code file conflicts in team development collaboration**. When multiple people execute changes to a file, the probability of file conflicts increases, leading to unnecessary energy expenditure in resolving these conflicts. +- **Lack of automated API HTTP SDK generation tool**. After developing the `API`, immediately providing it for internal or external calls often requires manually maintaining the `SDK` code, which can be time-consuming and costly. + +### Command Features + +- Standardizes API definition and controller file naming, as well as controller implementation method naming. +- Establishes the association between API definitions and controller code, facilitating quick location of API implementations. +- Automatically generates controller interface, controller initialization files and code, and interface initialization code based on API definitions. +- Automatically generates easy-to-use HTTP SDK code according to API definitions. This feature is configurable and turned off by default. +- Supports `File Watch` automation mode: automatically incrementally updates corresponding controller and `SDK` code when an API structure definition file changes. + +## Pre-conditions + +### Important Norms 🔥 + +One of the purposes of this command is to standardize `api` code writing, which requires some important norms to be understood (otherwise the code cannot be generated): + +- `api` layer's API definition file path needs to satisfy `/api/module/version/definition_file.go`, for example: `/api/user/v1/user.go`, `/api/user/v1/user_delete.go`, etc. + - **Module** refers to the division of `API` modules, which can be split according to different **business attributes** for easier aggregation and maintenance. Modules can also be considered as specific business resources. + - **Version** is usually defined as `v1`/`v2`, etc., for compatibility version control of `API`. Different versions are marked by different version numbers when compatibility updates occur. The first version is managed with `v1` by default. + - **Definition file** refers to the `API` input and output definition files, where each `API` is usually maintained independently in a separate `go` file. Of course, it also supports putting multiple `APIs` in one `go` file for unified maintenance. +- Structure names in `api` definition should conform to the naming convention of `Operation+Req` and `Operation+Res`, e.g., `GetOneReq/GetOneRes`, `GetListReq/GetListRes`, `DeleteReq/DeleteRes`, etc. + - The operation refers to the current `API` module's operation name, typically corresponding to `CRUD` as: `Create`, `Read`, `Update`, `Delete`. + +Below is a `Hello` interface example in the project template: + +![](/markdown/71be1e0ac8d8eaa7794a476086c110c2.png) + +### Suggested Naming + +We provide some suggested naming conventions for common API definitions for your reference: + +| Operation Name | Suggested Naming | Notes | +| --- | --- | --- | +| **Query List** | `GetListReq/Res` | Usually to fetch paginated query data records from the database | +| **Query Details** | `GetOneReq/Res` | Usually requires passing the primary key condition to query record details from the database | +| **Create Resource** | `CreateReq/Res` | Typically used to insert one or more data records into a table | +| **Modify Resource** | `UpdateReq/Res` | Typically used to modify one or more data records in a table according to certain conditions | +| **Delete Resource** | `DeleteReq/Res` | Typically used to delete one or more data records in a table according to certain conditions | + +## Command Usage + +This command analyzes the code within the specified `api` API definition directory and automatically generates the corresponding controller/`SDK Go` code files. + +### Manual Mode + +To manually execute the command line, run `gf gen ctrl` in the project's root directory. It will completely scan the `api` API definition directory and generate corresponding code. + +```text +$ gf gen ctrl -h +USAGE + gf gen ctrl [OPTION] + +OPTION + -s, --srcFolder source folder path to be parsed. default: api + -d, --dstFolder destination folder path storing automatically generated go files. default: internal/controller + -w, --watchFile used in file watcher, it re-generates go files only if given file is under srcFolder + -k, --sdkPath also generate SDK go files for api definitions to specified directory + -v, --sdkStdVersion use standard version prefix for generated sdk request path + -n, --sdkNoV1 do not add version suffix for interface module name if version is v1 + -c, --clear auto delete generated and unimplemented controller go files if api definitions are missing + -m, --merge generate all controller files into one go file by name of api definition source go file + -h, --help more information about this command + +EXAMPLE + gf gen ctrl + +``` +:::tip +If using the project's recommended framework scaffolding and `make` tool is installed, `make ctrl` shortcut can also be used. +::: +Parameter explanation: + +| Name | Required | Default Value | Meaning | +| --- | --- | --- | --- | +| `srcFolder` | No | `api` | Points to the directory address of `api` API definition files | +| `dstFolder` | No | `internal/controller` | Points to the directory where the generated controller files are stored | +| `watchFile` | No | | Used in IDE file monitoring, for automatic generation when files change | +| `sdkPath` | No | | If `HTTP SDK` needs to be generated, this parameter specifies the directory path for storing generated SDK code | +| `sdkStdVersion` | No | `false` | Whether the generated `HTTP SDK` uses standard version management. It will automatically add a version routing prefix according to the `API` version. For example, `v1` version API will have a `/api/v1` request routing prefix added automatically. | +| `sdkNoV1` | No | `false` | In the generated `HTTP SDK`, whether the interface module name should not have the `V1` suffix when the interface is version `v1`. | +| `clear` | No | `false` | Whether to delete generated controller interface files in `internal/controller` when corresponding `api` layer definitions don't exist. | +| `merge` | No | `false` | Controls whether to generate `ctrl` controller code files according to API layer files rather than splitting API interfaces into different implementation files by default. | + +### Automatic Mode (Recommended) + +If using `GolandIDE`, the provided configuration file, [watchers.xml](gen-ctrl-watchers.xml), can be used for automatically generating interface files when code file modifications are detected. The usage is shown in the image below: + +![](/markdown/7d15b228b1ee57f8f34254a0413f4fc0.png) + +## Usage Example + +### Automatically generated API definition files + +![](/markdown/636aedc34da9bad1f84545dcfbeb38e6.png) + +### Automatically generated controller code files + +![](/markdown/cff8e2509fc89f6f4c4c0c82bb753334.png) + +![](/markdown/e2219959e53c38a80d37254cd3e9e9de.png) + +### Automatically generated `HTTP SDK` code files + +![](/markdown/f2f5c6793e4aef5ea3c2004ce67edf7b.png) + +![](/markdown/dd2dac2338ebf838ba317f64b32f5a5f.png) + +## FAQ + +### Why does each `api` interface generate a `controller` file instead of merging into one `controller` file? + +![](image2023-6-15_16-29-12.png) + +For small or personal simple projects, or projects with only a few interfaces in an `api` module, this management style might not be an issue, and code files can be maintained according to personal preference. Here, we describe the scenario for more complex business projects or enterprise-level projects where an `api` module has many interfaces. + +- First, when developing `api` interfaces, locating `api` interface implementation is clearer and not about searching through a thousand lines of code in a file. +- Moreover, in a collaborative project, if multiple people modify the same `controller` file, file conflicts easily arise in version management. Maintaining one `api` interface with a corresponding `controller` implementation file can minimize file conflict probabilities during collaboration, as most developers prefer not to waste valuable time resolving file conflicts again and again. +- Finally, the `controller` layer code has its own responsibilities: + - Validate input parameters: Client-submitted parameters are not trusted and usually require data validation in most scenarios. + - Implement the interface logic: Either directly implement the interface logic in the `controller` or call one or more `service` interfaces or third-party service interfaces to implement the logic. Avoid implementing `api` interface logic in `service` layers as `api` interfaces are tied to specific business scenarios and are not reusable. 💀 **A common mistake is directly passing the request through the `controller` to the `service` interface, making the `controller` seemingly redundant and the `service` layer's implementation more cumbersome and non-reusable.** 💀 + - Generate return data: Organize internal result data and generate the returning data API definition. + +- These responsibilities imply that `controller` code is also relatively complex. Maintaining them separately can alleviate developer mental burden and facilitate clear maintenance of `api` interface logic. + +**Some Suggestions**: + +If there are too many interface files in an `api` module, consider further splitting complex `api` modules into sub-modules. This can decouple complex `api` modules, and maintaining `api` API definitions and `controller` interface implementation files through multiple directories will make the structure clearer and facilitate better collaboration and version management. + +After reviewing the design, if you still prefer managing all interfaces using a single source file, consider the `merge` parameter. + +### Why is there an empty `go` file in the corresponding `controller` module generated from the `api` module? + +**For Example**: + +![](/markdown/a5b84cce8be1a8b3d563102e7a4c81dd.png) + +**Explanation**: + +Each `api` module generates an empty `go` file under the module's `controller`. This file is generated only once, allowing users to fill in necessary pre-defined code content, such as internal variable definitions, constants, data structures, or package initialization `init` method definitions, etc., within that module `controller`. _We advocate for good code management practices, suggesting module **pre-defined content** should be consolidated in the module-named `go` file (`module.go`) rather than scattered across different `go` files, to better maintain the code._ + +If there is currently no need for custom code content in the `controller`, just leave the file empty for future extensions. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" new file mode 100644 index 00000000000..741ad8d9d47 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" @@ -0,0 +1,104 @@ +--- +slug: '/docs/cli/gen-pbentity' +title: 'DB Table To Protobuf' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, Golang entity object, proto data structure, GRPC services, database configuration, GF command line tools, data table generation, entity file generation, pbentity, naming format] +description: "Use the GoFrame framework's command line tool gf to generate proto data structure files pbentity based on database tables. Includes details on command usage, option configuration, and explanations, as well as the differences from the entity files generated in the gen dao module. Applicable for generating data entity structures for HTTP and GRPC services, supporting configuration of generation rules for multiple databases." +--- + +:::tip +This feature is available starting from version `v2.4`. +::: +## Introduction + +This command is used to read the configured database and generate the corresponding `proto` data structure files based on the data tables. + +## Command Usage + +```text +$ gf gen pbentity -h +USAGE + gf gen pbentity [OPTION] + +OPTION + -p, --path directory path for generated files storing + -k, --package package path for all entity proto files + -l, --link database configuration, the same as the ORM configuration of GoFrame + -t, --tables generate models only for given tables, multiple table names separated with ',' + -f, --prefix add specified prefix for all entity names and entity proto files + -r, --removePrefix remove specified prefix of the table, multiple prefix separated with ',' + -rf, --removeFieldPrefix remove specified prefix of the field, multiple prefix separated with ',' + -n, --nameCase case for message attribute names, default is "Camel": + | Case | Example | + |---------------- |--------------------| + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | default + | Snake | any_kind_of_string | + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -j, --jsonCase case for message json tag, cases are the same as "nameCase", default "CamelLower". + set it to "none" to ignore json tag generating. + -o, --option extra protobuf options + -h, --help more information about this command + +EXAMPLE + gf gen pbentity + gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login + gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf + gf gen pbentity -r user_ + +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + path: "protocol/demos/entity" + tables: "order,products" + package: "demos" + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "protocol/demos/entity" + prefix: "primary_" + tables: "user, userDetail" + package: "demos" + option: | + option go_package = "protobuf/demos"; + option java_package = "protobuf/demos"; + option php_namespace = "protobuf/demos"; +``` +:::tip +If you are using the framework's recommended project scaffolding and have `make` installed, you can also use the `make pbentity` shortcut command. +::: +Parameter Description: + +| Name | Default Value | Meaning | Example | +| --- | --- | --- | --- | +| `gfcli.gen.pbentity` | | Code generation configuration items, which can contain multiple configuration items as an array, supporting multiple databases. Different databases can set different generation rules, such as generating to different locations or files. | - | +| `path` | `manifest/protobuf/pbentity` | The directory address for storing the generated `proto` files. | `protobuf/pbentity` | +| `package` | Automatically recognize `go.mod` | The `go_package` path in the generated `proto` file, and automatically recognizes the `package` name | - | +| `link` | | Consists of two parts, the first indicating the type of database you are connecting to such as `mysql`, `postgresql`, etc., and the second being the `dsn` information for connecting to the database. Refer to the chapter [ORM - Configuration](../../核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) for details. | - | +| `prefix` | | The prefix for generating database objects and files, to distinguish between different databases or identical table names in different databases, preventing table name conflicts. | `order_`
`user_` | +| `removePrefix` | | Removes the specified prefix from the table names. Multiple prefixes are separated by commas. | `gf_` | +| `removeFieldPrefix` | | Removes the specified prefix from field names. Multiple prefixes are separated by commas. | `f_` | +| `tables` | | Designates tables in the current database for code generation. If empty, all tables in the database will be generated. | `user, user_detail` | +| `nameCase` | `CamelLower` | The name format for generated `message` attribute fields. Optional parameters are: `Camel`, `CamelLower`, `Snake`, `SnakeScreaming`, `SnakeFirstUpper`, `Kebab`, `KebabScreaming`. Refer to the command line help example for details. | `Snake` | +| `option` | | Additional `proto option` configuration list. | | + +## Differences from `gen dao`'s `entity` + +### Similarities + +- Both generate `entity` content, i.e., creating corresponding `Golang` entity objects from data collections (database tables) for easier program use. Both are one-way generation, meaning they can only generate entity object code from data collections, ensuring synchronization of entity object data structures. +- The `entity` data entity objects generated by `gen dao` are primarily used for `HTTP` protocol services, although they are generic for the `Golang` language. In `HTTP` services, the `entity` generated in `gen dao`, although under the `internal` directory, will ultimately be part of the `HTTP API` response serving the client. + +### Differences + +- In `GRPC` services, the `entity` data structures generated by `gen dao` cannot be used by `GRPC` interfaces because `GRPC` data structures need to be defined using `proto` files. Therefore, in `GRPC` services, the `pbentity proto` files generated by `gen pbentity` are needed. Moreover, in `GRPC` microservice development, the `entity` generated by `gen dao` no longer has specific functions. +- The name `pbentity` is used instead of `entity` to avoid conflicts with the `entity` in `gen dao`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" new file mode 100644 index 00000000000..3a7e61c4d60 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" @@ -0,0 +1,227 @@ +--- +slug: '/docs/cli/gen-dao' +title: 'Dao/Do/Entity Generating' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,gen dao,data access object,data transformation model,Go code generation,configuration file management,command line tool,database connection,ORM configuration,project engineering specification] +description: "Usage and parameter configuration of the gen dao command in the GoFrame framework. The gen dao command is a key tool for generating data access objects, data transformation models, and entity data models, supporting detailed configuration through command line parameters and configuration files, applicable to various database types. By flexibly using command options, different project code generation requirements can be met, ensuring the implementation of engineering design specifications." +--- + +The `gen dao` command is the most frequently used command in the `CLI` and is crucial for the accurate implementation of the framework’s engineering specifications. This command is used to generate `dao` data access objects, `do` data transformation models, and `entity` instance data model `Go` code files. Due to the numerous parameters and options for this command, it is recommended to manage generation rules using configuration files. +:::tip +For an introduction to the framework's project engineering specifications, please see the section [Code Layering](../../框架设计/工程开发设计/代码分层设计.md). +::: +## Usage + +In most scenarios, execute `gf gen dao` in the project's root directory. Below is the command line help information. + +```text +$ gf gen dao -h +USAGE + gf gen dao [OPTION] + +OPTION + -p, --path directory path for generated files + -l, --link database configuration, the same as the ORM configuration of GoFrame + -t, --tables generate models only for given tables, multiple table names separated with ',' + -x, --tablesEx generate models excluding given tables, multiple table names separated with ',' + -g, --group specifying the configuration group name of database for generated ORM instance, + it's not necessary and the default value is "default" + -f, --prefix add prefix for all table of specified link/database tables + -r, --removePrefix remove specified prefix of the table, multiple prefix separated with ',' + -rf, --removeFieldPrefix remove specified prefix of the field, multiple prefix separated with ',' + -j, --jsonCase generated json tag case for model struct, cases are as follows: + | Case | Example | + |---------------- |--------------------| + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | default + | Snake | any_kind_of_string | + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -i, --importPrefix custom import prefix for generated go files + -d, --daoPath directory path for storing generated dao files under path + -o, --doPath directory path for storing generated do files under path + -e, --entityPath directory path for storing generated entity files under path + -t1, --tplDaoIndexPath template file path for dao index file + -t2, --tplDaoInternalPath template file path for dao internal file + -t3, --tplDaoDoPath template file path for dao do file + -t4, --tplDaoEntityPath template file path for dao entity file + -s, --stdTime use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables + -w, --withTime add created time for auto produced go files + -n, --gJsonSupport use gJsonSupport to use *gjson.Json instead of string for generated json fields of + tables + -v, --overwriteDao overwrite all dao files both inside/outside internal folder + -c, --descriptionTag add comment to description tag for each field + -k, --noJsonTag no json tag will be added for each field + -m, --noModelComment no model comment will be added for each field + -a, --clear delete all generated go files that do not exist in database + -y, --typeMapping custom local type mapping for generated struct attributes relevant to fields of table + -fm, --fieldMapping custom local type mapping for generated struct attributes relevant to specific fields of + table + -/--genItems + -h, --help more information about this command + +EXAMPLE + gf gen dao + gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + gf gen dao -p ./model -g user-center -t user,user_detail,user_login + gf gen dao -r user_ + +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "order,products" + jsonCase: "CamelLower" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "./my-app" + prefix: "primary_" + tables: "user, userDetail" + typeMapping: + decimal: + type: decimal.Decimal + import: github.com/shopspring/decimal + numeric: + type: string + fieldMapping: + table_name.field_name: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` +:::tip +If using the framework-recommended project engineering scaffold and the system has the `make` tool installed, the shortcut command `make dao` can also be used. +::: +## Configuration Example + +File configuration example: + +```yaml title="hack/config.yaml" +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "order,products" + jsonCase: "CamelLower" + + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "./my-app" + prefix: "primary_" + tables: "user, userDetail" + + # sqlite requires manual compilation of gf with sqlite driver, modify the import path file (gf\cmd\gf\internal\cmd\cmd_gen_dao.go) to uncomment for sqlite driver, gcc is required for sqlite driver + - link: "sqlite:./file.db" +``` + +## Parameter Description + +| Name | Default Value | Meaning | Example | +| --- | --- | --- | --- | +| `gfcli.gen.dao` | | `dao` code generation configuration items, can have multiple configuration items to form an array, supporting multiple database generation. Different databases can set different generation rules, for example, they can be generated to different locations or files. | - | +| `link`| | **Required Parameter**. It consists of two parts, the first part represents your connected database type `mysql`, `postgresql`, etc., while the second part is the `dsn` information for the connection. For specific details, please refer to the section [ORM - Configuration](../../核心组件/数据库ORM/ORM使用配置/ORM使用配置.md). | - | +| `path` | `internal` | The storage **directory** address for generated `dao` and `model` files. | `./app` | +| `group` | `default` | Database group name in the database configuration. Only one name can be configured. The group name in the database configuration file is often not modified once determined. | `default`
`order`
`user` | +| `prefix` | | Prefixes for the generated database objects and files to distinguish between different databases or the same table names in different databases, to prevent data table name overwriting. | `order_`
`user_` | +| `removePrefix` | | Removes specified prefix names from data tables. Multiple prefixes are separated by commas. | `gf_` | +| `removeFieldPrefix` | | Removes specified prefix names from field names. Multiple prefixes are separated by commas. | `f_` | +| `tables` | | Specifies which tables in the current database need code generation. If empty, all tables in the database will be generated. | `user, user_detail` | +| `tablesEx` | | `Tables Excluding`, specifies which tables in the current database are excluded from code generation. | `product, order` | +| `jsonCase` | `CamelLower` | Specifies the naming rules for `json` tags in generated data entity objects in `model`, the parameter is case-insensitive. Optional parameters: `Camel`, `CamelLower`, `Snake`, `SnakeScreaming`, `SnakeFirstUpper`, `Kebab`, `KebabScreaming`. For a detailed introduction, please refer to the command line help example. | `Snake` | +| `stdTime` | `false` | When a data table field type is a time type, the generated property type uses the standard library `time.Time` instead of the framework's `*gtime.Time` type. | `true` | +| `withTime` | `false` | Adds generation time comments to each automatically generated code file | | +| `gJsonSupport` | `false` | When a data table field type is `JSON`, the generated property type uses `*gjson.Json` type. | `true` | +| `overwriteDao` | `false` | Whether to regenerate and overwrite files outside the `dao/internal` directory for each `dao` code generation. Note that files outside the `dao/internal` directory may have been custom extended by the developer, and overwriting may introduce risk. | `true` | +| `importPrefix` | Automatically detected via `go.mod` | Used to specify the `import` path prefix for generated `Go` files. Especially necessary when the `gen dao` command is not used at the project's root directory or when intending to generate code files to a custom directory. | `github.com/gogf/gf` | +| `descriptionTag` | `false` | Specifies whether to add a `description` tag with the schema for each data model struct field, where the content is the corresponding data table field comment. | `true` | +| `noJsonTag` | `false` | The generated data model does not include json tags for fields | | +| `noModelComment` | `false` | Specifies whether to disable automatic generation of comments for model struct fields, where the content is derived from the data table field comments. | `true` | +| `clear` | `false` | Automatically deletes local `dao/do/entity` code files that do not correspond to any data tables in the database. Use this parameter with caution! | | +| `daoPath` | `dao` | Directory for storing generated `DAO` files | | +| `doPath` | `model/do` | Directory for storing generated `DO` files | | +| `entityPath` | `model/entity` | Directory for storing generated `Entity` files | | +| `tplDaoIndexPath` | | Custom `DAO Index` code generation template file path, please refer to the source code for use | | +| `tplDaoInternalPath` | | Custom `DAO Internal` code generation template file path, please refer to the source code for use | | +| `tplDaoDoPath` | | Custom `DO` code generation template file path, please refer to the source code for use | | +| `tplDaoEntityPath` | | Custom `Entity` code generation template file path, please refer to the source code for use | | +| `typeMapping` | | **Supported from version v2.5**. Used to customize the mapping of data table fields types to corresponding types in generated Go files. | | +| `fieldMapping` | | **Supported from version v2.8**. Used to customize the mapping of specific data table fields to corresponding field types in generated Go files.| | + +### Parameter: `typeMapping` + +The `typeMapping` parameter supports configuring the database field types corresponding to `Go` data types, and the default value is: +```yaml +decimal: + type: float64 +money: + type: float64 +numeric: + type: float64 +smallmoney: + type: float64 +``` +This configuration supports importing third-party packages via the `import` configuration item, for example: +```yaml +decimal: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` + +### Parameter: `fieldMapping` + +The `fieldMapping` parameter provides fine-grained field type mapping configuration, supporting the configuration of specific database fields' generated `Go` data types. Apart from a different configuration name, the configuration content is the same as `typeMapping`. Example configuration: +```yaml +paid_orders.amount: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` +In the example, `paid_orders` is the table name, `amount` is the field name, `type` represents the data type name in the generated `Go` code, and `import` represents third-party packages that need to be imported in the generated code. + +## Usage Example + +Repository address: [https://github.com/gogf/focus-single](https://github.com/gogf/focus-single) + +![](/markdown/a02af38b70bb31224361565570e40789.png) + +1. Files in the following `3` directories are generated by the `dao` command: + +| Path | Description | Detailed Introduction | +| --- | --- | --- | +| `/internal/dao` | Data Access Object | Accesses the underlying data source through the object-oriented way, based on the `ORM` component. Often used in combination with `entity` and `do`. Files in this directory can be extended and modified by developers. | +| `/internal/model/do` | Data Transformation Model | The data transformation model is used for converting business models to data models, maintained by tools, and should not be modified by users. The tool will overwrite this directory each time code files are generated. For an introduction to `do` files, please refer to:
- [Data and Business Models](../../框架设计/工程开发设计/数据模型与业务模型.md)
- [Pain Points and Improvements In Business Project](../../框架设计/工程开发设计/DAO封装设计/DAO-工程痛点及改进.md)
- [Utilizing Pointer Properties and Do Objects for Flexible Modification Interfaces](../../核心组件/数据库ORM/ORM最佳实践/利用指针属性和do对象实现灵活的修改接口.md) | +| `/internal/model/entity` | Data Model | The data model is maintained by tools and should not be modified by users. The tool will overwrite this directory each time code files are generated. | + +2. Models in the `model` directory are divided into two categories: **Data Models** and **Business Models**. + +**Data Models:** Automatically generated by the `CLI` tool in the `model/entity` directory. The database tables will all be generated in this directory, and the files in this directory correspond to data models. Data models are data structures that correspond one-to-one with database tables; developers usually do not need to modify them and should not, as data models are automatically updated by the `CLI` tool when there are changes to the database table structures. Data models are generated and maintained uniformly by the `CLI` tool. + +**Business Models:** Business models are data structures related to the business, defined as needed, such as service input and output data structure definitions and some internal data structure definitions. Business models are defined and maintained by developers according to business needs, defined in the `model` directory. + +3. Files in the `dao` folder are named after database table names, with one file and its corresponding `DAO` object for each database table. Operations on database tables are implemented through `DAO` objects and related methods. The `dao` operation adopts a standardized design, requiring the `ctx` parameter to be passed, and the generated code must be operated by creating objects through the `Ctx` or `Transaction` method to chain operation on database tables. + +![](/markdown/f0da330685c6cfd82ba1c0254dfdbe39.png) + +## Notes + +### Databases Requiring Manual Compilation + +The `gen dao` command involves data access-related code generation and supports several commonly used database types by default. If Oracle database support is needed, developers must modify the source code files and manually compile and install the `CLI` tool locally, as the driver for these databases require `CGO` support and cannot be precompiled for general use. + +![](/markdown/7f849959c13d224393b93d6b371e8ae0.png) + +### About `bool` Type Corresponding to Database Table Fields + +Since most database types do not have `bool` type table field types, +we recommend using `bit(1)` to represent a field’s `bool` type instead of `tinyint(1)` or `int(1)`. Because the `tinyint(1)/int(1)` field type represents a range of `-127~127`, it is usually used as a status field type. While the `bit(1)` type range is `0/1`, which can accurately represent the `bool` type's two values `false/true`. + +For example, table fields: + +![](/markdown/50992d00a792555d2946d294975e9ec4.png) + +Generated properties: + +![](/markdown/4bb766d64e607a33c1a6fbf20c742924.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" new file mode 100644 index 00000000000..5bb7be2f14c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" @@ -0,0 +1,97 @@ +--- +slug: '/docs/cli/gen-enums' +title: 'Enums Maintenance' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, Enum maintenance, OpenAPIv3, API documentation, Enum values, Source code analysis, Command line tool, Development efficiency, Data validation] +description: "Use GoFrame command line tool to maintain and generate enum value information, especially for enum parameters in OpenAPIv3 documents. Automatically generate and load enum values by parsing source code to reduce manual maintenance costs, improve development efficiency, and enhance collaboration between backend and frontend." +--- +:::tip +This feature is an **experimental feature**, available from version `v2.4`. +::: +## Introduction + +This command is used to analyze the source code in the specified code directory and generate enum values and `Go` code files according to specifications, **mainly to improve the maintenance of enum values in `OpenAPIv3` documents**. + +## Addressed Pain Points + +### Pain Points Description + +- The issue of not displaying the optional items of enum values in the `API` documentation. +- The difficulty in maintaining enum values in the `API` documentation, and the issue of code and documentation being maintained separately. This reduces the collaboration efficiency with the caller, especially between frontend and backend. + +> For example, in the following API definition, tasks have multiple states which are enum values. It's costly for the backend to maintain and easy to miss states maintenance, causing incomplete state enum values. + +![](/markdown/3e2d58612c094dcf26ed2f17371ae482.png) + +### Pain Points Resolution + +The tool parses source code and generates enum values into the startup package `Go` file, automatically loading enum values when the service runs, reducing manual maintenance costs and avoiding omitted enum value maintenance. + +> For example, in the following API definition, using the tool to maintain enum values improves development efficiency. + +![](/markdown/4f5b0d82a3fa65b8c83fcd3f93a8c02a.png) + +## Command Usage + +```text +$ gf gen enums -h +USAGE + gf gen enums [OPTION] + +OPTION + -s, --src source folder path to be parsed + -p, --path output go file path storing enums content + -x, --prefixes only exports packages that starts with specified prefixes + -h, --help more information about this command + +EXAMPLE + gf gen enums + gf gen enums -p internal/boot/boot_enums.go + gf gen enums -p internal/boot/boot_enums.go -s . + gf gen enums -x github.com/gogf +``` + +Parameter Description: + +| Name | Required | Default | Description | +| --- | --- | --- | --- | +| `src` | No | `.` | Specify the source code directory path for analysis, default is the current project root directory | +| `path` | No | `internal/boot/boot_enums.go` | Specify the path for the generated enum registration Go code file | +| `prefixes` | No | - | Only generate enum values for package names with specified prefixes, supports multiple prefix configurations | + +:::info +This command uses `AST` parsing to recursively analyze `enums` definitions for all dependency packages. If the business project dependencies are complex, the generated `enums` may be many, but most of the time we only care about the `enums` definitions of our own project or specific dependency packages, hence the `prefixes` parameter can be used to control the generation of `enums` for only specific package name prefixes. +::: + +Example of the tool configuration item in `yaml` format: +```yaml title="hack/config.yaml" +gfcli: + gen: + enums: + src: "api" + path: "internal/boot/boot_enums.go" + prefixes: + - github.com/gogf + - myexample/project +``` + +## Usage of the Generated File + +Execute the `gf gen enums` command to generate the enum analysis file `internal/boot/boot_enums.go`. After generating the file, it needs to be anonymously imported in the project's entry file: + +```go +import ( + _ "project_module_name/internal/boot" +) +``` + +## Further Reading + +### How to Standardize Enum Values + +Please refer to the section: [Golang Enums](../../框架设计/Golang枚举值管理.md) + +### How to Validate Enum Values + +If enum values are standardized and files are generated by command for enum value maintenance, then in parameter validation, the `enums` rule can be used to validate enum value fields. For specific rule introduction, please refer to the section: [Data Validation - Rules](../../核心组件/数据校验/数据校验-校验规则.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" new file mode 100644 index 00000000000..b4343836f29 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" @@ -0,0 +1,189 @@ +--- +slug: '/docs/cli/gen-service' +title: 'Service Generating' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,Golang,Business Logic,Module Management,API Definition,Code Generation,Circular Dependencies,Service Registration,Compiler,Microservices] +description: "Encapsulate and manage business logic in the GoFrame framework by generating module API definitions and registration code to simplify the implementation process of separating business logic from interfaces. Improve module transparency through structured coding of logic and interfaces while avoiding circular dependencies. Provides both manual and automated implementation modes suitable for different development environments." +--- +:::warning +This feature is an **experimental feature**. It is recommended for developers to focus on module division under `logic`, streamline inter-module relationship, avoid circular dependencies, and fully utilize the circular dependency detection feature of the `Golang` compiler to write higher quality project code. +::: +:::tip +This feature is available from version `v2.1`. +::: + +## Introduction + +### Design Background + +In business project practices, business logic encapsulation is often the most complex part. Meanwhile, dependencies between business modules are very complex and boundaries are vague, making it difficult to use the `Golang` package management form. Effectively managing the encapsulation part of business logic in a project is a challenge that every project developed with `Golang` is bound to encounter. + +In a standard software design process, dependencies between modules first clarify API definitions, and then the implementation is done through code during the software development process. However, in most high-paced internet engineering, there isn't a rigorous software design process, and the quality level of developers varies. Most developers first focus on how to implement the functional logic corresponding to the requirements scenario to maximize development efficiency. + +### Design Objectives + +1. Provide a code management method that can directly generate module API definitions and module registration code through specific module implementation. +2. Simplify the implementation of separating business logic and interfaces, reduce the repetitive operation of module methods and API definitions, and enhance module transparency and ease of invocation between modules. + +### Design Implementation + +1. Add a `logic` category directory, migrate all business logic code to the `logic` category directory, and manage business modules in the form of package management. +2. Dependencies between business modules are decoupled through interfaces, and the original `service` category is adjusted to an interface directory. Each business module will maintain its own, being more flexible. +3. Based on certain coding specifications, `service` API definition code can be generated from the `logic` business logic code. It also allows for manual maintenance of this part of the `service` interface. + +## Things to Note +:::warning +Again, implementing `service` interfaces through `logic` is **not a standardized code management practice** but provides another **optional**, convenient code management method. This management method has both advantages and disadvantages. The advantage is that it is more convenient for business module interface auto-generation in microservice scenarios; the disadvantage is that it cannot recognize syntax inheritance relationships, cannot generate parent nested type methods, and disregards the `Golang` compile-time detection of circular dependencies. +::: +**The framework's project management also supports the standard interface code management approach**, i.e., defining `service` interfaces first, then coding `logic` for concrete implementation. Note that the `service` source code must not contain top comment information used by the tool (the tool uses this comment to determine if the file can be overwritten 😈). Many colleagues kept the comment at the top when copying and pasting files, rendering manual interface file maintenance invalid. See screenshot annotation: + +![](/markdown/f4b70fc856dfcb17c4680839e32bb78b.png) + +## Command Usage + +This command analyzes the code under the given `logic` business logic module directory to automatically generate `service` directory interface code. +:::info +Please note: + +1. Since this command generates `service` interfaces based on business modules, it only parses `go` code files in secondary directories and does not recursively analyze code files indefinitely. Taking `logic` directory as an example, this command only parses `logic/xxx/*.go` files. Thus, the `logic` layer code structure needs to meet specific specifications. +2. Struct names defined in different business modules may overlap when generating `service` interface names, so it is necessary to ensure that names do not conflict when designing business modules. +::: +For a sample project using this command, please refer to: [https://github.com/gogf/gf-demo-user](https://github.com/gogf/gf-demo-user) + +### Manual Mode + +If executing the command manually, simply execute `gf gen service` in the project root directory. + +```text +$ gf gen service -h +USAGE + gf gen service [OPTION] + +OPTION + -s, --srcFolder source folder path to be parsed. default: internal/logic + -d, --dstFolder destination folder path storing automatically generated go files. default: internal/service + -f, --dstFileNameCase destination file name storing automatically generated go files, cases are as follows: + | Case | Example | + |---------------- |--------------------| + | Lower | anykindofstring | + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | + | Snake | any_kind_of_string | default + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -w, --watchFile used in file watcher, it re-generates all service go files only if given file is under + srcFolder + -a, --stPattern regular expression matching struct name for generating service. default: ^s([A-Z]\w+)$ + -p, --packages produce go files only for given source packages + -i, --importPrefix custom import prefix to calculate import path for generated importing go file of logic + -l, --clear delete all generated go files that are not used any further + -h, --help more information about this command + +EXAMPLE + gf gen service + gf gen service -f Snake +``` +:::tip +If using the framework's recommended project scaffold and the `make` tool is installed on the system, the `make service` shortcut command can also be used. +::: +Parameter Description: + +| Name | Required | Default | Meaning | +| --- | --- | --- | --- | +| `srcFolder` | Yes | `internal/logic` | Points to the logic code directory address | +| `dstFolder` | Yes | `internal/service` | Points to the directory where the generated interface files are stored | +| `dstFileNameCase` | No | `Snake` | The format of the generated filename | +| `stPattern` | No | `s([A-A]\w+)` | Use regular expression to specify the business module struct definition format for easy parsing of business API definition names. Under the default regex, all structs starting with lowercase `s` followed by uppercase letters will be treated as business module interface names. For example: + +| Logic Struct Name | Service Interface Name | +| --- | --- | +| `sUser` | `User` | +| `sMetaData` | `MetaData` | | +| `watchFile` | | | Used in code file watching, represents the current changing code file path | +| `packages` | | | Only generate interface files for specified package names, given as a string array, JSON string when passed through command line, automatically converted by the command line component | +| `importPrefix` | | | Specifies the reference package name prefix in the generated business reference file | +| `overwrite` | | `true` | Whether to overwrite existing files when generating code files | +| `clear` | | `false` | Automatically deletes interface files that do not exist in `logic` (only deletes automatically maintained files) | + +### Automatic Mode + +#### `Goland/Idea` + +If you are using `GolandIDE`, you can use the configuration file we provide: [watchers.xml](gen-service-watchers.xml) to automatically monitor code file modifications and automatically generate interface files. Usage is shown in the picture below: + +![](/markdown/447830160c7c3f14c1ce09b34906047f.png) + +#### `Visual Studio Code` + +If you are using `Visual Studio Code`, you can install the plugin [RunOnSave](https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave) and then configure the plugin: + +```json +"emeraldwalk.runonsave": { + "commands": [ + { + "match": ".*logic.*go", + "isAsync": true, + "cmd": "gf gen service" + } + ] +} +``` + +## Specific Usage: Step by Step + +### Step1: Include the Configuration We Provide + +We recommend using the configuration file: [watchers.xml](gen-service-watchers.xml) when using `Goland IDE`. + +### Step2: Write Your Business Logic Code + +![](/markdown/84a59977f8a236410b20573a9377ed9b.png) + +### Step3: Generate Interface and Service Registration Files + +If you have done the configuration in `Step1`, you can ignore this step. Because when you write code, `service` will generate the API definition file at the same time. + +Otherwise, every time you finish developing/updating the `logic` business module, you need to manually execute the `gf gen service` command. + +![](/markdown/8f5ee2dc2c553ee9dd169930ff50003d.png) + +### Step4: Pay Attention to the Service Implementation Injection Part (Only Once) + +Only after the interface files are generated can you add the specific implementation injection of the interface in each business module. This method is added once per business module. + +![](/markdown/aebae0b3b3055119b3818da0515e0c28.png) + +### Step5: Reference Interface Implementation Registration in the Startup File (Only Once) + +It can be noticed that besides generating interface files, this command also generates an interface implementation registration file. This file is used to register the specific implementation of the interface during program startup. + +![](/markdown/ceddac49d9a4585f334902157d542e0d.png) + +The introduction of this file needs to be at the top of the `main` package import, and note the order of `import`, placing it at the top with a blank line behind. If the `packed` package is also imported, place it after the `packed` package. Like this: + +![](/markdown/864c4ad138cca78ac03d7e2d3fbf7a02.png) + +### Step6: Start & Enjoy + +Simply start `main.go`. + +## Common Issues FAQ + +### When a structure in `logic` has nested types, methods for the nested types cannot be automatically generated + +In such cases, it is recommended to manually maintain the `service` API definitions and not use the automatic generation tool. The manually maintained API definition files will not be overwritten by the tool, and both manual and automatic methods can be used together. + +### Quickly Locate Specific Implementations of Interfaces + +**After adopting interface-based decoupling for project business modules, the experience is fantastic! But if I want to quickly find the specific implementations of specified interfaces during development and debugging, it's a bit challenging. Any guiding thoughts?** + +\> Here, I recommend using `Goland IDE`, which has an excellent interface implementation locating feature, as shown in the figure. After finding the API definition, click the small icon on the left to quickly locate the specific implementations. If Goland doesn't show the small icon, try upgrading to the latest version of `Goland`. + +![](/markdown/bbcc72eb46954b60c49be42a8ecebe35.png) + +Alternatively, if there is no small icon on the left, you can right-click to select `Go To → Implementation(s)`. + +![](/markdown/4168ae8d0afee067e885e603eda37ccf.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" new file mode 100644 index 00000000000..aa8df99d80e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" @@ -0,0 +1,32 @@ +--- +slug: '/docs/cli/fix' +title: 'Compatibility Fix' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, GoFrame framework, compatibility fix, CLI usage, code update, version upgrade, backward compatibility, gf fix, command line tool, automatic correction] +description: "The compatibility fix command gf fix provided by the GoFrame framework helps solve backward compatibility issues during framework upgrades. This command has been available since version v2.3, and automatically updates local code to handle minor compatibility issues. It can be executed repeatedly to ensure no side effects." +--- +:::tip +This command has been available since framework version `v2.3`. +::: +## Usage Scenarios + +During the upgrade of official framework versions, every effort is made to ensure backward compatibility. However, in exceptionally difficult scenarios where complete backward compatibility cannot be ensured and the issues are minor, considering the high cost of adding a major version number, the official team will provide this command to automatically fix compatibility issues. The official team also guarantees that this command can be executed repeatedly without side effects. + +## Usage + +```text +$ gf fix -h +USAGE + gf fix + +OPTION + -/--path directory path, it uses current working directory in default + -h, --help more information about this command +``` + +Used to automatically update local code incompatible changes when upgrading from a lower version (the current `GoFrame` version in `go.mod`) to a higher version (the current `GoFrame` version used by `CLI`). + +## Precautions + +Please use `git` to commit local changes or back up the directory before executing the command. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" new file mode 100644 index 00000000000..9213a28181a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" @@ -0,0 +1,89 @@ +--- +slug: '/docs/cli/install' +title: 'Installation' +sidebar_position: 0 +hide_title: true +keywords: [Tool Installation,GoFrame,GoFrame Framework,gf Tool,Command Line Tool,Precompiled Binary,System Environment Variable,MacOS,Windows Installation,go install] +description: "Install GoFrame tools on different operating systems, including installation methods for MacOS and Windows. Provides download links for precompiled binary files and methods for installation via the go install command, ensuring the gf tool is correctly installed and used in system environment variables." +--- + +This command is only for downloading and installing precompiled binaries. If the tools are installed via the `go install` command, there is no need to manually use the `install` command to install the `gf` tool. + +## Download and Install + +### Download the Latest Version + +#### `Mac`&`Linux` Quick Download Command + +```bash +wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf +``` + +#### Windows Requires Manual Download + +Determine the current project's `goframe` dependency version, check your system information: + +```bash +go env GOOS +go env GOARCH +``` + +Download link: [releases](https://github.com/gogf/gf/releases) + +### Install via `go install` + +Note: Add `$GOPATH/bin` to the system environment variables, check with `go env GOPATH`. + +#### Latest version + +```bash +go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +#### Specify version (version needs to be >= v2.5.5) + +```bash +go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 +``` + +### Download Other Versions + +#### v2 Version + +Precompiled binary download: [releases](https://github.com/gogf/gf/releases) + +Source code: [gf/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +#### v1 Version + +Precompiled binary download: [releases](https://github.com/gogf/gf-cli/releases) + +Source code: [gogf/gf-cli](https://github.com/gogf/gf-cli) + +## Usage + +Project address: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +Usage: `./gf install` + +This command is usually executed after the `gf` command line tool is downloaded locally (note execution permissions), to install the `gf` command into directories supported by the default system environment variables, so that the `gf` tool can be used anywhere in the system. + +:::note +Some systems require administrator permissions. + +If users under `MacOS` using `zsh` encounter alias conflicts, it can be resolved by `alias gf=gf`. After running once, the `gf` tool will automatically modify the alias settings in the profile, and users can just re-login (or reopen the terminal). +::: + +## Usage Example + +```bash +$ ./gf_darwin_amd64 install +I found some installable paths for you(from $PATH): + Id | Writable | Installed | Path + 0 | true | true | /usr/local/bin + 1 | true | false | /Users/john/Workspace/Go/GOPATH/bin + 2 | true | false | /Users/john/.gvm/bin + 4 | true | false | /Users/john/.ft +please choose one installation destination [default 0]: +gf binary is successfully installed to: /usr/local/bin +``` diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" new file mode 100644 index 00000000000..c1b597382e5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/cli/help' +title: 'Help Info' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame Framework,CLI Tool,gf Command,Help Document,Command Line Tool,Software Help,Tool Usage,Sidebar Position] +description: "Use the help command of the CLI tool of the GoFrame framework to get help information by entering gf -h or gf [COMMAND] -h. If you encounter any problems during use, you can always use the help command to query relevant assistance. Here, you can also learn about the specific information related to the sidebar position." +--- + +Usage: + +- `gf -h` +- `gf [COMMAND] -h` + +If you don't understand anything, just use `help` to take a look. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" new file mode 100644 index 00000000000..8dd66bec8f0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/cli' +title: 'CLI Tool' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gf Command Line Tool, Development Tools, CLI Tools, Framework Design Specifications, Command Line Parameters, Configuration Files, Debug Mode, Code Generation] +description: "Detailed information about the gf command line development tool provided by the GoFrame framework, covering tool responsibilities, considerations, configuration support, tool debugging, and a command overview, among other aspects. The gf tool aims to simplify project development and increase efficiency, and supports parameter configuration through command line and configuration file methods to enhance tool usability." +--- + +The `GoFrame` framework provides a powerful `gf` command line development assistance tool, which is an important part of the framework's development. Tool link: + +- [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +Please refer to the repository page for tool installation. Once the tool is successfully installed, you can view all supported commands with `gf` or `gf -h`. For complex commands, you can view more detailed usage information with `gf COMMAND -h`, for example: `gf gen -h`. + +## Tool Responsibilities + +1. Simplify project development and improve development efficiency. +2. Support the accurate implementation of framework project design specifications. + +## Considerations + +1. Some commands require you to have a basic development environment for `Golang` installed. Please refer to the chapter [Installation](../其他资料/准备开发环境/环境安装.md) for details on environment installation. +2. The latest version of the `CLI` tool will follow the latest version of the framework. + +## Configuration Support + +**All commands of the tool support configuration parameters through both command line and configuration file simultaneously to improve usability. Command line parameters take precedence; if they are not provided, the tool automatically reads the corresponding parameter names from the configuration file.** + +The configuration file path prioritizes the `hack` directory in the current directory (`hack/config.yaml`), and then the framework's default configuration path. For the framework's default configuration file retrieval path, please refer to the chapter: [Configuration - File](../核心组件/配置管理/配置管理-文件配置.md). + +Example configuration file format: + +```yaml +# GoFrame CLI tool configuration. +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "user" + removePrefix: "gf_" + descriptionTag: true + noModelComment: true + + docker: + build: "-a amd64 -s linux -p temp" + tagPrefixes: + - ccr.ccs.tencentyun.com/xxx + - hkccr.ccs.tencentyun.com/xxx + - sgccr.ccs.tencentyun.com/xxx +``` + +Note that the above configuration example is for reference only. Please refer to specific command help for specific configuration items. + +## Tool Debugging + +When encountering issues during the tool's usage, you can try enabling the tool's debug mode to obtain more detailed execution log information. The debug mode can be enabled with the `debug` command line option, for example: + +```bash +gf build main.go --debug +``` + +Since the `gf` tool is also developed using the `GoFrame` framework, the enabling of debug information is consistent with the framework method. For a more detailed introduction, please refer to the framework introduction document: [Debug Mode](../核心组件/调试模式.md) + +## Command Overview + +This help document uses `gf cli v2.0.0` as an example for a brief introduction. For detailed introduction information, please refer to the command line help. The information in this chapter may be delayed, so please refer to the tool help for the latest specific introductions. + +```text +$ gf +USAGE + gf COMMAND [OPTION] + +COMMAND + env show current Golang environment variables + run running go codes with hot-compiled-like feature + gen automatically generate go files for dao/dto/entity/pb/pbentity... + init create and initialize an empty GoFrame project + pack packing any file/directory to a resource file, or a go file + build cross-building go project for lots of platforms + docker build docker image for current GoFrame project + install install gf binary to system (might need root/admin permission) + version show version information of current binary + +OPTION + -y, --yes all yes for all command without prompt ask + -v, --version show version information of current binary + -d, --debug show internal detailed debugging information + -h, --help more information about this command + +ADDITIONAL + Use "gf COMMAND -h" for details about a command. +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" new file mode 100644 index 00000000000..d8cdcb0bd87 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/cli/up' +title: 'Version Upgrade' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, CLI Tools, Version Update, Auto Fix, Incompatible Changes, Main Framework, Community Components, Command Line Options, Code Upgrade] +description: "Use the GoFrame framework's gf up command for version upgrades. The gf up command can update both the main framework and the CLI tool versions and automatically fix incompatible code changes during the upgrade process. This article provides detailed usage, command options, and examples to help users complete the upgrade process safely and efficiently." +--- +:::tip +This command is available starting from framework version `v2.3`. +::: +## Usage + +```text +$ gf up -h +USAGE + gf up [OPTION] + +OPTION + -a, --all upgrade both version and cli, auto fix codes + -c, --cli also upgrade CLI tool + -f, --fix auto fix codes(it only make sense if cli is to be upgraded) + -h, --help more information about this command + +EXAMPLE + gf up + gf up -a + gf up -c + gf up -cf +``` + +It is used to achieve version updates, simultaneously updating the main framework version and community components to the latest version. + +Option explanation: + +| Name | Meaning | +| --- | --- | +| `all` | Simultaneously update the `cli` tool version and automatically fix local code incompatibility changes during the upgrade | +| `fix` | Automatically fix local code incompatibility changes during the upgrade | +| `cli` | Also update the `cli` tool version during the upgrade | + +## Usage Example + +```text +$ gf up -a +start upgrading version... +upgrading "github.com/gogf/gf/contrib/drivers/mysql/v2" from "v2.2.4" to "latest" +go: upgraded github.com/BurntSushi/toml v1.1.0 => v1.2.1 +go: upgraded github.com/cespare/xxhash/v2 v2.1.2 => v2.2.0 +go: upgraded github.com/clbanning/mxj/v2 v2.5.6 => v2.5.7 +go: upgraded github.com/fsnotify/fsnotify v1.5.4 => v1.6.0 +go: upgraded github.com/go-sql-driver/mysql v1.6.0 => v1.7.0 +go: upgraded github.com/gogf/gf/contrib/drivers/mysql/v2 v2.2.4 => v2.2.6 +go: upgraded github.com/gogf/gf/v2 v2.2.4 => v2.2.6 +go: upgraded github.com/magiconair/properties v1.8.6 => v1.8.7 +go: upgraded github.com/mattn/go-colorable v0.1.12 => v0.1.13 +go: upgraded github.com/mattn/go-isatty v0.0.14 => v0.0.17 +go: upgraded github.com/mattn/go-runewidth v0.0.13 => v0.0.14 +go: upgraded github.com/rivo/uniseg v0.2.0 => v0.4.3 +go: upgraded go.opentelemetry.io/otel v1.7.0 => v1.11.2 +go: upgraded go.opentelemetry.io/otel/sdk v1.7.0 => v1.11.2 +go: upgraded golang.org/x/net v0.0.0-20220621193019-9d032be2e588 => v0.5.0 +go: upgraded golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c => v0.4.0 +go: upgraded golang.org/x/text v0.3.8-0.20220509174342-b4bca84b0361 => v0.6.0 +go: upgraded golang.org/x/tools v0.1.11-0.20220504162446-54c7ba520b92 => v0.1.12 + +upgrading "github.com/gogf/gf/v2" from "v2.2.4" to "latest" + +auto fixing path "/Users/john/Workspace/Go/GOPATH/src/github.com/Khaos/eros"... +done! +``` + +## Precautions + +Please commit local modifications or back up the directory with `git` before running the command. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" new file mode 100644 index 00000000000..de3dc2d73c5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/cli/version' +title: 'Version Check' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, gf command line, version check, gf version, CLI tool, Golang version, technical documentation, code compilation, environment configuration] +description: "Use GoFrame command line tool to check version information, including usage of gf -v and gf version. The content covers examples of different version uses, showing specific version information of GoFrame in projects, explaining CLI compilation details and notes, to help users accurately understand the relationship between GoFrame version and Golang and related technologies." +--- + +## Usage + +- `gf -v` +- `gf version` + +Used to check the version information compiled at the current `gf` command line tool. + +## Usage Example + +### `>= v2.5.7` + +```text +$ gf version +v2.7.2 +Welcome to GoFrame! +Env Detail: + Go Version: go1.22.2 linux/amd64 + GF Version(go.mod): + github.com/gogf/gf/contrib/drivers/mysql/v2@v2.7.2 + github.com/gogf/gf/v2@v2.7.2 +CLI Detail: + Installed At: /data/home/v_hlaghuang/go/bin/gf + Built Go Version: go1.20.8 + Built GF Version: v2.7.2 + Git Commit: 2024-06-26 10:08:04 b11caba5b03ed54fbb1415151f7d62b6d913179d + Built Time: 2024-06-26 10:09:50 +Others Detail: + Docs: https://goframe.org + Now : 2024-07-17T15:48:57+08:00 +``` + +### `< v2.5.6` + +```text +$ gf version +GoFrame CLI Tool v2.0.0, https://goframe.org +GoFrame Version: v2.0.0-beta.0.20211214160159-19c9f0a48845 in current go.mod +CLI Installed At: /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-cli/main +CLI Built Detail: + Go Version: go1.16.3 + GF Version: v2.0.0-beta + Git Commit: 2021-12-15 22:43:12 7884058b5df346d34ebab035224e415afb556c19 + Build Time: 2021-12-15 23:00:43 +``` + +## Notes + +The version information printed automatically detects the `GoFrame` version used by the current project (automatically parses `go.mod`) and prints it as `GoFrame Version`. + +The `CLI Built Detail` information displays various `Golang` version and `GoFrame` version information used at the time of the current binary compilation, the `Git` commit version at the time of compilation, and the compilation time of the current binary file. +:::warning +Please do not confuse the `GoFrame Version` with the `GF Version` in the `CLI Built Detail`. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" new file mode 100644 index 00000000000..500d6dd3825 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" @@ -0,0 +1,95 @@ +--- +slug: '/docs/cli/run' +title: 'Auto Compiling' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, auto compile, hot compile feature, go file auto monitor, command line arguments, compile run, binary files, file path monitoring, GoFrame framework, gf run command] +description: "When building a project with the GoFrame framework, learn how to achieve automatic compilation through the gf run command. Although Go language does not inherently support hot compilation features, the gf run command can automatically compile and run a new version of the program when go files in the project change, aiming to improve development efficiency." +--- + +## Precautions + +Since `Go` does not support hot compilation features, every code change requires manually stopping, compiling, and running the code files. The `run` command does not implement hot compilation but provides an automatic compilation feature. When developers modify the `go` files in the project, this command will automatically compile the current program, stop the existing one, and run the new version. +:::tip +The `run` command will recursively monitor all `go` file changes in the **current working directory** to implement automatic compilation. +::: +## Usage Help + +```text +$ gf run -h +USAGE + gf run FILE [OPTION] + +ARGUMENT + FILE building file path. + +OPTION + -p, --path output directory path for built binary file. it's "./" in default + -e, --extra the same options as "go run"/"go build" except some options as follows defined + -a, --args custom arguments for your process + -w, --watchPaths watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml" + -h, --help more information about this command + +EXAMPLE + gf run main.go + gf run main.go --args "server -p 8080" + gf run main.go -mod=vendor + gf run main.go -w "manifest/config/*.yaml" + +DESCRIPTION + The "run" command is used for running go codes with hot-compiled-like feature, + which compiles and runs the go codes asynchronously when codes change. + +``` + +Example of configuration file format: + +```yaml +gfcli: + run: + path: "./bin" + extra: "" + args: "all" + watchPaths: + - api/*.go + - internal/controller/*.go +``` + +Parameter introduction: + +| Name | Default | Meaning | Example | +| --- | --- | --- | --- | +| `path` | `./` | Specifies the directory to store the compiled binary files. | | +| `extra` | | Specifies the command arguments for underlying `go build` | | +| `args` | | Specifies the command line arguments for running the binary files | | +| `watchPath` | | Specifies the file path format for local project file monitoring. Multiple paths can be separated by `,`. This parameter's format is the same as the `filepath.Match` method parameter in the standard library | `internal/*.go` | + +## Usage Example + +Generally, `gf run main.go` is sufficient + +```text +$ gf run main.go --swagger +2020-12-31 00:40:16.948 build: main.go +2020-12-31 00:40:16.994 producing swagger files... +2020-12-31 00:40:17.145 done! +2020-12-31 00:40:17.216 gf pack swagger packed/swagger.go -n packed -y +2020-12-31 00:40:17.279 done! +2020-12-31 00:40:17.282 go build -o bin/main main.go +2020-12-31 00:40:18.696 go file changes: "/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-demos/packed/swagger.go": WRITE +2020-12-31 00:40:18.696 build: main.go +2020-12-31 00:40:18.775 producing swagger files... +2020-12-31 00:40:18.911 done! +2020-12-31 00:40:19.045 gf pack swagger packed/swagger.go -n packed -y +2020-12-31 00:40:19.136 done! +2020-12-31 00:40:19.144 go build -o bin/main main.go +2020-12-31 00:40:21.367 bin/main +2020-12-31 00:40:21.372 build running pid: 40954 +2020-12-31 00:40:21.437 [DEBU] [ghttp] SetServerRoot path: /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-demos/public +2020-12-31 00:40:21.440 40954: http server started listening on [:8199] +... +``` + +## Common Issues + +[too many open files on macOS](https://github.com/fsnotify/fsnotify/issues/129) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" new file mode 100644 index 00000000000..774ac8be6e8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" @@ -0,0 +1,51 @@ +--- +slug: '/docs/cli/pack' +title: 'Resource Packing' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, GoFrame framework, resource packaging, CLI tools, gf pack, resource files, go code files, file packaging, command line tools, source code management] +description: "This document introduces how to use the gf pack command in the GoFrame framework to package any file into a resource file or Go code file. Through this tool, users can achieve resource packaging and distribute it along with the executable file. Additionally, the gf pack command can be combined with the build command to achieve packaging and compiling in one step. The document provides a detailed list of usage methods and option explanations to help users better understand and use this feature." +--- + +## Usage + +```text +$ gf pack -h +USAGE + gf pack SRC DST + +ARGUMENT + SRC source path for packing, which can be multiple source paths. + DST destination file path for packed file. if extension of the filename is ".go" and "-n" option is given, + it enables packing SRC to go file, or else it packs SRC into a binary file. + +OPTION + -n, --name package name for output go file, it's set as its directory name if no name passed + -p, --prefix prefix for each file packed into the resource file + -k, --keepPath keep the source path from system to resource file, usually for relative path + -h, --help more information about this command + +EXAMPLE + gf pack public data.bin + gf pack public,template data.bin + gf pack public,template packed/data.go + gf pack public,template,config packed/data.go + gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app + gf pack /var/www/public packed/data.go -n=packed +``` + +This command is used to package any file into a resource file or a `Go` code file, allowing any file to be packaged and distributed along with the executable file. Additionally, the `build` command supports packaging and compiling in one step; please refer to the `build` command help for details. For an introduction to resource management, please refer to the [Resource](../核心组件/资源管理/资源管理.md) section. + +## Examples + +```text +$ gf pack public,template packed/data.go +done! +$ ll packed +total 184 +-rw-r--r-- 1 john staff 89K Dec 31 00:44 data.go +``` + +## Further Reading + +- [Resource - Best Practices](../核心组件/资源管理/资源管理-最佳实践.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" new file mode 100644 index 00000000000..aec3094aa36 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/cli/docker' +title: 'Image Building' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame Framework, Docker Image Compilation, gf docker, Makefile Build, Binary Build, gf build, gf gen enums, Command Combination, Automatic Push, Configuration File Management] +description: "Use the gf docker command of the GoFrame framework to compile and generate Docker images. After version 2.5, it is recommended to use gf build, gf gen enums, gf docker commands together through Makefile scripts for more flexibility and easier maintenance. Detailed examples and configuration file management suggestions are provided in the text." +--- +:::tip +Starting from version `v2.5`, considering the decoupling of various tool commands, the `gf docker` tool command no longer performs binary compilation by default. Instead, it is recommended to use the `Makefile` build script to organize the use of `gf build, gf gen enums, gf docker` and other commands in combination (the corresponding `make build, make enums, make docker` commands are provided in the project). Combined usage is more flexible and easier to maintain. +::: +## Usage + +```text +$ gf docker -h +USAGE + gf docker [MAIN] [OPTION] + +ARGUMENT + MAIN main file path for "gf build", it's "main.go" by default. empty string for no binary build + +OPTION + -f, --file file path of the Dockerfile. it's "manifest/docker/Dockerfile" by default + -s, --shell path of the shell file which is executed before docker build + -b, --build binary build options before docker image build, it's "-a amd64 -s linux" by default + -tn, --tagName tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes + -tp, --tagPrefixes tag prefixes for this docker, which are used for docker push. this option is required with + TagName + -p, --push auto push the docker image to docker registry if "-t" option passed + -e, --extra extra build options passed to "docker image" + -h, --help more information about this command + +EXAMPLE + gf docker + gf docker -t hub.docker.com/john/image:tag + gf docker -p -t hub.docker.com/john/image:tag + gf docker main.go + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -p -t hub.docker.com/john/image:tag + +DESCRIPTION + The "docker" command builds the GF project to a docker image. + It runs "gf build" firstly to compile the project to a binary file. + It then runs "docker build" command automatically to generate the docker image. + You should have Docker installed, and there must be a Dockerfile in the root of the project. +``` + +Automatically compile and generate `docker` image. The optional `MAIN` argument is the compile file path, defaulting to `main.go`. The optional `OPTIONS` are the same as the `docker build` command parameters and options. + +## Usage Example + +```text +$ gf docker main.go -p -tn loads/gf-demos:test +2020-12-31 00:47:28.207 start building... +2020-12-31 00:47:28.207 go build -o ./bin/linux_amd64/main main.go +2020-12-31 00:47:35.894 done! +Sending build context to Docker daemon 37.63MB +Step 1/10 : FROM loads/alpine:3.8 + ---> f9fb622e6db2 +Step 2/10 : LABEL maintainer="john@goframe.org" + ---> Using cache + ---> da238418d031 +Step 3/10 : ENV WORKDIR /var/www/gf-demos + ---> Using cache + ---> 3e7129c087c9 +Step 4/10 : ADD ./bin/linux_amd64/main $WORKDIR/main + ---> 3661a9dea494 +Step 5/10 : RUN chmod +x $WORKDIR/main + ---> Running in 1d49d5d91080 +Removing intermediate container 1d49d5d91080 + ---> a03ee04e3380 +Step 6/10 : ADD public $WORKDIR/public + ---> 63dd06d0e1a3 +Step 7/10 : ADD config $WORKDIR/config + ---> fa7a57eba577 +Step 8/10 : ADD template $WORKDIR/template + ---> 7075609b0447 +Step 9/10 : WORKDIR $WORKDIR + ---> Running in a34ef38e1031 +Removing intermediate container a34ef38e1031 + ---> 580077998eaf +Step 10/10 : CMD ./main + ---> Running in ed286b518ad9 +Removing intermediate container ed286b518ad9 + ---> fbbc05842901 +Successfully built fbbc05842901 +Successfully tagged loads/gf-demos:test +The push refers to repository [docker.io/loads/gf-demos] +b4025b95a79f: Preparing +9e0369a57507: Preparing +46c68dcc8e12: Preparing +59adbc083ee5: Preparing +10e0b999ba57: Preparing +8e850d7b086e: Waiting +d5e057db20a2: Waiting +92e898fd7f84: Waiting +d9ff549177a9: Waiting +... +``` + +## Configuration File Example + +In most scenarios, we recommend using a configuration file to manage the tool's configuration, maintained in the `hack/config.yaml` file. For example, a configuration example for the `docker` command: + +```yaml +gfcli: + docker: + build: "-a amd64 -s linux -p temp -ew" + tagPrefixes: + - ccr.ccs.tencentyun.com/cdb.khaos.eros +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" new file mode 100644 index 00000000000..f6a934f94c5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/cli/init' +title: 'Project Scaffold' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Project Creation, gf init, MonoRepo, GoFrame Empty Framework, Code Layer Design, go module, Resource Management, Microservice Monorepo Management Mode] +description: "Use the gf init command provided by the GoFrame framework to create a project. Starting from version v2, project creation is faster and no longer relies on remote sources; templates are built into the binary files. You can choose to initialize a single repository or monorepo project mode and flexibly adjust the generated directory structure to suit actual business needs." +--- +:::tip +Starting from version `v2`, project creation no longer relies on remote retrieval. The repository templates are embedded into the tool's binary files via [Resource](../核心组件/资源管理/资源管理.md), making project creation very fast. +::: +## Usage + +```text +$ gf init -h +USAGE + gf init ARGUMENT [OPTION] + +ARGUMENT + NAME The project name, creating a folder named NAME in the current directory, and the module name will also be NAME + +OPTION + -m, --mono Initialize monorepo mode + -a, --monoApp Initialize a small repository under monorepo + -u, --update Use the latest framework version after initialization + -g, --module Customize module + -h, --help More help + +EXAMPLE + gf init my-project + gf init my-mono-repo -m +``` + +We can use the `init` command to generate a sample `GoFrame` empty framework project in the current directory and provide a project name parameter. The generated project directory structure is for reference only and can be adjusted according to the specific situation of the business project. For the generated directory structure, please refer to the [Code Layering](../框架设计/工程开发设计/代码分层设计.md) section. +:::note +The `GoFrame` framework development recommends the unified use of the official `go module` feature for dependency package management, so there is also a `go.mod` file in the root directory of the empty project. +::: +:::tip +The project directory uses a generalized design, and in actual projects, you can increase or decrease the directories given by the template as needed. For example, in scenarios where there is no need for `kubernetes` deployment, simply delete the corresponding `deploy` directory. +::: + +## Examples of Use + +### Initialize a project in the current directory + +```bash +$ gf init . +initializing... +initialization done! +you can now run 'gf run main.go' to start your journey, enjoy! +``` + +### Create a project with a specified name + +```bash +$ gf init myapp +initializing... +initialization done! +you can now run 'cd myapp && gf run main.go' to start your journey, enjoy! +``` + +### Create a `MonoRepo` project + +By default, a `SingleRepo` project is created, but if needed, you can also create a `MonoRepo` project by using the `-m` option. + +```bash +$ gf init mymono -m +initializing... +initialization done! +``` + +For an introduction to monorepos, please refer to the section: [Mono-Repo Management](../框架设计/工程开发设计/微服务大仓管理模式.md) + +#### Create a `MonoRepoApp` project + +If you need to create a small repository under a `MonoRepo`, specify the project path to be generated in the root directory of the repository project and use the `-a` option. + +```bash +$ gf init app/user -a +initializing... +initialization done! +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..a18d1180a6f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/micro-service/structure' +title: 'Project Structure' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Microservices, Project Directory, Protocol Files, Interface Files, Development Tools, Service Launch, Interface Implementation, Data Validation] +description: "Standard project directory structure for microservices development using the GoFrame framework, including management of protocol and interface files. It describes in detail how to use the GoFrame framework's development tools to generate protobuf files corresponding to database table structures, and how to compile protocol files to generate interfaces and controllers. Additionally, it explains the specific steps for service startup and interface implementation, and introduces methods for tag injection and data validation plugins." +--- + +## Introduction + +The project directory for microservices development adopts a unified framework directory structure. For details, please refer to the section: [Project Structure🔥](../框架设计/工程开发设计/工程目录设计.md) + +Here we illustrate using the project [https://github.com/gogf/gf-demo-grpc](https://github.com/gogf/gf-demo-grpc) as an example. + +## Protocol Files + +![](/markdown/016fd519878bf775e744f9f2d1c46cb8.png) Protocol files are defined in the `manifest/protobuf` directory. The directory path rule for protocol files is: `module name/version number/xxx.proto`. The version number is managed in a form similar to `v1/v2` to facilitate maintaining interface compatibility. + +Data table structures involved are stored in the `manifest/pbentity` directory as `protobuf` files generated by the framework development tools. + +## Interface Files + +Interface documents generated by compiling `proto` protocol files are stored in a unified `api` directory. + +## Development Tools + +### Generate Data Table Structure + +We can automatically generate corresponding `protobuf` protocol files based on the database table structure using the `gf gen pbentity / make pbentity` command. For details on the command, please refer to the section: [DB Table To Protobuf](../开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) + +### Compile `proto` Protocol Files + +The framework provides the `gf gen pb / make pb` command to automatically compile `proto` protocol files and generate corresponding interface and controller files. + +## Service Startup + +Service startup is still maintained through the `cmd` directory, for example: [https://github.com/gogf/gf-demo-grpc/blob/main/internal/cmd/cmd.go](https://github.com/gogf/gf-demo-grpc/blob/main/internal/cmd/cmd.go) + +## Interface Implementation and Registration + +Controllers are used for the specific implementation of interfaces defined by `proto`. Controllers can be automatically generated and can automatically generate `Register` methods to register specific implementations with the service object. + +Registration method: + +![](/markdown/50e4eb739f08fcc6479bb32c9e9a6ade.png) + +Startup registration: + +![](/markdown/5cda3b08b1346f392c4b717b71fa2710.png) + +## Tag Injection and Data Validation + +### Automatic Tag Injection + +When compiling `proto` files using the `gf gen pb/make pb` command, automatic tag injection is supported. You only need to write comments in the `proto` file, and these comments will automatically be embedded as `dc` tags into the struct properties. If a comment rule is in the form `xxx:yyy`, it will automatically generate the `xxx` tag. For example: + +![](/markdown/96c4eaa4ff55045ac0d224539a903a2b.png) + +![](/markdown/620e319d848d5b91b93d86c33862f19a.png) + +Note that in the `GRPC` protocol, since both input and output use struct forms, the default value feature of `HTTP` services cannot be implemented. + +### Data Validation Plugin + +The data validation plugin will automatically validate requests based on rules set in the tags, and it needs to be manually introduced on the server side in the form of interceptors: + +![](/markdown/a38675f4912ab10e2680814f0dae2e0f.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..797d96c560f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" @@ -0,0 +1,288 @@ +--- +slug: '/docs/micro-service' +title: 'Microservice Development' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,Microservice,HTTP Protocol,GRPC Protocol,Componentization,Service Discovery,Registration Component,Traceability,protobuf,Development Tutorial] +description: "The microservice development features of the GoFrame framework, including microservice components supporting HTTP and GRPC protocols, low coupling design, use of service registration and discovery components, and traceability capabilities with example tutorials, helping teams quickly implement microservice development and transformation. Examples cover how to use GoFrame for microservice architecture setup and communication." +--- +:::tip +The complete microservice features and related components are provided starting from version `v2.4`. +::: +## Introduction + +The `GoFrame` framework supports microservice mode development, providing commonly used microservice components, development tools, and development tutorials to help teams quickly transition to microservices. + +## Simple Example + +The `GoFrame` microservice components feature low coupling and generic design, supporting most microservice communication protocols. In the official documentation, we use examples of `HTTP` and `GRPC` protocols to introduce microservice development and the use of component tools. Since `HTTP Web` development has a relatively rich and comprehensive independent chapter introduction, most of the microservice chapter is introduced with a focus on `GRPC`. + +### `HTTP` Microservice Example + +[https://github.com/gogf/gf/tree/master/example/registry/file](https://github.com/gogf/gf/tree/master/example/registry/file) + +#### `server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +As you can see, an `HTTP` microservice end and a regular `Web Server` end have no significant differences, but there is an additional line of code at the top: + +``` +gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) +``` + +This line of code is used to enable and register the registration and discovery component used by the current service. In this example, `file.New(gfile.Temp("gsvc"))` is a service registration and discovery component based on local system files, where `gfile.Temp("gsvc")` specifies the path to store service files, for example, in `Linux/MacOS` systems, it points to the `/tmp/gsvc` directory. File system-based registration discovery is only used for local microservice examples and cannot be used for cross-node communication. In production environments, we often use other service registration and discovery components, such as `etcd, polaris, zookeeper`, etc. The community components of the framework already provide implementations of commonly used service registration and discovery components. + +Secondly, in this example, we set a name `hello.svc` for the `Server`, which represents the name of the microservice bound by this `Server`. The service name serves as a unique identifier for microservices, used for identification communication between services. When the service registration component registration is enabled, the `HTTP Server` will register its access address to the service registration component at runtime, making it easier for other services to access it through the same component by service name. + +#### `client.go` + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + client := g.Client() + for i := 0; i < 10; i++ { + ctx := gctx.New() + res, err := client.Get(ctx, `http://hello.svc/`) + if err != nil { + panic(err) + } + g.Log().Debug(ctx, res.ReadAllString()) + res.Close() + time.Sleep(time.Second) + } +} +``` + +The client creates an `HTTP Client` through `g.Client()` and accesses the server through the address `http://hello.svc/`, where `hello.svc` is the microservice name bound by the `Server` end previously. When the client accesses through the microservice name, the service registration and discovery component will perform retrieval at the underlying level and find the corresponding server address for communication. + +#### Execution Results + +First, run the `server.go` server to run a simple service, and then execute `client.go` to request the service by service name. + +After execution, the client outputs: + +```bash +$ go run client.go +2023-03-14 20:22:10.006 [DEBU] {8054f3a48c484c1760fb416bb3df20a4} Hello world +2023-03-14 20:22:11.007 [DEBU] {6831cae08c484c1761fb416b9d4df851} Hello world +2023-03-14 20:22:12.008 [DEBU] {9035761c8d484c1762fb416b1e648b81} Hello world +2023-03-14 20:22:13.011 [DEBU] {a05a32588d484c1763fb416bc19ff667} Hello world +2023-03-14 20:22:14.012 [DEBU] {40fdea938d484c1764fb416b8459fc43} Hello world +2023-03-14 20:22:15.014 [DEBU] {686c9acf8d484c1765fb416b3697d369} Hello world +2023-03-14 20:22:16.015 [DEBU] {906a470b8e484c1766fb416b85b9867e} Hello world +2023-03-14 20:22:17.017 [DEBU] {28c7fd468e484c1767fb416b86e5557f} Hello world +2023-03-14 20:22:18.018 [DEBU] {90d2ad828e484c1768fb416bfcde738f} Hello world +2023-03-14 20:22:19.019 [DEBU] {d05559be8e484c1769fb416baad06f23} Hello world +``` + +The server outputs: + +```bash +$ go run server.go +2023-03-14 20:20:06.364 [INFO] pid[96421]: http server started listening on [:61589] +2023-03-14 20:20:06.364 [INFO] openapi specification is disabled +2023-03-14 20:20:06.364 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:61589 Metadata:map[insecure:true protocol:http]} + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :61589 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :61589 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-14 20:22:10.006 [INFO] {8054f3a48c484c1760fb416bb3df20a4} request received +2023-03-14 20:22:11.007 [INFO] {6831cae08c484c1761fb416b9d4df851} request received +2023-03-14 20:22:12.008 [INFO] {9035761c8d484c1762fb416b1e648b81} request received +2023-03-14 20:22:13.010 [INFO] {a05a32588d484c1763fb416bc19ff667} request received +2023-03-14 20:22:14.012 [INFO] {40fdea938d484c1764fb416b8459fc43} request received +2023-03-14 20:22:15.013 [INFO] {686c9acf8d484c1765fb416b3697d369} request received +2023-03-14 20:22:16.015 [INFO] {906a470b8e484c1766fb416b85b9867e} request received +2023-03-14 20:22:17.016 [INFO] {28c7fd468e484c1767fb416b86e5557f} request received +2023-03-14 20:22:18.017 [INFO] {90d2ad828e484c1768fb416bfcde738f} request received +2023-03-14 20:22:19.019 [INFO] {d05559be8e484c1769fb416baad06f23} request received +``` + +### `GRPC` Microservice Example + +[https://github.com/gogf/gf/tree/master/example/rpc/grpcx/basic](https://github.com/gogf/gf/tree/master/example/rpc/grpcx/basic) + +#### `helloworld.proto` + +The main difference between `grpc` and `http` protocols is that `grpc` requires `protobuf` to define `API` interfaces and data structures. + +``` +syntax = "proto3"; + +package protobuf; + +option go_package = "github.com/gogf/gf/grpc/example/helloworld/protobuf"; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} +``` + +The above `protobuf` file is compiled with the following command (please install the `protoc` tool in advance): + +``` +gf gen pb +``` + +It will generate the corresponding `proto go` data structure file and `grpc` interface file: + +``` +helloworld.pb.go +helloworld_grpc.pb.go +``` + +#### `controller.go` + +The controller is used to implement the interface methods defined in `proto` (if using the framework's standardized engineering directory structure, this controller code file is also automatically generated by the framework's `gf gen pb` tool, and developers only need to fill in the corresponding method's specific implementation): + +```go +type Controller struct { + protobuf.UnimplementedGreeterServer +} + +func Register(s *grpcx.GrpcServer) { + protobuf.RegisterGreeterServer(s.Server, &Controller{}) +} + +// SayHello implements helloworld.GreeterServer +func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) { + return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil +} +``` + +#### `config.yaml` + +The server configuration file specifies that the service's name is `demo`. The microservice name is used as the unique identification mark for service communication. When the server's listening port is not explicitly specified, the server will randomly listen on an available local port. In microservice mode, since communication is conducted using the service name, the server port often does not need to be explicitly specified, and random listening is sufficient. + +``` +grpc: + name: "demo" + logPath: "./log" + logStdout: true + errorLogEnabled: true + accessLogEnabled: true + errorStack: true +``` + +#### `server.go` + +The `grpc` server does not explicitly specify the service registration and discovery component used by the server by default uses the system file registration discovery component, which is only used for single-machine testing. The `controller.Register` call registers the specific interface implementation into the server via the controller registration method generated by our tool. + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +#### `client.go` + +The `grpc` client needs to specify the specific name of the server service when creating the connection. Here, the server service name is `demo`, which is the microservice name mentioned above. When the client does not explicitly specify the service registration and discovery component used, it defaults to the system file registration discovery component for single-machine testing. + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +#### Execution Results + +The server output: You can see the server output some `DEBU` debug information to indicate some details of the service registration. At the same time, because the server's listening port was not explicitly specified, a local port `64517` was randomly listened on here. + +```bash +$ go run server.go +2023-03-14 20:50:58.465 [DEBU] set default registry using file registry as no custom registry set +2023-03-14 20:50:58.466 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:64517 Metadata:map[protocol:grpc]} +2023-03-14 20:50:58.466 [INFO] pid[98982]: grpc server started listening on [:64517] +2023-03-14 20:52:37.059 {9898c809364a4c17da79e47f3e6c3b8f} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +``` + +The client output: The client accessed through the microservice name and received a return from the server. Note that in the client's log and the server's log, the chain tracing `TraceID` is the same (`9898c809364a4c17da79e47f3e6c3b8f`), indicating logs generated from the same request. The `GoFrame` microservice feature enables traceability capabilities by default. + +```bash +$ go run client.go +2023-03-14 20:52:37.060 [DEBU] {9898c809364a4c17da79e47f3e6c3b8f} Response: Hello World +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..ac30df51a5a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/micro-service/interceptor' +title: 'GRPC Interceptor Component' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, GRPC Interceptor, Server Interceptor, Client Interceptor, grpcx Component, Link Tracing, Error Handling, Automatic Error Validation, Server Panic Capture] +description: "Methods for using GRPC interceptors in the GoFrame framework, detailing the implementation of server and client interceptors, including features such as link tracing, error handling, and automatic error validation. By using the grpcx component, users can flexibly add and customize interceptors to enhance the scalability and stability of GRPC services and clients." +--- + +`GRPC` supports interceptor features, enhancing the flexibility and extensibility of `GRPC`. + +## Interceptor Usage + +### Server + +Use `grpcx.Server.ChainUnary` to add extra server interceptors: + +```go +c := grpcx.Server.NewConfig() +c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., +) +s := grpcx.Server.New(c) +user.Register(s) +s.Run() +``` + +### Client + +Use `grpcx.Client.ChainUnary` to add extra server interceptors: + +``` +conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Client.ChainUnary( + grpcx.Client.UnaryTracing, +)) +``` + +## Interceptor List + +The `grpcx` component of the framework offers a series of commonly used interceptors, some built-in and others optional. + +| Interceptor | Usage Part | Built-in | Description | +| --- | --- | --- | --- | +| `UnaryError` | Client | Yes | Supports the framework's error handling component. | +| `UnaryTracing` | Client | Yes | Supports link tracing. | +| `StreamTracing` | Client | Yes | Supports link tracing (long connection). | +| `UnaryError` | Server | Yes | Supports the framework's error handling component. | +| `UnaryRecover` | Server | Yes | Supports automatic capture of server `panic` without crashing. | +| `UnaryAllowNilRes` | Server | Yes | Supports returning `nil` `Res` objects. | +| `UnaryValidate` | Server | No | Supports the framework's automatic error validation, based on struct tags. Needs to be manually introduced. | +| `UnaryTracing` | Server | Yes | Supports link tracing. | +| `StreamTracing` | Server | Yes | Supports link tracing (long connection). | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" new file mode 100644 index 00000000000..7deecf9a79a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" @@ -0,0 +1,229 @@ +--- +slug: '/docs/micro-service/registry-discovery' +title: 'Service Registry and Discovery' +sidebar_position: 5 +hide_title: true +description: "The use and implementation of the service registry and discovery components within the GoFrame framework, managed by the gsvc component, supports multiple implementations such as etcd, zookeeper, and polaris. Proper configuration enables global service registry and discovery, enhancing the flexibility and scalability of service calls, which is an important guide for developing microservice architectures." +keywords: [GoFrame framework, service registry, service discovery, microservice architecture, gsvc component, etcd, zookeeper, polaris, flexibility, scalability] +--- + +## Introduction + +The `GoFrame` framework provides service registry and discovery components managed by the `gsvc` component, which primarily defines the interface for registry and discovery. The specific implementation is provided by community components: [https://github.com/gogf/gf/tree/master/contrib/registry](https://github.com/gogf/gf/tree/master/contrib/registry). Currently, community components offer multiple implementations for registry and discovery, such as `etcd, zookeeper, polaris`, etc. Developers can use them interchangeably as needed or implement their own registry and discovery components according to the API definition of the `gsvc` component. + +## Component Activation + +The registry and discovery components are activated only when a specific interface implementation is introduced. For example, using `etcd` to implement registry and discovery: + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + // ... +} +``` + +## Common Components + +| Component Name | Documentation | Remarks | +| --- | --- | --- | +| `file` | [https://github.com/gogf/gf/tree/master/contrib/registry/file](https://github.com/gogf/gf/tree/master/contrib/registry/file) | Only for single-machine testing | +| `etcd` | [https://github.com/gogf/gf/tree/master/contrib/registry/etcd](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) | | +| `polaris` | [https://github.com/gogf/gf/tree/master/contrib/registry/polaris](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) | | +| `zookeeper` | [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) | | + +For more components, refer to: [https://github.com/gogf/gf/tree/master/contrib/registry](https://github.com/gogf/gf/tree/master/contrib/registry) + +## Usage Example + +### `HTTP` + +You can use ``gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`))`` to set the registry and discovery with `etcd`. The `etcd.New` in this case denotes creating an interface implementation object of `gsvc.Registry` through a community component, and setting the global default registry and discovery interface implementation object via the `gsvc.SetRegistry` method. + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + ctx := gctx.New() + res := g.Client().GetContent(ctx, `http://hello.svc/`) + g.Log().Info(ctx, res) +} +``` + +After execution, the server output: + +```bash +$ go run server.go +2023-03-15 20:55:56.256 [INFO] pid[3358]: http server started listening on [:60700] +2023-03-15 20:55:56.256 [INFO] openapi specification is disabled +2023-03-15 20:55:56.256 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:60700 Metadata:map[insecure:true protocol:http]} +2023-03-15 20:55:56.297 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:60700", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813002" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :60700 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :60700 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 20:56:45.739 [INFO] {880eaa8104994c17ffb384495cd4c613} request received +``` + +Client output: + +```bash +$ go run client.go +2023-03-15 20:56:45.739 [INFO] {880eaa8104994c17ffb384495cd4c613} Hello world +``` + +### `GRPC` +:::warning +For the `GRPC` protocol, you must use the `grpcx.Resolver` module from `grpcx` to set the service registry and discovery component. On the `Server` side, if the `grpc.name` value is not set in the `config.yaml`, the default value is `default`. +::: +`server.go` + +In the code, `etcd.New` denotes creating an interface implementation object of `gsvc.Registry` through a community component, and setting the global `grpc` registry and discovery interface implementation object via `grpcx.Resolver.Register`. + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/registry/etcd/grpc/controller" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +config.yaml + +The default configuration file for the server: + +``` +grpc: + name: "demo" # Service name + address: ":8000" # Custom service listening address + logPath: "./log" # Log storage directory path + logStdout: true # Whether the log is output to the terminal + errorLogEnabled: true # Whether to enable error log recording + accessLogEnabled: true # Whether to enable access log recording + errorStack: true # Whether to record the error stack when an error occurs +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/registry/etcd/grpc/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +After execution, the server output: + +```bash +$ go run server.go +2023-03-15 21:06:57.204 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:61978 Metadata:map[protocol:grpc]} +2023-03-15 21:06:57.257 [DEBU] etcd put success with key "/service/default/default/demo/latest/10.35.12.81:61978", value "{"protocol":"grpc"}", lease "7587869265945813015" +2023-03-15 21:06:57.257 [INFO] pid[5786]: grpc server started listening on [:61978] +2023-03-15 21:07:04.955 {08f0aead94994c1731591d2b653ddc18} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +``` + +Client output: + +```bash +$ go run client.go +2023-03-15 21:07:04.950 [DEBU] Watch key "/service/default/default/demo/latest/" +2023-03-15 21:07:04.952 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:61978","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 21:07:04.953 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:61978","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 21:07:04.955 [DEBU] {08f0aead94994c1731591d2b653ddc18} Response: Hello World +``` + +## Frequently Asked Questions + +### How to disable the discovery feature for specific requests when globally enabling the service registry and discovery + +**Question**: When using `gclient`, if the service registry and discovery feature are globally enabled, all requests from `gclient` will go through the discovery service. However, for services that are not maintained in the service registry and discovery, such as a request to an `IP:PORT` address or an external network service request, it will also go through the discovery service, resulting in a service not found error. How can this be avoided? + +**Answer**: When globally enabling the discovery feature, `gclient` requests will by default use the globally set discovery service. If a specific request does not use the discovery service, you can disable the discovery service for the current request using the `Discovery(nil)` chain operation method of `gclient.Client` or disable the discovery service for the current client using the `SetDiscovery(nil)` configuration method. This way, the request will not go through the discovery service. + +**Example**: + +```go +// Disable the discovery service for the current request via chain operation method +g.Client().Discovery(nil).Get(ctx, "http://192.168.1.1/api/v1/user") + +// Disable the discovery service for the current client via configuration method +client := g.Client() +client.SetDiscovery(nil) +client.Get(ctx, "http://192.168.1.1/api/v1/user") +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..6cc33228ab0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/micro-service/config' +title: 'GRPC Server Configuration' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Configuration Management, Server Configuration, GrpcServerConfig, Log Configuration, Log Component, Server Listening, Automatic Mapping, Error Logging] +description: "Server configuration methods, including how to read and manage configuration files through the GoFrame Framework. Provides a complete configuration template example covering service name, service listening address, log storage directory, error log recording, and access log recording settings. The configuration aligns with the framework's automatic reading logic, ensuring convenient service deployment and efficient log management, as well as how to set up and use parameter log component configurations for independent grpc server log management." +--- + +## Introduction + +The server supports configuration files, and all configurations will be automatically mapped to the configuration object. The configuration object is as follows: + +```go +// GrpcServerConfig is the configuration for server. +type GrpcServerConfig struct { + Address string // (optional) Address for server listening. + Name string // (optional) Name for current service. + Logger *glog.Logger // (optional) Logger for server. + LogPath string // (optional) LogPath specifies the directory for storing logging files. + LogStdout bool // (optional) LogStdout specifies whether printing logging content to stdout. + ErrorStack bool // (optional) ErrorStack specifies whether logging stack information when error. + ErrorLogEnabled bool // (optional) ErrorLogEnabled enables error logging content to files. + ErrorLogPattern string // (optional) ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + AccessLogEnabled bool // (optional) AccessLogEnabled enables access logging content to file. + AccessLogPattern string // (optional) AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log +} +``` + +The logic for automatic reading of configuration files is consistent with the framework. For detailed information, please refer to the section: [Configuration](../核心组件/配置管理/配置管理.md) + +## Configuration Template + +An example of a complete configuration template: + +```yaml +grpc: + name: "demo" # Service name + address: ":8000" # Custom service listening address + logPath: "./log" # Log storage directory path + logStdout: true # Whether to output logs to the terminal + errorLogEnabled: true # Whether to enable error logging + accessLogEnabled: true # Whether to enable access logging + errorStack: true # Whether to log error stacks when errors occur + + # Log extension configuration (parameter log component configuration) + logger: + path: "/var/log/" # Log file path. Default is empty, indicating disabled, output to terminal only + file: "{Y-m-d}.log" # Log file format. Default is "{Y-m-d}.log" + prefix: "" # Prefix for log content output. Default is empty + level: "all" # Log output level + stdout: true # Whether to output logs to terminal simultaneously. Default is true + rotateSize: 0 # Rotate files based on log file size. Default is 0, indicating disabled + rotateExpire: 0 # Rotate files based on time intervals. Default is 0, indicating disabled + rotateBackupLimit: 0 # Limit backup files based on the number of split files, valid when rotation is enabled. Default is 0, meaning no backup, delete when split + rotateBackupExpire: 0 # Clean up split files based on the expiration period, valid when rotation is enabled. Default is 0, meaning no backup, delete when split + rotateBackupCompress: 0 # Compression ratio for rotating files (0-9). Default is 0, indicating no compression + rotateCheckInterval: "1h" # Time interval for rotation checks, usually no need to set. Default is 1 hour + +``` + +The log configuration here is consistent with the `http server` and can use independent log component configuration items to configure the `grpc server` logs. For detailed information about the log component configuration, please refer to this document: [Logging - Configuration](../核心组件/日志组件/日志组件-配置管理.md) + +In case the `address` is not configured, the `grpc server` will start using all `ip` addresses of the local network card with a **random free port** (default configuration `:0`). If you want to specify the `ip` but use a random free port to start the `grpc server`, you can configure the `address` using the format `ip:0`, for example: `192.168.1.1:0, 10.0.1.1:0`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" new file mode 100644 index 00000000000..bb2d77fc26b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" @@ -0,0 +1,240 @@ +--- +slug: '/docs/micro-service/load-balance' +title: 'Service Load Balancing' +sidebar_position: 6 +hide_title: true +keywords: [load balancing, GoFrame, gsel component, LeastConnection, Random, RoundRobin, Weight, HTTP service, GRPC service, custom strategy] +description: "The load balancing component in the GoFrame framework, the gsel component, provides multiple built-in load balancing strategies including LeastConnection, Random, RoundRobin, and Weight. Developers can choose the appropriate strategy based on their needs or implement a custom one, with practical application examples of load balancing strategies shown through HTTP and GRPC." +--- + +## Introduction + +The load balancing component is used on the client side. The `GoFrame` framework provides a decoupled design with high flexibility and strong extensibility for the load balancing component, which is managed by the `gsel` component. This component defines the interface for load balancing and provides multiple built-in load balancing strategy implementations. Developers can also implement custom load balancing strategies based on the interface. + +## Strategy List + +The `gsel` component provides several commonly used load balancing strategies for developers to choose from: + +| Strategy Name | Strategy Description | +| --- | --- | +| `LeastConnection` | Least number of connections. | +| `Random` | Random access. | +| `RoundRobin` | Round-robin access. | +| `Weight` | Weighted access. The `Weight` parameter needs to be set during service registration. | + +## Usage Example + +### `HTTP` + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +`client.go` + +Here, `gsel.SetBuilder(gsel.NewBuilderRoundRobin())` is used to set the global load balancing strategy to round-robin access. + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + gsel.SetBuilder(gsel.NewBuilderRoundRobin()) + + for i := 0; i < 10; i++ { + ctx := gctx.New() + res := g.Client().GetContent(ctx, `http://hello.svc/`) + g.Log().Info(ctx, res) + } +} +``` + +Start two servers separately and run the client. + +`server1` terminal output: + +```bash +$ go run server.go +2023-03-15 21:24:08.413 [INFO] pid[10219]: http server started listening on [:63956] +2023-03-15 21:24:08.413 [INFO] openapi specification is disabled +2023-03-15 21:24:08.413 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:63956 Metadata:map[insecure:true protocol:http]} +2023-03-15 21:24:08.455 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:63956", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813020" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63956 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63956 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 21:24:18.357 [INFO] {e05b6049859a4c17d1de5d62eafa5a5f} request received +2023-03-15 21:24:18.358 [INFO] {785e9349859a4c17d3de5d62049e5b51} request received +2023-03-15 21:24:18.360 [INFO] {7076ab49859a4c17d5de5d62aaa64c85} request received +2023-03-15 21:24:18.360 [INFO] {205fb849859a4c17d7de5d62cb2590f4} request received +2023-03-15 21:24:18.361 [INFO] {885fc349859a4c17d9de5d6235937e31} request received +``` + +`server2` terminal output: + +```bash +$ go run server.go +2023-03-15 21:24:10.769 [INFO] pid[10242]: http server started listening on [:63964] +2023-03-15 21:24:10.770 [INFO] openapi specification is disabled +2023-03-15 21:24:10.770 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:63964 Metadata:map[insecure:true protocol:http]} +2023-03-15 21:24:10.812 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:63964", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813023" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63964 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63964 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 21:24:18.357 [INFO] {602d8749859a4c17d2de5d62d515e464} request received +2023-03-15 21:24:18.359 [INFO] {e0ed9b49859a4c17d4de5d628284ae62} request received +2023-03-15 21:24:18.360 [INFO] {e0e0b249859a4c17d6de5d62beda3001} request received +2023-03-15 21:24:18.361 [INFO] {7087bd49859a4c17d8de5d62f892e8aa} request received +2023-03-15 21:24:18.361 [INFO] {e8aec849859a4c17dade5d6247101836} request received +``` + +Client terminal output: + +```bash +$ go run client.go +2023-03-15 21:24:18.357 [INFO] {e05b6049859a4c17d1de5d62eafa5a5f} Hello world +2023-03-15 21:24:18.358 [INFO] {602d8749859a4c17d2de5d62d515e464} Hello world +2023-03-15 21:24:18.358 [INFO] {785e9349859a4c17d3de5d62049e5b51} Hello world +2023-03-15 21:24:18.359 [INFO] {e0ed9b49859a4c17d4de5d628284ae62} Hello world +2023-03-15 21:24:18.360 [INFO] {7076ab49859a4c17d5de5d62aaa64c85} Hello world +2023-03-15 21:24:18.360 [INFO] {e0e0b249859a4c17d6de5d62beda3001} Hello world +2023-03-15 21:24:18.360 [INFO] {205fb849859a4c17d7de5d62cb2590f4} Hello world +2023-03-15 21:24:18.361 [INFO] {7087bd49859a4c17d8de5d62f892e8aa} Hello world +2023-03-15 21:24:18.361 [INFO] {885fc349859a4c17d9de5d6235937e31} Hello world +2023-03-15 21:24:18.361 [INFO] {e8aec849859a4c17dade5d6247101836} Hello world +``` + +### `GRPC` + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx context.Context + conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom()) + client = protobuf.NewGreeterClient(conn) + ) + for i := 0; i < 10; i++ { + ctx = gctx.New() + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) + } +} +``` + +The `grpcx.Balancer.WithRandom()` indicates using a random request strategy. Start two `server.go` servers and then run the `client.go` client to check the server request logs: + +`server1` terminal output: + +```bash +$ go run server.go +2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:44.802 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53962 Metadata:map[protocol:grpc]} +2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962] +2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +`server2` terminal output: + +```bash +$ go run server.go +2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:51.721 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53973 Metadata:map[protocol:grpc]} +2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973] +2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +Client terminal output: + +```bash +$ go run client.go +2023-03-15 19:50:57.278 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:53962","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null},{"Addr":"10.35.12.81:53973","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World +2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World +2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World +2023-03-15 19:50:57.287 [DEBU] {f0fab02f6d954c17cdf33505438b2c80} Response: Hello World +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..64b5ca58aad --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,179 @@ +--- +slug: '/docs/micro-service/config-service' +title: 'Service Configuration' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, configuration management, microservices, Polaris, Apollo, Nacos, Consul, Kubernetes, container orchestration, configuration center] +description: "Using the configuration management component in the GoFrame framework, it supports various third-party configuration centers like Polaris, Apollo, Nacos, Consul, etc., through decoupled design. Detailed code is provided to showcase how to initialize and enable the Polaris configuration client, including usage examples and error handling methods." +--- + +## Introduction + +The `GoFrame` framework provides a configuration management component designed with a decoupled and interface-oriented approach, allowing flexible integration with various third-party configuration management centers. The component default implementation is based on local system files. For more implementations, refer to community components: [https://github.com/gogf/gf/tree/master/contrib/config](https://github.com/gogf/gf/tree/master/contrib/config) + +The community components provide implementations for various popular configuration centers, such as `polaris, apollo, nacos, consul`, and container orchestration `kubernetes configmap`. + +## Component Activation + +The activation of the configuration management component is achieved through package initialization. Since the configuration management feature is quite fundamental, it is necessary to ensure that the community package is introduced at the very top of the `main` package to avoid pitfalls. Here we take `polaris` as an example, for how to use the community component: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) + +An independent import package is needed, such as `boot`: + +```go +package boot + +import ( + "github.com/gogf/gf/contrib/config/polaris/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func init() { + var ( + ctx = gctx.GetInitCtx() + namespace = "default" + fileGroup = "TestGroup" + fileName = "config.yaml" + path = "manifest/config/polaris.yaml" + logDir = "/tmp/polaris/log" + ) + // Create polaris Client that implements gcfg.Adapter. + adapter, err := polaris.New(ctx, polaris.Config{ + Namespace: namespace, + FileGroup: fileGroup, + FileName: fileName, + Path: path, + LogDir: logDir, + Watch: true, + }) + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + // Change the adapter of default configuration instance. + g.Cfg().SetAdapter(adapter) +} +``` + +Where: + +- `Namespace` specifies the namespace in the `polaris` configuration. +- `FileGroup` specifies the file group in `polaris`. +- `FileName` specifies the name of the configuration file to read in `polaris`. +- `Path` specifies the server-side configuration of `polaris`, including the connection address, listening address, component output log path, etc. + +The configuration file of `Polaris` is as follows: + +```yaml +global: + serverConnector: + addresses: + - 127.0.0.1:8091 +config: + configConnector: + addresses: + - 127.0.0.1:8093 +consumer: + localCache: + persistDir: "/tmp/polaris/backup" +``` + +Then introduce the `boot` package at the top of `main.go`, ensuring that its import is before all other components: + +```go +package main + +import ( + _ "github.com/gogf/gf/example/config/polaris/boot" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.GetInitCtx() + + // Available checks. + g.Dump(g.Cfg().Available(ctx)) + + // All key-value configurations. + g.Dump(g.Cfg().Data(ctx)) + + // Retrieve certain value by key. + g.Dump(g.Cfg().MustGet(ctx, "server.address")) +} +``` + +Note the import statement at the top: + +``` +_ "github.com/gogf/gf/example/config/polaris/boot" +``` + +## Common Components + +| Component Name | Documentation | Remark | +| --- | --- | --- | +| `file` | Built-in framework | Default implementation | +| `apollo` | [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) | | +| `kubecm` | [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) | Commonly used in container deployment environments | +| `nacos` | [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) | | +| `polaris` | [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) | | +| `consul` | [https://github.com/gogf/gf/tree/master/contrib/config/consul](https://github.com/gogf/gf/tree/master/contrib/config/consul) | | + +For more components, refer to: [https://github.com/gogf/gf/tree/master/contrib/config](https://github.com/gogf/gf/tree/master/contrib/config) + +## Usage Example + +[https://github.com/gogf/gf/tree/master/example/config/polaris](https://github.com/gogf/gf/tree/master/example/config/polaris) + +### Run `polaris` + +```bash +docker run -d --name polaris -p 8080:8080 -p 8090:8090 -p 8091:8091 -p 8093:8093 loads/polaris-server-standalone:1.11.2 +``` + +### Run the example + +```bash +$ go run main.go +true +{} +"failed to update local value: config file is empty" +panic: failed to update local value: config file is empty + +goroutine 1 [running]: +github.com/gogf/gf/v2/os/gcfg.(*Config).MustGet(0x0?, {0x1c1c4f8?, 0xc0000c2000?}, {0x1ac11ad?, 0x0?}, {0x0?, 0xc000002340?, 0xc000064738?}) + /Users/john/Workspace/gogf/gf/os/gcfg/gcfg.go:167 +0x5e +main.main() + /Users/john/Workspace/gogf/gf/example/config/polaris/main.go:20 +0x1b8 +``` + +As seen, the `MustGet` method execution at the end reports an error because there is no specified namespace, configuration group, and configuration file in `polaris`. Even if no data is retrieved, it returns an error due to configuration issues. Because the `Must*` method is used here, when an error is returned upon execution, it will `panic` directly instead of returning an error. + +So let's add some test data in the `polaris` backend and try again. + +### Add Test Data + +Log in to [http://127.0.0.1:8080/#/login](http://127.0.0.1:8080/#/login) with default username `polaris` and password `polaris`. + +![](/markdown/b6aa368b0594187558a778aa54a428a2.png) + +![](/markdown/acaecb43af69ec5f149e1fbe8f74dc4b.png) + +### Run the example again + +```bash +$ go run main.go +true +{ + "server": { + "openapiPath": "/api.json", + "swaggerPath": "/swagger", + "address": ":8199", + }, +} + +":8199" +``` + +We can see that the configuration data in `polaris` has been correctly retrieved. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..7f61f43bf13 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/micro-service/prepare-environment' +title: 'Preparation' +sidebar_position: 0 +hide_title: true +keywords: [environment preparation, microservices, GRPC protocol, GoFrame, GoFrame framework, dependency installation, CLI development tool, protoc tool, framework tool, protocol support] +description: "Prepare the environment for microservices development, mainly including the installation guide of GRPC protocol dependencies and GoFrame framework CLI tools. Learners need to ensure the installation of GRPC related tools and master the basic application of GRPC protocol in the microservices development of the GoFrame framework." +--- + +The subsequent microservices chapters mainly focus on the `GRPC` protocol, introducing microservices development, components, and tool usage. + +## Dependency Installation + +Before further learning, please first understand the relevant concepts of `GRPC` and install the corresponding tools to the local development environment: + +- [https://grpc.io/](https://grpc.io/) +- [https://grpc.io/docs/languages/go/quickstart/](https://grpc.io/docs/languages/go/quickstart/) + +If it's a `MacOS` environment, consider using the `brew` tool to install dependencies: + +```bash +brew install grpc protoc-gen-go protoc-gen-go-grpc +``` + +After installing the dependency tools, please refer to the chapter [https://grpc.io/docs/languages/go/quickstart/](https://grpc.io/docs/languages/go/quickstart/) to complete the basic study of the `protoc` tool usage. + +## Framework Tools + +Please ensure the version of the GoFrame framework CLI development tool is `>= v2.4`. For tool installation and upgrade, please refer to the chapter: [CLI Tool](../开发工具/开发工具.md) + +The framework tools provide additional command support for the `GRPC` protocol, greatly simplifying the microservices development based on the `GRPC` protocol. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" new file mode 100644 index 00000000000..293d80a21b0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" @@ -0,0 +1,377 @@ +--- +slug: '/docs/micro-service/scaffold' +title: 'Scaffold Components' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Microservices, GRPC, Scaffold, Server, Client, Context, Load Balancing, Service Discovery, etcd] +description: "The scaffold module in the GoFrame framework provides convenient implementations of GRPC server and client, including features like context, load balancing, and service discovery. With the grpcx component, users can easily build and manage microservice applications, supporting a variety of load balancing strategies and etcd as a registry center, simplifying inter-service communication and data transfer." +--- + +The `GoFrame` framework includes multiple microservice components and provides an easy-to-use `GRPC` scaffold module and tools. The scaffold is implemented by the `grpcx` community package: [https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx](https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx) which contains multiple modules. + +## Server - `Server` + +The server is maintained by the `grpcx.Server` module, used for creating and maintaining server objects. Example usage: [https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/server/main.go](https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/server/main.go) + +The creation of the server often involves using configuration files. For an introduction to server configuration files, please refer to the chapter: [GRPC Server Configuration](服务端配置.md) + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +## Client - `Client` + +The client is maintained by the `grpcx.Client` module, used for creating and maintaining client objects. Example usage: [https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/client/main.go](https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/client/main.go) + +In most scenarios, service access is based on service names. + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` +:::warning +Common Issue + +**Should the `Client` object that is created and reused be saved?** + +Each `grpc Client` object actually corresponds to access to a target service. This object should be saved and reused, rather than creating a new `Client` object each time, as this can improve efficiency, reduce resource usage, and be GC-friendly. +::: + +## Context - `Ctx` + +The context is maintained by the `grpcx.Ctx` module, used for custom data transfer between clients and servers, as well as between services. It includes the following commonly used methods: + +```go +func (c Ctx) NewIncoming(ctx context.Context, data ...g.Map) context.Context +func (c Ctx) NewOutgoing(ctx context.Context, data ...g.Map) context.Context +func (c Ctx) IncomingToOutgoing(ctx context.Context, keys ...string) context.Context +func (c Ctx) IncomingMap(ctx context.Context) *gmap.Map +func (c Ctx) OutgoingMap(ctx context.Context) *gmap.Map +func (c Ctx) SetIncoming(ctx context.Context, data g.Map) context.Context +func (c Ctx) SetOutgoing(ctx context.Context, data g.Map) context.Context +``` + +The `Outgoing` is used on the client side to indicate custom data to be transmitted to the server, usually composed of `Key-Value` pairs in `Map` data format. The `Incoming` is used on the server side to indicate data received from the client. Some framework-related embedded information, such as trace information and client version information, is also written into the `Incoming` key-value pairs. Example usage: + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`controller.go` + +```go +package controller + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf" + "github.com/gogf/gf/v2/frame/g" +) + +type Controller struct { + protobuf.UnimplementedGreeterServer +} + +func Register(s *grpcx.GrpcServer) { + protobuf.RegisterGreeterServer(s.Server, &Controller{}) +} + +// SayHello implements helloworld.GreeterServer +func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) { + m := grpcx.Ctx.IncomingMap(ctx) + g.Log().Infof(ctx, `incoming data: %v`, m.Map()) + return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil +} +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ctx = grpcx.Ctx.NewOutgoing(gctx.New(), g.Map{ + "UserId": "1000", + "UserName": "john", + }) + ) + g.Log().Infof(ctx, `outgoing data: %v`, grpcx.Ctx.OutgoingMap(ctx).Map()) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +After execution, the server outputs: + +```bash +$ go run server.go +2023-03-15 19:28:45.331 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:28:45.331 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:51707 Metadata:map[protocol:grpc]} +2023-03-15 19:28:45.332 [INFO] pid[83753]: grpc server started listening on [:51707] +2023-03-15 19:28:54.093 [INFO] {d049db1a39944c1711bd9f37d58a88f9} incoming data: map[:authority:service/default/default/demo/latest/ content-type:application/grpc traceparent:00-d049db1a39944c1711bd9f37d58a88f9-adbd2ba657ffea45-01 user-agent:grpc-go/1.49.0 userid:1000 username:john] +2023-03-15 19:28:54.093 {d049db1a39944c1711bd9f37d58a88f9} /protobuf.Greeter/SayHello, 0.049ms, name:"World", message:"Hello World" +``` + +Client output: + +```bash +$ go run client.go +2023-03-15 19:28:54.087 [INFO] {d049db1a39944c1711bd9f37d58a88f9} outgoing data: map[userid:1000 username:john] +2023-03-15 19:28:54.089 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:51707","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:28:54.093 [DEBU] {d049db1a39944c1711bd9f37d58a88f9} Response: Hello World +``` + +## Load Balancer - `Balancer` + +Load balancing is maintained by the `grpcx.Balancer` module, used to implement how requests are made when the server has multiple access addresses. If the client does not set a load balancing strategy, the default round-robin strategy is used. For detailed information about load balancing, please refer to the chapter: [Service Load Balancing](服务负载均衡.md) + +Here, we use the **random strategy** as an example, where the random strategy will make the number of requests received by different servers relatively random. + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx context.Context + conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom()) + client = protobuf.NewGreeterClient(conn) + ) + for i := 0; i < 10; i++ { + ctx = gctx.New() + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) + } +} +``` + +Here, `grpcx.Balancer.WithRandom()` indicates the use of a random request strategy. + +Start two `server.go` instances, then run the `client.go` client, and observe the servers' request logs: + +`server1` terminal output: + +```bash +$ go run server.go +2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:44.802 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53962 Metadata:map[protocol:grpc]} +2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962] +2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +`server2` terminal output: + +```bash +$ go run server.go +2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:51.721 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53973 Metadata:map[protocol:grpc]} +2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973] +2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +Client terminal output: + +```bash +$ go run client.go +2023-03-15 19:50:57.278 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:53962","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null},{"Addr":"10.35.12.81:53973","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World +2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World +2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World +2023-03-15 19:50:57.287 [DEBU] {f0fab02f6d954c17cdf33505438b2c80} Response: Hello World +``` + +You can see that the client sent `10` requests, and the two servers each received `4` and `6` requests, respectively, with the load of requests being relatively random. + +## Service Discovery - `Resolver` + +Service discovery is maintained by the `grpcx.Resolver` module, used for `GRPC` service name resolution. The `grpcx` component uses the local file system for service discovery by default, only for testing purposes. For production environments, other service discovery components are required. A simple example: + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/resolver/controller" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +The `grpcx.Resolver.Register(etcd.New("127.0.0.1:2379"))` is used to set the service discovery component to `etcd`, which only supports `GRPC` protocol. + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/resolver/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +Start `etcd`: + +```bash +$ etcd +{"level":"info","ts":"2023-03-15T20:02:50.966+0800","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd"]} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:100","msg":"failed to detect default host","error":"default host not supported on darwin_amd64"} +{"level":"warn","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:105","msg":"'data-dir' was empty; using default","data-dir":"default.etcd"} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:116","msg":"server has been already initialized","data-dir":"default.etcd","dir-type":"member"} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"embed/etcd.go:124","msg":"configuring peer listeners","listen-peer-urls":["http://localhost:2380"]} +{"level":"info","ts":"2023-03-15T20:02:50.968+0800","caller":"embed/etcd.go:132","msg":"configuring client listeners","listen-client-urls":["http://localhost:2379"]} +... +``` + +Run `server.go`: + +```bash +$ go run server.go +2023-03-15 20:02:19.472 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:55066 Metadata:map[protocol:grpc]} +2023-03-15 20:02:19.516 [DEBU] etcd put success with key "/service/default/default/demo/latest/10.35.12.81:55066", value "{"protocol":"grpc"}", lease "7587869265929863945" +2023-03-15 20:02:19.516 [INFO] pid[92040]: grpc server started listening on [:55066] +2023-03-15 20:02:29.310 {88c4c04e0e964c17dddec53b1adb54f7} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +Run `client.go`: + +```bash +$ go run client.go +2023-03-15 20:02:29.297 [DEBU] Watch key "/service/default/default/demo/latest/" +2023-03-15 20:02:29.307 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 20:02:29.308 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 20:02:29.310 [DEBU] {88c4c04e0e964c17dddec53b1adb54f7} Response: Hello World +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" new file mode 100644 index 00000000000..a7e4a78cfe5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/obs' +title: 'Service Observability' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Service Observability, Engineering Perfection, Development Framework, Observability Features, Developers, Easy Implementation, Technical Documentation, Engineering Practice] +description: "Service observability is an important aspect of modern software development. Using the GoFrame framework allows developers to easily implement this feature. GoFrame provides powerful tools and methods to help developers enhance service monitoring and management capabilities, ensuring system stability and reliability." +--- + +As a well-engineered development framework, `GoFrame` allows developers to easily implement service observability features. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" new file mode 100644 index 00000000000..080e3abb512 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" @@ -0,0 +1,16 @@ +--- +slug: '/docs/obs/metrics' +title: 'Service Metrics' +sidebar_position: 1 +hide_title: true +keywords: [Service Monitoring, Alerts, Monitoring Alerts, Metrics Exposure, GoFrame, GoFrame Framework, Monitoring Metrics, Developers, Version 2.7, Framework Features] +description: "Using the GoFrame framework for service monitoring and alerts, from version 2.7 onwards, developers can quickly implement monitoring metrics and their exposure, facilitating comprehensive system monitoring and timely warnings." +--- + +The monitoring and alerting feature has been available since framework version `v2.7`. + +Developers can quickly implement monitoring metrics and metric exposure through the `GoFrame` framework. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..3c1c4ecfb70 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" @@ -0,0 +1,95 @@ +--- +slug: '/docs/obs/metrics-builtin' +title: 'Metrics - Built-in Metrics' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Built-in Metrics, Monitoring Alerts, Metric Types, Performance Monitoring, Prometheus, OpenTelemetry, Performance Optimization, Go Basic Metrics] +description: "Usage of built-in metrics for monitoring alerts in the GoFrame framework, including how to enable Go basic metrics with otelmetric, and how to integrate with Prometheus and OpenTelemetry for performance monitoring and optimization. The document provides example code and detailed descriptions of metrics, including metric names, types, and descriptions, to assist users in understanding and implementing performance monitoring." +--- + +## Introduction + +The framework includes built-in `Go` basic metrics, which are disabled by default and need to be manually enabled. This can be done by enabling `otelmetric.WithBuiltInMetrics()` when creating the `Provider`. + +```go +package main + +import ( + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + otelmetric.WithBuiltInMetrics(), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +After execution, visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to view the results. + +![](/markdown/daf1d8449208ba307efd483c505b7b5a.png) + +## Built-in Metrics Description + +| **Metric Name** | **Metric Type** | **Metric Unit** | **Metric Description** | +| --- | --- | --- | --- | +| `process.runtime.go.cgo.calls` | `Counter` | | Number of `cgo` calls in the current process. | +| `process.runtime.go.gc.count` | `Counter` | | Number of completed `gc` cycles. | +| `process.runtime.go.gc.pause_ns` | `Histogram` | `ns` | Number of nanoseconds paused in `GC stop-the-world `. | +| `process.runtime.go.gc.pause_total_ns` | `Counter` | `ns` | Cumulative microseconds count of `GC stop-the-world ` since the program started. | +| `process.runtime.go.goroutines` | `Gauge` | | Number of currently running goroutines. | +| `process.runtime.go.lookups` | `Counter` | | Number of pointer lookups executed at runtime. | +| `process.runtime.go.mem.heap_alloc` | `Gauge` | `bytes` | Number of bytes allocated to heap objects. | +| `process.runtime.go.mem.heap_idle` | `Gauge` | `bytes` | Idle (unused) heap memory. | +| `process.runtime.go.mem.heap_inuse` | `Gauge` | `bytes` | Heap memory in use. | +| `process.runtime.go.mem.heap_objects` | `Gauge` | | Number of heap objects allocated. | +| `process.runtime.go.mem.live_objects` | `Gauge` | | Number of live objects (`Mallocs - Frees`). | +| `process.runtime.go.mem.heap_released` | `Gauge` | `bytes` | Heap memory returned to the operating system. | +| `process.runtime.go.mem.heap_sys` | `Gauge` | `bytes` | Heap memory obtained from the operating system. | +| `process.runtime.uptime` | `Counter` | `ms` | Number of milliseconds since the application was initialized. | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..f06cfccb3ca --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/obs/metrics-sync' +title: 'Metrics - Synchronous Metrics' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Monitoring, Synchronous Metrics, Counter, UpDownCounter, Histogram, Prometheus, OpenTelemetry, Metrics Export, Performance Monitoring] +description: "Using synchronous metrics in the GoFrame framework to quickly expose and record HTTP request-related data through types such as Counter, UpDownCounter, and Histogram provided by gmetric. Implementing metrics output with the Prometheus protocol for external monitoring tools to capture and analyze, achieving effective performance monitoring and management." +--- + +## Introduction + +**Synchronous Type** is used to quickly expose monitoring metrics. Whether or not the `metrics reader` uses the monitoring metrics, the metric calculation results are completed and await reading. For example, total HTTP request count and request size must be recorded in the corresponding monitoring metrics during the request execution process, making them suitable for management as synchronous metrics. + +The synchronous metrics provided by `gmetric` include: `Counter, UpDownCounter, Histogram`. + +We will demonstrate the basic usage of synchronous metrics with a simple example. + +```go +package main + +import ( + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) + upDownCounter = meter.MustUpDownCounter( + "goframe.metric.demo.updown_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for UpDownCounter usage", + Unit: "%", + }, + ) + histogram = meter.MustHistogram( + "goframe.metric.demo.histogram", + gmetric.MetricOption{ + Help: "This is a simple demo for histogram usage", + Unit: "ms", + Buckets: []float64{0, 10, 20, 50, 100, 500, 1000, 2000, 5000, 10000}, + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider(otelmetric.WithReader(exporter)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // UpDownCounter. + upDownCounter.Inc(ctx) + upDownCounter.Add(ctx, 10) + upDownCounter.Dec(ctx) + + // Record values for histogram. + histogram.Record(1) + histogram.Record(20) + histogram.Record(30) + histogram.Record(101) + histogram.Record(2000) + histogram.Record(9000) + histogram.Record(20000) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +## Counter & UpDownCounter + +`Counter` and `UpDownCounter` are relatively simple, so they won't be elaborated here. However, it is important to note that although `Counter` and `UpDownCounter` appear similar, they serve to more accurately and finely distinguish usage scenarios. Mapping these data types to classic `Prometheus` metric types, `Counter` corresponds to the `Counter` metric type of `Prometheus`, whereas `UpDownCounter` corresponds to the `Gauge` metric type of `Prometheus`. + +## Histogram + +`Histogram` is a statistical type that allows for the quick computation of percentile statistics such as `p95, p99`, resulting in a histogram of metrics like time consumption and success/failure rates. Note that this metric uses considerable memory and space, so it's inadvisable to add too many **dynamic attributes**, as different attributes may derive different storage values for the same `Histogram` metric. + +## Prometheus Exporter + +In this example, we have used the commonly used `Prometheus` protocol to output metric contents, typically to expose metrics for external components to capture. The metrics are exposed using the following route with the `Prometheus` protocol: + +``` +otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +``` + +After execution, visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to view the exposed metrics: + +![](/markdown/50c5c45e521aa19633873aa9f9186ac3.png) + +We focus only on the metrics in this example, while other automatically exposed metrics will be introduced in subsequent sections. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..096f0c7d768 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" @@ -0,0 +1,124 @@ +--- +slug: '/docs/obs/metrics-intro' +title: 'Metrics - Intro' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Monitoring Alerts, OpenTelemetry, Metric Types, Plugin Interface, Metrics Standard, Abstract Decoupling Design, Synchronous Metrics, Asynchronous Metrics] +description: "The fundamentals of monitoring alerts in the GoFrame framework, focusing on OpenTelemetry's design specifications and components related to monitoring and alerting. Covers the relationships and data flow among components like Meter Provider, Meter, and Instrument. The article also explains how the framework achieves observability through the gmetric component by adopting an abstract decoupling design, supporting various types of synchronous and asynchronous metrics, aiding developers in handling and extending monitoring functionalities flexibly." +--- + +Before introducing the framework's monitoring alerts, we cannot avoid introducing industry standard observability and the design and specifications of `OpenTelemetry` concerning monitoring alerts. `OpenTelemetry` has a lot of content on this front, and we will highlight some key points. + +## OpenTelemetry + +### Related Components + +Let's look at a diagram showing the relationships between `OpenTelemetry` components. In the implementation of the `OpenTelemetry Metrics` standard, the following components are primarily included. + +![](/markdown/f7c048b9050aa8d6c17f85e1dc1c0540.png) + +In the implementation of standardized documentation, each component is typically designed using interfaces to enhance scalability: + +#### Meter Provider + +Used for interfacing with global management of `Meter` creation, akin to a global monitoring metrics management factory. + +#### Meter + +Used for interfacing with the creation and management of global `Instruments`. Different `Meters` can be regarded as different program components. For example, various components in a framework can be seen as different `Meters`, like `gclient` and `ghttp` being two different `Meters`. + +#### Instrument + +Used to manage the various types of metrics under different components, such as `http.server.request.duration`, `http.server.request.body_size`, etc., under `ghttp`. + +#### Measurements + +The specific `DataPoint` metrics data reported for metrics, consisting of a series of numerical items. + +#### View + +Implements operations like calculation, aggregation, filtering, and modification on `Measurements`. Since metrics are usually **numerical types**, the default `View` is typically used. + +#### Metric Reader + +Used for implementing data flow readings of metrics and defining specific metric data structures internally. The `OpenTelemetry` community provides various flexible `Reader` implementations, such as `PeridRader`, `ManualReader`, etc. + +#### Metric Exporter + +The `Exporter` is used to expose local metrics to corresponding third-party vendors and define whether data transfer is `push` or `pull`. `Exporter` leverages `Reader`, and while there are only several ways for `Reader`, `Exporter` varies by vendor, with many available, such as `prometheus`, `zipkin`, etc. + +The data flow of multiple `DataPoints` for one `Instrument` is shown below: + +![](/markdown/5d476c4969a41f996ac8c39a6d841f81.png) + +### Related Types + +The `OpenTelemetry` community implementation aims to meet various usage scenarios, hence the detailed granularity of type designs. It includes `int64` and `float64` data types and encompasses both **synchronous** and **asynchronous** metric types. + +#### Synchronous Types + +**Synchronous types** are used for promptly exposing monitoring metrics. Regardless of whether the `metrics reader` employs the metric, the calculation results are completed and ready for reading. For example, HTTP request total count and request size must be recorded in the monitoring metrics during the request execution flow, making them suitable for management as synchronous metrics. + +| **Type** | **Description** | **Example** | +| --- | --- | --- | +| `Int64Counter` | `int64` metrics that only increase. | Total request count, total request byte size | +| `Int64UpDownCounter` | `int64` metrics that can increase or decrease. | Current active requests, execution queue size | +| `Float64Counter` | `float64` metrics that only increase. | Total request count, total request byte size | +| `Float64UpDownCounter` | `float64` metrics that can increase or decrease. | Total request count, total request byte size | +| `Int64Histogram` | `int64` metrics that can be grouped. | Request execution time `p99` | +| `Float64Histogram` | `float64` metrics that can be grouped. | Request execution time `p99` | + +#### Asynchronous Types + +Monitoring metrics of **asynchronous types** execute metric calculation logic only when the `metrics reader` begins using the metric. Asynchronous-type metrics require a callback function that generates metric values, triggering only when the `metrics reader` reads the metric. For examples like machine CPU, memory, and disk usage metrics, if there's no pulling or use by the target end, pre-calculating metric values is meaningless and wastes computing resources, making them suitable for management as asynchronous metrics. + +| **Type** | **Description** | **Example** | +| --- | --- | --- | +| `Int64ObservableCouter` | `int64` metrics that only increase. | CPU, memory, disk usage | +| `Int64ObservableUpDownCounter` | `int64` metrics that can increase or decrease. | CPU, memory, disk usage | +| `Float64ObservableCouter` | `float64` metrics that only increase. | CPU, memory, disk usage | +| `Float64ObservableUpDownCounter` | `float64` metrics that can increase or decrease. | Current active requests, execution queue size | +| `Int64ObservableGauge` | `int64` metrics that can increase or decrease. | CPU, memory, disk usage | +| `Float64ObservableGauge` | `float64` metrics that can dynamically increase or decrease. | CPU, memory, disk usage | + +## Framework Monitoring Components + +### Component Abstraction + +The framework achieves monitoring capabilities through the `gmetric` component, with its internal component hierarchy design similar to `OpenTelemetry Metrics`: + +![](/markdown/a1f33528941fcf91e87b87aa8c0219cd.png) + +The `gmetric` component employs an **abstract decoupling design**, partly because the framework aims to reduce external dependencies and partly to achieve automatic switching of monitoring capabilities. By default, the component uses an implementation object of `NoopPerform`, where monitoring capabilities are disabled; they automatically enable once a monitoring interface implementation is introduced. + +![](/markdown/99374a3d9b7e4805c5c7c0bd3fefb221.png) + +### Metric Types + +The metric types provided by the framework simplify by removing the `int64` numerical type in contrast to `OpenTelemetry`, using a unified `float64` numerical type. However, it is crucial for developers to **avoid designing values as decimals to prevent precision issues**. This is particularly vital in `Histogram` type `Buckets` design, where using decimals is not recommended. + +#### Synchronous Types + +| **Type** | **Description** | **Example** | +| --- | --- | --- | +| `Counter` | `float64` metrics that only increase. | Total request count, total request byte size | +| `UpDownCounter` | `float64` metrics that can increase or decrease. | Total request count, total request byte size | +| `Histogram` | `float64` metrics that can be grouped. | Request execution time `p99` | + +#### Asynchronous Types + +| **Type** | **Description** | **Example** | +| --- | --- | --- | +| `ObservableCounter` | `float64` metrics that only increase. | Total request count, total request byte size | +| `ObservableUpDownCounter` | `float64` metrics that can increase or decrease. | Total request count, total request byte size | +| `ObservableGauge` | `float64` metric that can dynamically increase or decrease. | CPU, memory, disk usage | + +## Reference Materials + +- [https://github.com/prometheus/client_golang](https://github.com/prometheus/client_golang%20"https://github.com/prometheus/client_golang") + +- [https://github.com/open-telemetry/opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib%20"https://github.com/open-telemetry/opentelemetry-go-contrib") + +- [https://opentelemetry.io/docs/specs/otel/metrics/api/](https://opentelemetry.io/docs/specs/otel/metrics/api/%20"https://opentelemetry.io/docs/specs/otel/metrics/api/") + +- [https://opentelemetry.io/docs/specs/otel/metrics/data-model](https://opentelemetry.io/docs/specs/otel/metrics/data-model%20"https://opentelemetry.io/docs/specs/otel/metrics/data-model") \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..1ebf1935a7f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/obs/metrics-example' +title: 'Metrics - Usage' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Monitoring Indicators, otelmetric, OpenTelemetry, Metric Management, Counter, MetricOption, gmetric, Data Reading, Metric Implementation] +description: "Use the gmetric component in the GoFrame framework to develop monitoring metrics. By introducing the otelmetric component, the OpenTelemetry can be used to implement the framework's monitoring metrics interface. The article describes in detail the creation of metric management objects, the use and initialization methods of various monitoring metric objects, and demonstrates how to read and manipulate metric data through code examples." +--- + +## Introduction + +The code development of monitoring metrics directly uses the `gmetric` component of the main library of the framework. However, since the `gmetric` component actually only defines the relevant interfaces for monitoring metrics and provides the default `NoopPerformer`, the default monitoring metrics feature is turned off. Therefore, it is necessary to introduce specific interface implementation components to truly enable the monitoring metrics feature. The framework community provides the community component `github.com/gogf/gf/contrib/metric/otelmetric/v2`, which uses `OpenTelemetry` to implement the monitoring metrics interface of the framework. By introducing this community component and executing the creation of monitoring metrics management objects, the monitoring metrics feature can be enabled. The source code address of the `otelmetric` component: [https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +We will introduce the basic usage of the monitoring metrics component through a simple monitoring metrics implementation example. + +```go +package main + +import ( + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) +) + +func main() { + var ( + ctx = gctx.New() + reader = metric.NewManualReader() + ) + + provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + counter.Inc(ctx) + counter.Add(ctx, 10) + + var ( + rm = metricdata.ResourceMetrics{} + err = reader.Collect(ctx, &rm) + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + g.DumpJson(rm) +} +``` + +## Creation of Metric Management Components + +The global monitoring **metric management object** can be obtained through the `gmetric.GetGlobalProvider()` method. **This object is a singleton design, and there can only be one globally.** The `Meter` method of this object can be used to create/get the corresponding **component object**. The component object is used to manage all the monitoring metrics under this component. When creating a component object, it is usually necessary to define the name and version of the component (`Instrument`) (although it can also be empty, it is recommended to set it for easy maintenance in the future). + +``` +meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", +}) +``` + +The `gmeter.MeterOption` data structure is as follows: + +```go +// MeterOption holds the creation option for a Meter. +type MeterOption struct { + // Instrument is the instrumentation name to bind this Metric to a global MeterProvider. + // This is an optional configuration for a metric. + Instrument string + + // InstrumentVersion is the instrumentation version to bind this Metric to a global MeterProvider. + // This is an optional configuration for a metric. + InstrumentVersion string + + // Attributes holds the constant key-value pair description metadata for all metrics of Meter. + // This is an optional configuration for a meter. + Attributes Attributes +} +``` + +## Creation of Monitoring Metric Objects + +Through the `Meter` interface object, we can create the corresponding various metrics under this component. Metrics have various data types, so the definition of the `Meter` interface is as follows: + +```go +// Meter hold the functions for kinds of Metric creating. +type Meter interface { + // Counter creates and returns a new Counter. + Counter(name string, option MetricOption) (Counter, error) + + // UpDownCounter creates and returns a new UpDownCounter. + UpDownCounter(name string, option MetricOption) (UpDownCounter, error) + + // Histogram creates and returns a new Histogram. + Histogram(name string, option MetricOption) (Histogram, error) + + // ObservableCounter creates and returns a new ObservableCounter. + ObservableCounter(name string, option MetricOption) (ObservableCounter, error) + + // ObservableUpDownCounter creates and returns a new ObservableUpDownCounter. + ObservableUpDownCounter(name string, option MetricOption) (ObservableUpDownCounter, error) + + // ObservableGauge creates and returns a new ObservableGauge. + ObservableGauge(name string, option MetricOption) (ObservableGauge, error) + + // MustCounter creates and returns a new Counter. + // It panics if any error occurs. + MustCounter(name string, option MetricOption) Counter + + // MustUpDownCounter creates and returns a new UpDownCounter. + // It panics if any error occurs. + MustUpDownCounter(name string, option MetricOption) UpDownCounter + + // MustHistogram creates and returns a new Histogram. + // It panics if any error occurs. + MustHistogram(name string, option MetricOption) Histogram + + // MustObservableCounter creates and returns a new ObservableCounter. + // It panics if any error occurs. + MustObservableCounter(name string, option MetricOption) ObservableCounter + + // MustObservableUpDownCounter creates and returns a new ObservableUpDownCounter. + // It panics if any error occurs. + MustObservableUpDownCounter(name string, option MetricOption) ObservableUpDownCounter + + // MustObservableGauge creates and returns a new ObservableGauge. + // It panics if any error occurs. + MustObservableGauge(name string, option MetricOption) ObservableGauge + + // RegisterCallback registers callback on certain metrics. + // A callback is bound to certain component and version, it is called when the associated metrics are read. + // Multiple callbacks on the same component and version will be called by their registered sequence. + RegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric) error + + // MustRegisterCallback performs as RegisterCallback, but it panics if any error occurs. + MustRegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric) +} +``` + +Taking the `meter.MustCounter` method used in this example code as an introduction, this method creates a `Counter` synchronous metric, and since we lazily use the `Must*` method, it means that if the creation of the metric fails, then this method will `panic` with an error. When creating a metric object, the metric name `name` is a required parameter, and the other `MetricOption` is optional, used to further describe the metric information. The data structure of `MetricOption` is defined as follows: + +```go +// MetricOption holds the basic options for creating a metric. +type MetricOption struct { + // Help provides information about this Histogram. + // This is an optional configuration for a metric. + Help string + + // Unit is the unit for metric value. + // This is an optional configuration for a metric. + Unit string + + // Attributes holds the constant key-value pair description metadata for this metric. + // This is an optional configuration for a metric. + Attributes Attributes + + // Buckets defines the buckets into which observations are counted. + // For Histogram metric only. + // A histogram metric uses default buckets if no explicit buckets configured. + Buckets []float64 + + // Callback function for metric, which is called when metric value changes. + // For observable metric only. + // If an observable metric has either Callback attribute nor global callback configured, it does nothing. + Callback MetricCallback +} +``` + +## Initialization of Monitoring Metric Implementation + +The creation and initialization of the monitoring metric management object can be done through the `otelmetric.MustProvider` creation method. + +```go +provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) +provider.SetAsGlobal() +defer provider.Shutdown(ctx) +``` + +As introduced earlier, `GlobalProvider` is actually a singleton metric management object, so the `provider.SetAsGlobal` method call here can set the object as a global metric management object, which facilitates the subsequent creation of metrics based on this object. + +We call the `defer provider.ShutDown` method in the `main` function to facilitate a graceful shutdown of the metric management object when the program ends, allowing, for example, the timely output of indicator caches to the target end. + +## Usage of Monitoring Metric Objects + +Different metric objects have different operational methods for implementing changes in metric values. Taking the `Counter` metric type used in the example as an example, its interface is defined as follows: + +```go +// Counter is a Metric that represents a single numerical value that can ever +// goes up. +type Counter interface { + Metric + CounterPerformer +} + +// CounterPerformer performs operations for Counter metric. +type CounterPerformer interface { + // Inc increments the counter by 1. Use Add to increment it by arbitrary + // non-negative values. + Inc(ctx context.Context, option ...Option) + + // Add adds the given value to the counter. It panics if the value is < 0. + Add(ctx context.Context, increment float64, option ...Option) +} +``` + +As can be seen, the `Counter` metric can primarily perform two operation methods, `Inc` and `Add`. There is also a `Metric` interface, and all metrics implement this interface to obtain basic information of the current metric. The interface is defined as follows: + +```go +// Metric models a single sample value with its metadata being exported. +type Metric interface { + // Info returns the basic information of a Metric. + Info() MetricInfo +} + +// MetricInfo exports information of the Metric. +type MetricInfo interface { + Key() string // Key returns the unique string key of the metric. + Name() string // Name returns the name of the metric. + Help() string // Help returns the help description of the metric. + Unit() string // Unit returns the unit name of the metric. + Type() MetricType // Type returns the type of the metric. + Attributes() Attributes // Attributes returns the constant attribute slice of the metric. + Instrument() InstrumentInfo // InstrumentInfo returns the instrument info of the metric. +} + +// InstrumentInfo exports the instrument information of a metric. +type InstrumentInfo interface { + Name() string // Name returns the instrument name of the metric. + Version() string // Version returns the instrument version of the metric. +} +``` + +## Data Reading of Monitoring Metrics + +Through the `OpenTelemetry` component relationship introduced in the previous chapter, we know that if you want to use metrics, you must use a `MetricReader`. Therefore, in this example code, we use the most commonly used `ManualReader` in the open-source components of the `OpenTelemetry` official community to simply implement metric data reading. The `ManualReader` is provided by the `OpenTelemetry` official community. + +``` +reader = metric.NewManualReader() +``` + +And configured to `Provider` through the `WithReader` method: + +```go +provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) +``` + +Then, the current metric data can be obtained through the `Collect` method: + +```go +var ( + rm = metricdata.ResourceMetrics{} + err = reader.Collect(ctx, &rm) +) +if err != nil { + g.Log().Fatal(ctx, err) +} +g.DumpJson(rm) +``` + +After execution, the terminal output: + +``` +... + "ScopeMetrics": [ + { + "Scope": { + "Name": "github.com/gogf/gf/example/metric/basic", + "Version": "v1.0", + "SchemaURL": "" + }, + "Metrics": [ + { + "Name": "goframe.metric.demo.counter", + "Description": "This is a simple demo for Counter usage", + "Unit": "bytes", + "Data": { + "DataPoints": [ + { + "Attributes": [], + "StartTime": "2024-03-25T10:13:19.326977+08:00", + "Time": "2024-03-25T10:13:19.327144+08:00", + "Value": 11 + } + ], + "Temporality": "CumulativeTemporality", + "IsMonotonic": true + } + } + ] + }, +... +``` + +To simplify the example introduction, we have omitted some output contents here. For more detailed metric and output descriptions, please refer to the following chapters. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..fc3930bc043 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" @@ -0,0 +1,127 @@ +--- +slug: '/docs/obs/metrics-async' +title: 'Metrics - Asynchronous Metrics' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Asynchronous Metrics, Monitoring Alerts, ObservableCounter, ObservableUpDownCounter, ObservableGauge, Callback Function, Prometheus Export, OpenTelemetry] +description: "Using asynchronous monitoring metrics in the GoFrame framework, this document details the usage of three types of asynchronous metrics: ObservableCounter, ObservableUpDownCounter, and ObservableGauge. It explains how to define Callback functions to manage metric value changes and uses Prometheus to export metric data." +--- + +## Introduction + +**Asynchronous** monitoring metrics execute the metric calculation logic only when `metrics reader` starts using that metric. Asynchronous metrics require a callback function, which is used to generate metric values and is triggered only when the `metrics reader` reads the metric. For example, metrics for machine CPU, memory, and disk usage, if not pulled or used by the target end, calculating metric values in advance is meaningless and wasteful of computational resources, making them suitable to manage as asynchronous metrics. + +The asynchronous metrics provided by `gmetric` include: `ObservableCounter, ObservableUpDownCounter, OvservableGauge`. Asynchronous metric types are named starting with `Observable`, and operations for the three asynchronous metrics are quite similar, differing only in usage across different business scenarios. + +Let's demonstrate the basic usage of asynchronous metrics with a simple example. + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + }, + ) + observableUpDownCounter = meter.MustObservableUpDownCounter( + "goframe.metric.demo.observable_updown_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableUpDownCounter usage", + Unit: "%", + }, + ) + observableGauge = meter.MustObservableGauge( + "goframe.metric.demo.observable_gauge", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableGauge usage", + Unit: "%", + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) + obs.Observe(observableUpDownCounter, 20) + obs.Observe(observableGauge, 30) + return nil + }, observableCounter, observableUpDownCounter, observableGauge) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider(otelmetric.WithReader(exporter)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +## Meter Callback + +Asynchronous metrics require the definition of a `Callback` function to manage metric value changes, and this `Callback` function will only execute upon request or use of the metric. The `Observe` function is used within the `Callback` function to update the metric's value, producing different results for different types of asynchronous metrics. + +- For `ObservableCounter/ObservableUpDownCounter` metric types, using the `Observe` function will increment or decrement the existing metric value. +- For `ObservableGauge` metric types, using the `Observe` function will update the metric to the value given by `Observe`. + +## Metric Callback + +In addition to updating the value of asynchronous metrics using `Meter Callback`, you can also specify the `Callback` function by using `MetricOption` when creating metrics. For example: + +``` +observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Callback: func(ctx context.Context, obs gmetric.MetricObserver) error { + obs.Observe(10) + return nil + }, + }, +) +``` + +## Prometheus Exporter + +Expose metrics using the `Prometheus` protocol through the following route: + +``` +otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +``` + +After execution, visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to view exposed metrics: + +![](/markdown/5e79d0fe7ae3773ee055a5d600abe7dd.png) + +Here we only focus on the metrics from this example; other automatically exposed metrics will be introduced in subsequent sections. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" new file mode 100644 index 00000000000..9e88e7f05d3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" @@ -0,0 +1,325 @@ +--- +slug: '/docs/obs/metrics-attributes' +title: 'Metrics - Attributes' +sidebar_position: 4 +hide_title: true +keywords: [Monitoring Alerts, Metric Attributes, GoFrame, Constant Attributes, Variable Attributes, Global Attributes, OpenTelemetry, Prometheus, Meter, Metric Injection] +description: "Use metric attributes for filtering, aggregation, and statistics in the monitoring alerts component of the GoFrame framework. It provides three attribute injection methods: constant attributes, variable attributes, and global attributes, and demonstrates through specific examples how to apply these attributes in different scenarios. Combined with OpenTelemetry and Prometheus, it shows how to define and apply metric attributes to achieve flexible and efficient data monitoring and analysis." +--- + +Metric attributes are used for filtering, aggregation, statistical, and other high-level operations in higher-level metric usage. In the monitoring alerts component of the `GoFrame` framework, three types of attribute injection methods are provided: **constant attributes**, **variable attributes**, and **global attributes**. +:::tip +In `OpenTelemetry`, they are called metric attributes (`attributes`), but in `Prometheus`, they are called metric labels (`labels`), both meaning the same. +::: +## Constant Attributes + +**Constant attributes** are a set of fixed attribute key-value pairs that can be bound to a `Meter` or directly to a metric object. If bound to a `Meter`, all metric objects created under the `Meter` carry this attribute key-value pair. If bound to a metric object, it will only be effective on the current metric. Let's look at an example: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +As you can see, we specify constant attributes through the `Attributes` attribute field in the `MeterOption` or `MetricOption` parameters when creating a `Meter` object or a `Metric` object. + +Once executed, we visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to see the results. The constant attributes bound to the `Meter` took effect on both metrics, but the constant attributes bound to each metric only took effect on the corresponding metric. + +![](/markdown/7604946c482b5592bf13db15e99486f5.png) + +## Variable Attributes + +**Variable attributes** are attribute key-value pairs specified at runtime, usually determined only at runtime, and might vary based on different execution scenarios, hence called variable attributes. + +Let's look at an example: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_1", 1), + }, + }) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_2", 2), + }, + }) + counter.Add(ctx, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_3", 3), + }, + }) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +As shown, we specify metric variable attributes at runtime through the `Attributes` attribute field in the `Option` parameter. Variable attributes are quite flexible, and the same metric can use different variable attributes. + +Similarly, after execution, we visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to see the results. + +![](/markdown/17cd106aa40f6ca397486301bdaf16cd.png) + +## Global Attributes + +**Global attributes** are a more flexible metric attribute injection method, which can be automatically injected based on `Instrument` information and can determine whether to inject metric attributes into all metrics under a given `Instrument` based on regex matches of the `Instrument` name. + +Let's look at an example: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + + gmetric.SetGlobalAttributes(gmetric.Attributes{ + gmetric.NewAttribute("global_attr_1", 1), + }, gmetric.SetGlobalAttributesOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + InstrumentPattern: "", + }) + + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_1", 1), + }, + }) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_2", 2), + }, + }) + counter.Add(ctx, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_3", 3), + }, + }) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +Set global attributes through the `gmetric.SetGlobalAttributes` method, and restrict the affected metric range based on the parameter `gmetric.SetGlobalAttributesOption`. + +Similarly, after execution, visit [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) to see the results. You can see that the global attributes have been automatically added to the metrics. + +![](/markdown/dfc79773cb999c35208fe27c98e1ab48.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..699636dc044 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/obs/metrics-components' +title: 'Metrics - Component Metrics' +sidebar_position: 6 +hide_title: true +keywords: [Monitoring Alerts, Component Metrics, HTTP Client, HTTP Server, Monitoring Metrics, GoFrame, GoFrame Framework, WEB Service Development, Advanced Features, Performance Monitoring] +description: "Supported monitoring metrics for GoFrame framework components, including HTTP Client and HTTP Server. Readers can access more detailed monitoring metrics information through the document links. Metrics for other components will be provided in subsequent versions to ensure comprehensive system performance monitoring." +--- + +Supported monitoring metrics for the framework components, with other component metrics to be provided in subsequent versions. + +| Component | Document Address | +| --- | --- | +| `HTTP Client` | [HTTPClient - Metrics](../../WEB服务开发/HTTPClient/HTTPClient-监控指标.md) | +| `HTTP Server` | [HTTPServer - Metrics](../../WEB服务开发/高级特性/HTTPServer-监控指标.md) | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" new file mode 100755 index 00000000000..f4b93c2ceaf Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..75619a2d266 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/obs/tracing' +title: 'Service Tracing' +sidebar_position: 0 +hide_title: true +keywords: [Service Link Tracing, Distributed Link Tracing, GoFrame, GoFrame Framework, Link Tracing Implementation, Standardized Link Tracing, Distributed Systems, Tracing Features, Service Tracing, Performance Monitoring] +description: "The GoFrame framework supports service link tracing by implementing standardized distributed link tracing, helping developers monitor and analyze the interaction process between services. Utilizing this feature can effectively improve the system's performance and reliability, help pinpoint problem sources, optimize service architecture, and ensure efficient operation of the system in complex environments." +--- + +`GoFrame` implements standardized Distributed Tracing features. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..67eb5da598a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" @@ -0,0 +1,324 @@ +--- +slug: '/docs/obs/tracing-grpc-example' +title: 'Tracing - GRPC Example' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GRPC,Tracing,Microservices,Client,Server,Cache Adapter,Database,Jaeger,protobuf] +description: "Develop a simple GRPC server and client using the GoFrame framework, and add tracing features to GRPC microservices. The example code demonstrates how to initialize Jaeger, implement caching with a Redis adapter, and transmit trace information between the client and server." +--- + +In this chapter, we will modify the previously introduced `HTTP Client&Server` example to a `GRPC` microservice and demonstrate how to develop a simple `GRPC` server and client using the `GoFrame` framework and add tracing features to the `GRPC` microservice. + +The example code for this chapter is located at: [https://github.com/gogf/gf/tree/master/example/trace/grpc\_with\_db](https://github.com/gogf/gf/tree/master/example/trace/grpc_with_db) + +## Directory Structure + +![](/markdown/e9fe7410038348854e83de6cb3e35e32.png) + +## Protobuf + +``` +syntax = "proto3"; + +package user; + +option go_package = "protobuf/user"; + +// User service for tracing demo. +service User { + rpc Insert(InsertReq) returns (InsertRes) {} + rpc Query(QueryReq) returns (QueryRes) {} + rpc Delete(DeleteReq) returns (DeleteRes) {} +} + +message InsertReq { + string Name = 1; // v: required#Please input user name. +} +message InsertRes { + int32 Id = 1; +} + +message QueryReq { + int32 Id = 1; // v: min:1#User id is required for querying. +} +message QueryRes { + int32 Id = 1; + string Name = 2; +} + +message DeleteReq { + int32 Id = 1; // v:min:1#User id is required for deleting. +} +message DeleteRes {} +``` + +Use the `gf gen pb` command to compile this `proto` file, which will generate the corresponding `grpc` interface and data structure files. + +## GRPC Server + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" + + "context" + "fmt" + "time" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/contrib/trace/otlpgrpc/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +type Controller struct { + user.UnimplementedUserServer +} + +const ( + serviceName = "otlp-grpc-server" + endpoint = "tracing-analysis-dc-bj.aliyuncs.com:8090" + traceToken = "******_******" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ctx = gctx.New() + shutdown, err := otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + // Set ORM cache adapter with redis. + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + + s := grpcx.Server.New() + user.RegisterUserServer(s.Server, &Controller{}) + s.Run() +} + +// Insert is a route handler for inserting user info into database. +func (s *Controller) Insert(ctx context.Context, req *user.InsertReq) (res *user.InsertRes, err error) { + result, err := g.Model("user").Ctx(ctx).Insert(g.Map{ + "name": req.Name, + }) + if err != nil { + return nil, err + } + id, _ := result.LastInsertId() + res = &user.InsertRes{ + Id: int32(id), + } + return +} + +// Query is a route handler for querying user info. It firstly retrieves the info from redis, +// if there's nothing in the redis, it then does db select. +func (s *Controller) Query(ctx context.Context, req *user.QueryReq) (res *user.QueryRes, err error) { + err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: 5 * time.Second, + Name: s.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Scan(&res) + if err != nil { + return nil, err + } + return +} + +// Delete is a route handler for deleting specified user info. +func (s *Controller) Delete(ctx context.Context, req *user.DeleteReq) (res *user.DeleteRes, err error) { + err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: s.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Scan(&res) + return +} + +func (s *Controller) userCacheKey(id int32) string { + return fmt.Sprintf(`userInfo:%d`, id) +} +``` + +Brief explanation of the server code: + +1. First, the server needs to initialize `Jaeger` through the `jaeger.Init` method. + +2. As you can see, the business logic is entirely consistent with the previous HTTP example project, only the access layer is modified for the GRPC protocol. + +3. We still inject Redis cache via the cache adapter: + +```go +g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) +``` + +4. Here, we also enable the cache feature of `ORM` using the `Cache` method, which has already been introduced before and will not be elaborated on here. + +## GRPC Client + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/contrib/trace/otlpgrpc/v2" + "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-grpc-client" + endpoint = "tracing-analysis-dc-bj.aliyuncs.com:8090" + traceToken = "******_******" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ctx = gctx.New() + shutdown, err := otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + client := user.NewUserClient(grpcx.Client.MustNewGrpcClientConn("demo")) + + // Baggage. + ctx = gtrace.SetBaggageValue(ctx, "uid", 100) + + // Insert. + insertRes, err := client.Insert(ctx, &user.InsertReq{ + Name: "john", + }) + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + g.Log().Info(ctx, "insert id:", insertRes.Id) + + // Query. + queryRes, err := client.Query(ctx, &user.QueryReq{ + Id: insertRes.Id, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "query result:", queryRes) + + // Delete. + _, err = client.Delete(ctx, &user.DeleteReq{ + Id: insertRes.Id, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "delete id:", insertRes.Id) + + // Delete with error. + _, err = client.Delete(ctx, &user.DeleteReq{ + Id: -1, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "delete id:", -1) +} +``` + +Brief explanation of the client code: + +1. First, the client also needs to initialize `Jaeger` through the `jaeger.Init` method. + +2. The client is very simple, internal initialization and setting of default interceptors have been encapsulated by the `Katyusha` framework, developers need only focus on implementing business logic. + +## Effect Viewing + +**Start the Server:** + +![](/markdown/3470e30ff88d5fdafcaaa175ed5d40a6.png) + +**Start the Client:** + +![](/markdown/3730fb2264d7e19a4990cdf8470c3998.png) + +At the end of the client execution here, an error was reported, which we did **intentionally**, to demonstrate the link information display when `GRPC` has an error. Let's open `jaeger` to check the tracing information: + +![](/markdown/933c5a2bda5208eaf0b92231923a8334.png) + +You can see that this request involves two services: `tracing-grpc-client` and `tracing-grpc-server`, which are the client and the server. The entire request link involves `17` spans, with the client having `5` spans, the server having `12` spans, and producing `2` errors. Let's click to view the details: + +![](/markdown/4ebecd20a7894a222dfacffe33ccf262.png) + +Let's click to check the span situation of the last interface call error: + +![](/markdown/f0e9b3892dbbb628757686a311e8bbf0.png) + +It looks like a parameter validation error, click to view the request parameters in `Events/Logs`: + +![](/markdown/a9c5cdcd7e86c22f926fd20a141f3d68.png) + +Checking the `Log` information in `Process`, it can be seen that the error occurred because the parameter passed was `-1`, which did not meet the validation rules, hence an error was returned during data validation. + +### GRPC Client + +Since `orm`, `redis`, and `logging` components have previously introduced link information, here we mainly introduce link information for `GRPC Client&Server`. + +#### Attributes + +![](/markdown/273442cb521050b63863e94ac9334d68.png) + +| Attribute/Tag | Description | +| --- | --- | +| `net.peer.ip` | Target IP of the request. | +| `net.peer.port` | Target port of the request. | +| `rpc.grpc.status_code` | Internal status code for `GRPC`, `0` indicates success, `non-0` indicates failure. | +| `rpc.service` | Name of the `RPC` service, note that this is `RPC` not `GRPC`, as this is a general definition, and the client supports multiple `RPC` communication protocols, with `GRPC` being one of them. | +| `rpc.method` | Name of the `RPC` method. | +| `rpc.system` | Type of `RPC` protocol, such as: `grpc`, `thrift`, etc. | + +#### Events/Logs + +![](/markdown/3ee7778da75473938eb5acd4459304a5.png) + +| Event/Log | Description | +| --- | --- | +| `grpc.metadata.outgoing` | `GRPC` client request submitted `Metadata` information, which may be large. | +| `grpc.request.baggage` | `GRPC` client request submitted `Baggage` information for service-to-service trace information transmission. | +| `grpc.request.message` | `GRPC` client request submitted `Message` data, which may be large, only `512KB` is recorded, and if it exceeds that size, it is ignored. Only valid for `Unary` request types. | +| `grpc.response.message` | `GRPC` client request received returned `Message` information, which may be large. Only valid for `Unary` request types. | + +### GRPC Server + +#### Attributes + +![](/markdown/b6a1d35aebb050058c9305cfb49a4bff.png) + +`GRPC Server` attributes mean the same as `GRPC Client`, with parsed data being almost identical in the same request. + +#### Events + +![](/markdown/88e292828c1785d7e6bd1ba5af191414.png) + +The difference in `Events` for the `GRPC Server` compared to the `GRPC Client` is that in the same request, the metadata received by the server is `grpc.metadata.incoming`, while others are the same as the `GRPC Client`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" new file mode 100644 index 00000000000..8f9d1b86733 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" @@ -0,0 +1,184 @@ +--- +slug: '/docs/obs/tracing-http-example-baggage' +title: 'Tracing HTTP - Baggage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame framework,tracing,HTTP example,Baggage,client,server,Jaeger,OTLP,TraceId] +description: "Using the GoFrame framework for tracing, focusing on demonstrating the method of transmitting Baggage data between services through an HTTP example. It provides a detailed explanation of client and server code implementation, including how to set and get Baggage, and offers a way to view link information through Jaeger, providing a practical guide for developers to achieve efficient tracing in distributed systems." +--- + +## `baggage` Link Data Transmission + +`baggage` transmits custom information between links (between services). + +Example code address: [https://github.com/gogf/gf/tree/master/example/trace/http](https://github.com/gogf/gf/tree/master/example/trace/http) + +## Client + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" +) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + ctx = gtrace.SetBaggageValue(ctx, "name", "john") + + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/hello") + g.Log().Print(ctx, content) +} +``` + +Brief explanation of client code: + +1. First, the client also needs to initialize `Jaeger` through the `jaeger.Init` method. +2. Then, using `gtrace.SetBaggageValue(ctx, "name", "john")`, a `baggage` is set, which will be transmitted in all links of the request. However, in this example, there are only two nodes, so the `baggage` data will only be transmitted to the server. This method will return a new `context.Context` variable, which we will need to pass in the subsequent call chain. +3. Here, an HTTP client request object is created using `g.Client()`, which automatically enables tracing features without requiring developers to explicitly call any methods or settings. +4. Finally, `g.Log().Print(ctx, content)` is used to print the server's return content, where `ctx` is used to pass link information to the logging component. If the `ctx` context object contains link information, the logging component will automatically include the `TraceId` in the log content. + +## Server + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-server" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/hello", HelloHandler) + }) + s.SetPort(8199) + s.Run() +} + +func HelloHandler(r *ghttp.Request) { + ctx, span := gtrace.NewSpan(r.Context(), "HelloHandler") + defer span.End() + + value := gtrace.GetBaggageVar(ctx, "name").String() + + r.Response.Write("hello:", value) +} +``` + +Brief explanation of server code: + +1. Of course, the server also needs to initialize `Jaeger` through the `jaeger.Init` method. +2. The server starts with tracing enabled, and developers do not need to call any methods or configure any settings explicitly. +3. The server uses `gtrace.GetBaggageVar(ctx, "name").String()` to obtain the `baggage` information submitted by the client and converts it to a string for return. + +## View the Effects + +**Start the server:** + +![](/markdown/54c448f0e2863c1159a5470adc52aac8.png) + +**Start the client:** + +![](/markdown/8237ff1b02be36ce1b4e9c160be80d26.png) + +As you can see, the `baggage` submitted by the client has been successfully received and printed by the server. Also, the client outputs the `TraceId` information when logging. `TraceId` is a unique ID for a link, and it can be used to retrieve all log information for that link as well as query the detailed call chain on the `Jaeger` system. + +View the link information on `Jaeger`: + +![](/markdown/83cd3fa37aab22f429df13682afdbe30.png) + +As seen here, there are two service names: `tracing-http-client` and `tracing-http-server`, indicating that this request involves two services, specifically the HTTP request client and server, and each service involves `2` `span` link nodes. + +When clicking on the details of this `trace`, you can see the hierarchical relationship of the call chain. Additionally, you can find the client's request address, the server's received route, and the server route function names. Let's introduce the `Attributes` and `Events` information of the client, or the `Tags` and `Process` information shown in `Jaeger`. + +### HTTP Client Attributes + +![](/markdown/dcbb5e8e0444a4ce3b433aaa4308222c.png) + +| Attribute/Tag | Description | +| --- | --- | +| `otel.instrumentation_library.name` | Current instrument name, usually the component name of the current `span` operation | +| `otel.instrumentation_library.version` | Current instrument component version | +| `span.kind` | Type of the current `span`, generally written by the component automatically. Common `span` types include: + +| Type | Description | +| --- | --- | +| `client` | Client | +| `server` | Server | +| `producer` | Producer, commonly used in MQ | +| `consumer` | Consumer, commonly used in MQ | +| `internal` | Internal method, generally used in business | +| `undefined` | Undefined, rarely used | | +| `status.code` | Current `span` status, `0` is normal, `non-zero` indicates failure | +| `status.message` | Current `span` status information, often contains error information when failed | +| `hostname` | Hostname of the current node | +| `ip.intranet` | Intranet address list of the current node | +| `http.address.local` | Local address and port of HTTP communication | +| `http.address.remote` | Target address and port of HTTP communication | +| `http.dns.start` | Domain name address to resolve when the target address of the request contains a domain name | +| `http.dns.done` | IP address after domain name resolution when the request target address contains a domain name | +| `http.connect.start` | Type and address where connection creation starts | +| `http.connect.done` | Type and address after successful connection creation | + +### HTTP Client Events + +![](/markdown/9d35a850c1713efc19d56ec3ac990013.png) + +| Event/Log | Description | +| --- | --- | +| `http.request.headers` | `Header` information submitted by the HTTP client request, which may be large. | +| `http.request.baggage` | `Baggage` information submitted by the HTTP client request for inter-service link information transmission. | +| `http.request.body` | `Body` data submitted by the HTTP client request, which may be large. Only records up to `512KB`, exceeding which will be ignored. | +| `http.response.headers` | `Header` information returned by the HTTP client request, which may be large. | +| `http.response.body` | `Body` data returned by the HTTP client request, which may be large. Only records up to `512KB`, exceeding which will be ignored. | + +### HTTP Server Attributes + +![](/markdown/4df0d3d8e5de018788b9b134ea13d535.png) + +The `Attributes` of the `HTTP Server` side are the same as those of the `HTTP Client`, and similar data is printed in the same request. + +### HTTP Server Events + +![](/markdown/f951072d1f5f116b90350788ad71bc89.png) + +The `Events` of the `HTTP Server` side are the same as those of the `HTTP Client`, and similar data is printed in the same request. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..7cdb599afeb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" @@ -0,0 +1,312 @@ +--- +slug: '/docs/obs/tracing-http-example-with-database' +title: 'Tracing HTTP - Data Ops' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Tracing, HTTP Example, Database Operations, Cache Management, ORM, Redis, OTLP, Trace, Jaeger] +description: "This document provides a detailed introduction on how to implement tracing in the GoFrame framework, demonstrating end-to-end tracing using OTLP through HTTP and database operation examples. The example integrates cache management, database operations, and Redis usage, allowing for viewing detailed trace information through Jaeger and analyzing tracing data across clients and servers to help developers optimize and debug the full process." +--- + +## `HTTP+DB+Redis+Logging` + +Let's look at a relatively complete example that includes several commonly used core components for tracing. The example code is available at: [https://github.com/gogf/gf/tree/master/example/trace/http\_with\_db](https://github.com/gogf/gf/tree/master/example/trace/http_with_db) + +## Client + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + var ( + err error + client = g.Client() + ) + // Add user info. + var insertRes = struct { + ghttp.DefaultHandlerResponse + Data struct{ Id int64 } `json:"data"` + }{} + err = client.PostVar(ctx, "http://127.0.0.1:8199/user/insert", g.Map{ + "name": "john", + }).Scan(&insertRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "insert result:", insertRes) + if insertRes.Data.Id == 0 { + g.Log().Error(ctx, "retrieve empty id string") + return + } + + // Query user info. + var queryRes = struct { + ghttp.DefaultHandlerResponse + Data struct{ User gdb.Record } `json:"data"` + }{} + err = client.GetVar(ctx, "http://127.0.0.1:8199/user/query", g.Map{ + "id": insertRes.Data.Id, + }).Scan(&queryRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "query result:", queryRes) + + // Delete user info. + var deleteRes = struct { + ghttp.DefaultHandlerResponse + }{} + err = client.PostVar(ctx, "http://127.0.0.1:8199/user/delete", g.Map{ + "id": insertRes.Data.Id, + }).Scan(&deleteRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "delete result:", deleteRes) +} +``` + +Brief explanation of the client code: + +1. First, the client needs to initialize `Jaeger` via the `jaeger.Init` method. +2. In this example, we send `3` requests to the server via HTTP client: + 1. `/user/insert` is used to add a new user and returns the user's ID upon success. + 2. `/user/query` is used to query users using the user ID returned from the previous interface. + 3. `/user/delete` is used to delete users using the user ID returned from the previous interface. + +## Server + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +type cTrace struct{} + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + // Set ORM cache adapter with redis. + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + + // Start HTTP server. + s := g.Server() + s.Use(ghttp.MiddlewareHandlerResponse) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/user", new(cTrace)) + }) + s.SetPort(8199) + s.Run() +} + +type InsertReq struct { + Name string `v:"required#Please input user name."` +} +type InsertRes struct { + Id int64 +} + +// Insert is a route handler for inserting user info into database. +func (c *cTrace) Insert(ctx context.Context, req *InsertReq) (res *InsertRes, err error) { + result, err := g.Model("user").Ctx(ctx).Insert(req) + if err != nil { + return nil, err + } + id, _ := result.LastInsertId() + res = &InsertRes{ + Id: id, + } + return +} + +type QueryReq struct { + Id int `v:"min:1#User id is required for querying"` +} +type QueryRes struct { + User gdb.Record +} + +// Query is a route handler for querying user info. It firstly retrieves the info from redis, +// if there's nothing in the redis, it then does db select. +func (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err error) { + one, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: 5 * time.Second, + Name: c.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).One() + if err != nil { + return nil, err + } + res = &QueryRes{ + User: one, + } + return +} + +type DeleteReq struct { + Id int `v:"min:1#User id is required for deleting."` +} +type DeleteRes struct{} + +// Delete is a route handler for deleting specified user info. +func (c *cTrace) Delete(ctx context.Context, req *DeleteReq) (res *DeleteRes, err error) { + _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: c.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Delete() + if err != nil { + return nil, err + } + return +} + +func (c *cTrace) userCacheKey(id int) string { + return fmt.Sprintf(`userInfo:%d`, id) +} +``` + +Brief explanation of the server code: + +1. First, the client also needs to initialize `Jaeger` via the `jaeger.Init` method. +2. In this example, we use database and database cache features to demonstrate tracing for both `ORM` and `Redis`. +3. At program startup, we set the adapter of the current database cache management to `redis` using the following method. For those interested in cache adapter introductions, please refer to the [Caching - Interface](../../../核心组件/缓存管理/缓存管理-接口设计.md) section. + +```go +g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) +``` + +4. In `ORM` operations, the `Ctx` method needs to be used to pass the context variable to the component. The `orm` component automatically recognizes if the current context contains tracing information and enables tracing features if it does. +5. In `ORM` operations, the `Cache` method is used here to cache query results to `redis`, and in delete operations, the `Cache` method is also used to clear the cache results in `redis`. For introductions to `ORM` cache management, please refer to [ORM Model - Query Cache](../../../核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) section. + +## View Results + +**Start the server:** + +![](/markdown/da456bb3ba924d8310f8f2a730f6c883.png) + +**Start the client:** + +![](/markdown/1cdc85405e6cdb92d653df295edf4672.png) + +View the tracing information in `Jaeger`: + +![](/markdown/85c062f73b9254973232ec56fa76e1db.png) + +As you can see, this request generates a total of `14` `spans`, with `4` `spans` on the client side and `10` `spans` on the server side. Each `span` represents a trace node. However, we notice that `3` `errors` occurred. Let's click for details to see the reasons. + +![](/markdown/14a27e50c9d458f751a4aca17cb6ecb4.png) + +It seems that all `redis` operations reported errors. Let's randomly click a related `redis` `span` to check the details: + +![](/markdown/2c59e1b7feaa7094ae74bdcc987bd6a6.png) + +It turns out that the error was caused by the inability to connect to `redis`, causing all `orm` cache functionalities to fail. However, it didn't affect the interface logic, and all queries went through the database. This error occurred because I forgot to start the local `redis server`. I'll quickly start the local `redis server` and check the results again: + +![](/markdown/f43bf06efa5da79a21146d9f6d93ceab.png) + +Now let's run the client code above again and check `jaeger`: + +![](/markdown/4a9fdf41e5e608907176a5d3e4bfd1ff.png) + +![](/markdown/405329a1897f8cc0c9cc28aba01505d9.png) + +No errors this time. + +The `HTTP Client&Server`, `Logging` components have been introduced before, so here we mainly focus on the tracing information of the `orm` and `redis` components. + +### ORM Tracing Information + +#### Attributes/Tags + +Let's randomly click an `ORM` trace `Span` and look at the `Attributes/Tags` information: + +![](/markdown/dd036ffacb33f3750edce3484387fe30.png) + +We can see that the `span.kind` here is `internal`, which is a method internal `span` type introduced before. Many `Tags` here have been introduced before, so we'll mainly introduce database-related `Tags`: + +| Attribute/Tag | Description | +| --- | --- | +| `db.type` | Database connection type, such as `mysql`, `mssql`, `pgsql` etc. | +| `db.link` | Database connection information. The password field is automatically hidden. | +| `db.group` | Database group name in the configuration file. | + +#### Events/Process + +![](/markdown/598498f523ea992f53d3cb58fd51eb64.png) + +| Event/Log | Description | +| --- | --- | +| `db.execution.sql` | The specific `SQL` statement executed. Since the ORM bottom layer is pre-processed, this statement is automatically concatenated for easier viewing and is for reference only. | +| `db.execution.type` | The type of `SQL` statement executed, commonly `DB.ExecContext` and `DB.QueryContext`, representing write and read operations, respectively. | +| `db.execution.cost` | Execution time of the current `SQL` statement, measured in milliseconds (ms). | + +### Redis Tracing Information + +#### Attributes/Tags + +![](/markdown/b6acd314e55ef431966734940ea867fe.png) + +| Attribute/Tag | Description | +| --- | --- | +| `redis.host` | `Redis` connection address. | +| `redis.port` | `Redis` connection port. | +| `redis.db` | `Redis` operation `db`. | + +#### Events/Process + +![](/markdown/34139be20f62d27fb0a16fd2edf59031.png) + +| Event/Log | Description | +| --- | --- | +| `redis.execution.command` | `Redis` execution command. | +| `redis.execution.arguments` | `Redis` execution command arguments. | +| `redis.execution.cost` | Execution time of the `Redis` command, measured in milliseconds (ms). | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..92af096c6bb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/obs/tracing-http-example' +title: 'Tracing - HTTP Example' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Tracing, HTTP Example, Baggage, Context Data Transfer, HTTP+DB+Redis+Logging, Service Link, Demo, Component Tracing] +description: "Trace the link when using the GoFrame framework. Through two examples, demonstrating the context data transfer between services and the full link tracing of HTTP, database, Redis, and logging components to help developers better understand the request handling process between application services and improve the system's monitoring and analysis capabilities." +--- + +In this chapter, we demonstrate two examples: one for demonstrating `baggage` context data transfer between services; another for demonstrating a more complete `HTTP+DB+Redis+Logging` component link tracing. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" new file mode 100644 index 00000000000..951dd853d86 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/obs/tracing-prepare' +title: 'Tracing - Preparation' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, OpenTelemetry, Jaeger, Distributed Tracing, Tracing, docker, OTLP HTTP, OTLP GRPC, Example Code] +description: "Implement tracing with Jaeger in the GoFrame framework. We will demonstrate how to introduce distributed tracing in a system through a combination of Jaeger and OpenTelemetry, including a quick deployment method for Jaeger and the location of example code in the GoFrame framework, as well as encapsulated registration modules." +--- + +After gaining a preliminary understanding of the concept of `OpenTelemetry`, we will use `Jaeger` as an example to demonstrate how to implement tracing in a program. + +## Jaeger + +[Jaeger](https://www.jaegertracing.io/) \\ˈyā-gər\ is Uber's open-source distributed tracing system and one of the systems that support `OpenTelemetry`. It is also a `CNCF` project. In this article, we will use `Jaeger` to demonstrate how to introduce distributed tracing into a system. Below is the architecture diagram for `Opentracing+Jaeger`, which also applies to using `OpenTelemetry`. + +## ![](/markdown/cd8d6734e501e9ac4917920666cb0867.png) + +## Preparation + +`Jaeger` offers an `all-in-one` image, which allows us to quickly start testing: + +```bash +docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 +``` + +If pulling the `docker` image is too slow, you can try modifying the image address of the `docker` pull site, such as: [http://mirrors.ustc.edu.cn/help/dockerhub.html?highlight=docker](http://mirrors.ustc.edu.cn/help/dockerhub.html?highlight=docker) + +After the image starts, you can open the `Jaeger UI` via [http://localhost:16686](http://localhost:16686/). + +![](/markdown/870c4c69cfd848787f88b074f0879519.png) + +## Sample Code Address + +Our sample code is in the `gf` main repository, located at: [https://github.com/gogf/gf/tree/master/example/trace](https://github.com/gogf/gf/tree/master/example/trace) + +## Encapsulated Jaeger Registration (to be removed in version 2.6.0) + +For developers' convenience, we have encapsulated the initialization logic for `jaeger` through a community module, code address: [https://github.com/gogf/gf/tree/master/contrib/trace/jaeger](https://github.com/gogf/gf/tree/master/contrib/trace/jaeger) + +## OTLP HTTP Encapsulation + +For developers' convenience, we have encapsulated the initialization logic for `otelhttp` through a community module, code address: [https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## OTLP GRPC Encapsulation + +For developers' convenience, we have encapsulated the initialization logic for `otelgrpc` through a community module, code address: [https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc](https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..876a00ba279 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" @@ -0,0 +1,136 @@ +--- +slug: '/docs/obs/tracing-example' +title: 'Tracing - Basic Example' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Tracing, Single Process, Root Span, Jaeger UI, Span Creation, Method Call, GoFrame Framework, OpenTelemetry, Performance Bottleneck] +description: "Using GoFrame for tracing in a single process by creating a Root Span to trace method call chains and viewing results in Jaeger UI. It introduces how to create Spans between methods to record the process of method calls and demonstrates in detail with example code how to implement the transmission of trace information and performance monitoring in the GoFrame framework, helping users quickly locate system anomalies and identify performance bottlenecks." +--- + +## Single Process Example + +Single process tracing refers to the call chain relationship between methods within the process. This scenario does not involve distributed tracing, making it relatively simple. Let's use this example as our introductory case. Example code address: [https://github.com/gogf/gf/tree/master/example/trace/inprocess](https://github.com/gogf/gf/tree/master/example/trace/inprocess) + +## Root Span + +`root span` is the first `span` object in the trace. In the single process scenario here, it often needs to be created manually. Subsequently, `spans` created within methods will act as its child `spans`. +:::tip +In inter-service communication scenarios of distributed architectures, it is often unnecessary for developers to manually create a `root span`, as it is automatically created by interceptors of client/server requests. +::: +Create a `tracer` and generate a `root span`: + +```go +func main() { + const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" + ) + + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + ctx, span := gtrace.NewSpan(ctx, "main") + defer span.End() + + // Trace 1. + user1 := GetUser(ctx, 1) + g.Dump(user1) + + // Trace 2. + user100 := GetUser(ctx, 100) + g.Dump(user100) +} +``` + +The above code creates a `root span` and passes this `span` through `context` to the `GetUser` method, so that the trace chain can continue within the `GetUser` method. + +## Span Creation Between Methods + +```go +// GetUser retrieves and returns hard coded user data for demonstration. +func GetUser(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetUser") + defer span.End() + m := g.Map{} + gutil.MapMerge( + m, + GetInfo(ctx, id), + GetDetail(ctx, id), + GetScores(ctx, id), + ) + return m +} + +// GetInfo retrieves and returns hard coded user info for demonstration. +func GetInfo(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetInfo") + defer span.End() + if id == 100 { + return g.Map{ + "id": 100, + "name": "john", + "gender": 1, + } + } + return nil +} + +// GetDetail retrieves and returns hard coded user detail for demonstration. +func GetDetail(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetDetail") + defer span.End() + if id == 100 { + return g.Map{ + "site": "https://goframe.org", + "email": "john@goframe.org", + } + } + return nil +} + +// GetScores retrieves and returns hard coded user scores for demonstration. +func GetScores(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetScores") + defer span.End() + if id == 100 { + return g.Map{ + "math": 100, + "english": 60, + "chinese": 50, + } + } + return nil +} +``` + +This example code shows multi-level method trace information transmission, meaning that the `ctx` context variable is passed as the first parameter of the method. Within the method, we use fixed syntax to create/start a `Span`: + +```go +ctx, span := gtrace.NewSpan(ctx, "xxx") +defer span.End() +``` + +By calling `span.End` using `defer`, we end a `Span`, which records the lifecycle (start and end) information of the `Span`. This information will be displayed in the tracing system. For the second parameter `spanName` of the `gtrace.NewSpan` method, we directly give the method name so that it is more recognizable in the trace display. + +## Result Viewing + +After executing the above program, the terminal outputs: + +![](/markdown/8124c7049fb50f1885c70626b28869da.png) + +Open `Jaeger UI`: [http://localhost:16686/search](http://localhost:16686/search), and you can see the trace results: ![](/markdown/bd0a6f9c87f239e6730243a09de02d6d.jpg) + +![](/markdown/fda619bc9de75bd8040ae71d18738528.png) + +Clicking on the details allows you to view specific information, including the invocation order and relationship of `spans`, the execution timeline, and recorded Attributes and Events information, which significantly helps us locate system anomalies and identify performance bottlenecks. + +![](/markdown/a4e87a91b4ba860a8045e68b6cc34cae.png) + +The `tracing-inprocess` is the name of our `tracer`, which is usually the service name. Since we only have one process and one `tracer`, there is only one service name displayed here. The `main` is the name of the `root span` we created, and other `spans` were created based on this `root span`. Since we called the `GetUser` method twice in the program, it shows two calls to the `GetUser` method. Each `GetUser` call internally invokes the three methods `GetInfo`, `GetDetail`, and `GetScores`, and the hierarchical relationship of method calls is displayed very clearly, along with the duration of each method call. + +The `Tags` and `Process` information recorded in each `span` corresponds to the `Attributes` and `Events` information in `OpenTelemetry`, which we will introduce in more detail in subsequent chapters. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" new file mode 100644 index 00000000000..44b0d603393 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" @@ -0,0 +1,334 @@ +--- +slug: '/docs/obs/tracing-practice-inject-traceid' +title: 'TraceID Injection and Retrieval' +sidebar_position: 0 +hide_title: true +keywords: [Tracing, TraceID, GoFrame, OpenTelemetry, Context, Client, Server, Log, Request, Response Header] +description: "How to inject and retrieve TraceID using the GoFrame framework in tracing. TraceID is an important identifier for associating requests between services, passed through the Context parameter, and can be automatically generated, inherited, or customized on both the client and server sides. The glog logging component automatically records TraceID, and GoFrame's Client and Server offer convenient TraceID management methods. Practice examples, including custom TraceID and integration methods for handling third-party RequestID, are provided." +--- + +## 1. Introduction + +In tracing, `TraceID` serves as a unique identifier passed between various services, used to associate requests between services, making it a crucial piece of data. `TraceID` is passed through the `Context` parameter, and if you use the framework's `glog` logging component, the `TraceID` will automatically be read and recorded in the log content. Therefore, it is recommended to use the framework's `glog` logging component for logging to perfectly support the tracing feature. + +## 2. Injection of TraceID + +### 1. Client + +If using the `GoFrame` framework's `Client`, all requests will inherently include `TraceID` injection. `GoFrame` utilizes the `OpenTelemetry` standard for its `TraceID`, which is a `32` hexadecimal character string. +:::tip +It is strongly recommended to use the `gclient` component, which is both feature-rich and equipped with tracing capabilities. +::: +### 2. Server + +If using the `GoFrame` framework's `Server`, if the request includes a `TraceID`, it will be automatically inherited into the `Context`; otherwise, a standard `TraceID` will be automatically injected and passed to subsequent logic. + +## 3. Retrieval of TraceID + +### 1. Client + +If using the `GoFrame` framework's `Client`, there are three methods available here. + +#### 1) Automatically Generate TraceID (Recommended) + +Create a `Context` with a `TraceID` using the `gctx.New/WithCtx` method, and pass this `Context` parameter when making requests. You can subsequently retrieve the `TraceID` for the entire trace using the `gctx.CtxId` method. Relevant methods: + +```go +// New creates and returns a context which contains context id. +func New() context.Context + +// WithCtx creates and returns a context containing context id upon given parent context `ctx`. +func WithCtx(ctx context.Context) context.Context +``` + +When using the `WithCtx` method, if the given `ctx` parameter already contains a `TraceID`, it will be used directly without creating a new one. + +#### 2) Client Custom TraceID + +An advanced usage is that the client can customize the `TraceID` using the `gtrace.WithTraceID` method. The method is defined as follows: + +```go +// WithTraceID injects custom trace id into context to propagate. +func WithTraceID(ctx context.Context, traceID string) (context.Context, error) +``` + +#### 3) Read Response Header + +If it is a request to a `GoFrame` framework `Server`, the `Trace-Id` field will be added to the returned request's `Header` for the client to read. + +### 2. Server + +If using the `GoFrame` framework's `Server`, you can directly obtain the `TraceID` using the `gctx.CtxId` method. Relevant methods: + +```go +// CtxId retrieves and returns the context id from context. +func CtxId(ctx context.Context) string +``` + +## 4. Examples of Use + +### 1. HTTP Response Header TraceID + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + req, err := g.Client().Get(context.Background(), "http://127.0.0.1:8199/") + if err != nil { + panic(err) + } + defer req.Close() + req.RawDump() +} +``` + +After execution, the terminal output: + +``` + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:14:37.245 [INFO] pid[55899]: http server started listening on [:8199] +2022-06-06 21:14:38.247 [INFO] {908d2027560af616e218e912d2ac3972} handler ++---------------------------------------------+ +| REQUEST | ++---------------------------------------------+ +GET / HTTP/1.1 +Host: 127.0.0.1:8199 +User-Agent: GClient v2.1.0-rc4 at TXQIANGGUO-MB0 +Traceparent: 00-908d2027560af616e218e912d2ac3972-1e291041b9afa718-01 +Accept-Encoding: gzip + ++---------------------------------------------+ +| RESPONSE | ++---------------------------------------------+ +HTTP/1.1 200 OK +Connection: close +Content-Length: 32 +Content-Type: text/plain; charset=utf-8 +Date: Mon, 06 Jun 2022 13:14:38 GMT +Server: GoFrame HTTP Server +Trace-Id: 908d2027560af616e218e912d2ac3972 + +908d2027560af616e218e912d2ac3972 +``` + +### 2. Injecting `TraceID` in Client + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + ctx := gctx.New() + g.Log().Info(ctx, "request starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "response: %s", content) +} +``` + +After execution, the terminal output: + +```html +2022-06-06 21:17:17.447 [INFO] pid[56070]: http server started listening on [:8199] + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:17:18.450 [INFO] {e843f0737b0af616d8ed185d46ba65c5} request starts +2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} handler +2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} response: e843f0737b0af616d8ed185d46ba65c5 +``` + +### 3. Custom `TraceID` in Client + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/text/gstr" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + ctx, _ := gtrace.WithTraceID(context.Background(), gstr.Repeat("a", 32)) + g.Log().Info(ctx, "request starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "response: %s", content) +} +``` + +After execution, the terminal output: + +``` + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:40:03.897 [INFO] pid[58435]: http server started listening on [:8199] +2022-06-06 21:40:04.900 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} request starts +2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} handler +2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} response: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +``` + +## 5. Common Questions + +### 1. If not using `GoFrame` framework's `Client/Server`, how to obtain the trace's `TraceID`? + +Refer to the implementation of trace in `GoFrame` framework's `Client/Server` and implement it manually. This may involve certain costs. + +If using a third-party `Client/Server` component, refer to the relevant documentation of the third-party component. + +### 2. Internal services do not use the standard `OpenTelemetry` protocol, but each request carries a `RequestID` parameter in the form of `33612a70-990a-11ea-87fe-fd68517e7a2d`. How to integrate with `TraceID`? + +Based on my analysis, your `RequestID` format closely aligns with the `TraceID` standard, utilizing a `UUID` string, which can be directly converted to a `TraceID`. It is suggested to convert `RequestID` to `TraceID` in the first middleware layer within your `Server` internally and inject it into the `Context` using the custom `TraceID` method, passing the `Context` to subsequent business logic. + +Here's an example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + // Internal service + internalServer := g.Server("internalServer") + internalServer.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "internalServer handler") + r.Response.Write(traceID) + }) + internalServer.SetPort(8199) + go internalServer.Start() + + // External service, accessed for testing + // http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d + userSideServer := g.Server("userSideServer") + userSideServer.Use(func(r *ghttp.Request) { + requestID := r.Get("RequestID").String() + if requestID != "" { + newCtx, err := gtrace.WithUUID(r.Context(), requestID) + if err != nil { + panic(err) + } + r.SetCtx(newCtx) + } + r.Middleware.Next() + }) + userSideServer.BindHandler("/", func(r *ghttp.Request) { + ctx := r.Context() + g.Log().Info(ctx, "request internalServer starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "internalServer response: %s", content) + r.Response.Write(content) + }) + userSideServer.SetPort(8299) + userSideServer.Run() +} +``` + +This example code runs two HTTP services to demonstrate inter-service tracing capabilities. One is an internal service that provides functionality; the other is an external service for testing, which achieves its functionality by calling the internal service. After execution, access: [http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d](http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d) + +Then check the terminal output: + +```html +2022-06-07 14:51:21.957 [INFO] openapi specification is disabled +2022-06-07 14:51:21.958 [INTE] ghttp_server.go:78 78198: graceful reload feature is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + internalServer | default | :8199 | ALL | / | main.main.func1 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + internalServer | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-07 14:51:21.959 [INFO] pid[78198]: http server started listening on [:8199] +2022-06-07 14:51:21.965 [INFO] openapi specification is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | / | main.main.func3 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | /* | main.main.func2 | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-07 14:51:21.965 [INFO] pid[78198]: http server started listening on [:8299] +2022-06-07 14:53:05.322 [INFO] {33612a70990a11ea87fefd68517e7a2d} request internalServer starts +2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer handler +2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer response: 33612a70990a11ea87fefd68517e7a2d +``` + +We can see that the `RequestID` has been successfully circulated as the `TraceID` through the service chain! \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" new file mode 100644 index 00000000000..db679139633 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" @@ -0,0 +1,162 @@ +--- +slug: '/docs/obs/tracing-intro' +title: 'Tracing - Intro' +sidebar_position: 0 +hide_title: true +keywords: [OpenTelemetry, Distributed Tracing, OpenTracing, OpenCensus, Tracer, Span, Attributes, Events, GoFrame, Propagator] +description: "The background and important concepts of the OpenTelemetry project, including components like TracerProvider, Tracer, Span, Attributes, Events, SpanContext, and Propagator, as well as the support of GoFrame framework in these technologies and how to use the gtrace module to implement tracing. Additionally, it lists GoFrame core components that support the OpenTelemetry standard, such as HTTP client, HTTP server, gRPC client and server, Logging, ORM, and NoSQL Redis." +--- + +## OpenTelemetry + +The concept of Distributed Tracing was first proposed by `Google`. The technology has matured over time, and there are now some protocol standards available for reference. Currently, two open-source frameworks are quite influential in this area: `Netflix's` `OpenTracing` and `Google's` `OpenCensus`. Both frameworks have a sizable developer community. To establish a unified technical standard, the two frameworks merged to form the `OpenTelemetry` project, abbreviated as `otel`. For more details, you can refer to: + +1. [Introduction to OpenTracing](https://johng.cn/observability/opentracing-introduction) +2. [Introduction to OpenTelemetry](https://johng.cn/observability/opentelemetry-introduction) + +Thus, our tracing technology solution is implemented based on the `OpenTelemetry` standard, with some open-source projects implementing the protocol standard in `Golang`: + +1. [https://github.com/open-telemetry/opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) +2. [https://github.com/open-telemetry/opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib) + +Other third-party frameworks and systems (such as `Jaeger/Prometheus/Grafana`) also adhere to standardized protocols to integrate with `OpenTelemetry`, significantly reducing development and maintenance costs. + +![](/markdown/63ac4d816e08ea5dd6d986f4119d03e1.jpg) + +## Key Concepts + +Let's first look at the architecture diagram of `OpenTelemetry`. +We won't introduce it in full, only the commonly used concepts. For an introduction to the internal technical architecture of `OpenTelemetry`, +you can refer to [OpenTelemetry Architecture](https://johng.cn/observability/opentelemetry-architecture), +and for semantic conventions, please refer to: [https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md) + +![](8fbc65f937aaac8c9b6947faa89a6964.png) + +### TracerProvider + +Mainly responsible for creating `Tracer`, usually requiring a concrete implementation from a third-party distributed tracing management platform. By default, it is an empty `TracerProvider (NoopTracerProvider)`. While it can create `Tracer`, it doesn’t actually execute specific data flow transmission logic internally. + +### Tracer + +`Tracer` represents a complete tracing, consisting of one or more `span`. The example below illustrates a `tracer` consisting of `8` spans: + +``` + [Span A] ←←←(the root span) + | + +------+------+ + | | + [Span B] [Span C] ←←←(Span C is a `ChildOf` Span A) + | | + [Span D] +---+-------+ + | | + [Span E] [Span F] >>> [Span G] >>> [Span H] + ↑ + ↑ + ↑ + (Span G `FollowsFrom` Span F) +``` + +The timeline representation makes it easier to understand: + +``` +––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time + + [Span A···················································] + [Span B··············································] + [Span D··········································] + [Span C········································] + [Span E·······] [Span F··] [Span G··] [Span H··] +``` + +We usually create a `Tracer` in the following way: + +``` +gtrace.NewTracer(tracerName) +``` + +### Span + +A `Span` is a fundamental component of a tracing. It represents a single work unit, such as a function call or an `HTTP` request. A `span` records the following essential elements: + +- Service name (`operation name`) +- Service start and end times +- `K/V` form `Tags` +- `K/V` form `Logs` +- `SpanContext` + +`Span` is the most frequently used object among them, thus creating a `Span` is very straightforward, for example: + +``` +gtrace.NewSpan(ctx, spanName, opts...) +``` + +### Attributes + +`Attributes` are stored as `K/V` key-value pairs for user-defined tags, mainly used for query filtering of tracing results. For example: `http.method="GET",http.status_code=200`. The `key` must be a string, and the `value` must be a string, boolean, or numeric type. `Attributes` in a `span` are only visible to itself and are not passed to subsequent spans through `SpanContext`. The way to set `Attributes` is as follows: + +``` +span.SetAttributes( + label.String("http.remote", conn.RemoteAddr().String()), + label.String("http.local", conn.LocalAddr().String()), +) +``` + +### Events + +`Events` are similar to `Attributes` and are also in the form of `K/V` key-value pairs. Unlike `Attributes`, `Events` also record the time when they are written, so `Events` are mainly used to record the time certain events occur. The `key` for `Events` must also be a string, but there are no restrictions on the type of `value`. For example: + +``` +span.AddEvent("http.request", trace.WithAttributes( + label.Any("http.request.header", headers), + label.Any("http.request.baggage", gtrace.GetBaggageMap(ctx)), + label.String("http.request.body", bodyContent), +)) +``` + +### SpanContext + +`SpanContext` carries some data used for **cross-service (cross-process) communication**, mainly including: + +- Information sufficient to identify this span within the system, e.g., `span_id, trace_id`. +- `Baggage` - maintains user-defined `K/V` formatted data for cross-service (cross-process) tracing in the entire tracing link. `Baggage` is similar to `Attributes`, also in `K/V` key-value pair format. The differences are: + +- Both `key` and `value` can only be in string format. +- `Baggage` is visible not only to the current span but is also passed to all subsequent child spans through `SpanContext`. Be cautious using `Baggage` because transmitting these `K,V` in all spans incurs non-trivial network and CPU overhead. + +### Propagator + +The `Propagator` is used for encoding/decoding data end-to-end, such as data transmission from the `Client` to the `Server`. `TraceId`, `SpanId`, and `Baggage` also need to be managed for data transmission through the propagator. Business-end developers are usually not aware of the `Propagator`, only middleware/interceptor developers need to understand its role. The standard protocol implementation library of `OpenTelemetry` provides a commonly used `TextMapPropagator` for common text data end-to-end transmission. Furthermore, to ensure compatibility of the data transmitted in the `TextMapPropagator`, special characters should not be included. For details, please refer to: [https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md) + +The `GoFrame` framework uses the following propagator objects through the `gtrace` module and sets them globally in `OpenTelemetry`: + +```go +// defaultTextMapPropagator is the default propagator for context propagation between peers. +defaultTextMapPropagator = propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, +) +``` + +## Supported Components +:::tip +The core components of `GoFrame` have fully supported the `OpenTelemetry` standard and **automatically enable** the tracing feature, with no need for developers to explicitly call or use it. Without injecting an external `TracerProvider`, the framework will use the default `TracerProvider`, which will only automatically create a `TraceID` and `SpanID` to facilitate tracing the request log and will not execute complex logic. +::: +Including but not limited to the following core components: + +| Components with Auto Tracing Support | Component Name | Description | +| --- | --- | --- | +| `HTTP Client` | `gclient` | The `HTTP` client automatically enables the tracing feature. For specific usage examples, please refer to the subsequent example chapter. | +| `Http Server` | `ghttp` | The `HTTP` server automatically enables the tracing feature. For specific usage examples, please refer to the subsequent example chapter. | +| `gRPC Client` | `contrib/rpc/grpcx` | The `gRPC` client automatically enables the tracing feature. For specific usage examples, please refer to the subsequent example chapter. | +| `gRPC Server` | `contrib/rpc/grpcx` | The `gRPC` server automatically enables the tracing feature. For specific usage examples, please refer to the subsequent example chapter. | +| `Logging` | `glog` | The log content needs to inject the current request's `TraceId` to quickly locate issues through logs. This feature is implemented by the `glog` component. Developers need to call the `Ctx` chain operation method to pass the `context.Context` context variable to the current logging operation chain when outputting logs. Failing to pass the `context.Context` will result in losing the `TraceId` in the log content. | +| `ORM` | `gdb` | The execution of the database is an essential part of the link. The `Orm` component needs to deliver its execution information into the tracing as part of the execution trace. | +| `NoSQL Redis` | `gredis` | The execution of `Redis` is also an essential part of the tracing. `Redis` needs to deliver its execution information into the tracing as part of the execution trace. | +| `Utils` | `gtrace` | Managing the `Tracing` feature requires some encapsulation, mainly considering extensibility and usability. This encapsulation is implemented by the `gtrace` module. The documentation can be found at: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace) | + +## Reference Materials + +- [https://opentracing.io](https://opentracing.io/) +- [https://opencensus.io](https://opencensus.io/) +- [https://opentelemetry.io](https://opentelemetry.io/) +- [https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..4c5e083d901 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" @@ -0,0 +1,347 @@ +--- +slug: '/docs/core/gi18n-example' +title: 'I18N - Example' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, i18n, internationalization, multilingual, context, template translation, context, T method, independent object, SetLanguage] +description: "Using the i18n internationalization feature in the GoFrame framework, which includes object creation, language setting, commonly used methods, and integration with the view engine. It describes in detail how to manage language translation through singleton and independent objects, use SetLanguage and WithLanguage methods for language setting, and achieve keyword and template content translation through T and Tf methods. The article also demonstrates examples of performing internationalization operations through context settings and the view engine." +--- + +## Object Creation + +### Singleton Object + +In most scenarios, we recommend using the `g.I18n` singleton object and allowing customization of different singleton objects. However, it is important to note that modifications to the configuration of a singleton object are globally effective. For example: + +```go +g.I18n().T(context.TODO(), "{#hello} {#world}") +``` +:::tip +In all translation methods, the first parameter requires the input of a `Context` context variable parameter, which is used for context variable transmission, specifying the translation language, and for future extensibility. Although this parameter can also be directly passed as `nil`, for the sake of program rigor, it is recommended that you pass `context.TODO()` or `context.Background()` when you are unsure of what to pass or have no special requirements. +::: +### Independent Object + +Alternatively, we can modularly use the `gi18n` module independently by creating an independent `i18n` object with the `gi18n.New()` method, then managed by the developer. For example: + +```go +i18n := gi18n.New() +i18n.T(context.TODO(), "{#hello} {#world}") +``` + +## Language Setting + +There are two ways to set the translation language: one is through the `SetLanguage` method to set a unified translation language for the current `I18N` object, and the other is through context setting for the language of the currently executing translation. + +### `SetLanguage` + +For example, we can set the current translation object's language with `g.I18n().SetLanguage("zh-CN")`. Thereafter, any usage of this object will perform translation in `zh-CN`. It is important to note that the configuration method of the component is also not concurrency-safe, and this method should be set during program initialization and not be changed during runtime. + +### `WithLanguage` + +The `WithLanguage` method can create a new context variable and temporarily set the language of your current translation. Since this method acts on the `Context`, it is concurrency-safe and is often used for runtime translation language setting. Let's see an example: + +```go +ctx := gi18n.WithLanguage(context.TODO(), "zh-CN") +i18n.Translate(ctx, `hello`) +``` + +The `WithLanguage` method is defined as follows: + +```go +// WithLanguage append language setting to the context and returns a new context. +func WithLanguage(ctx context.Context, language string) context.Context +``` + +It is used to set the translation language in the context variable and return a new context variable, which can be used for subsequent translation methods. + +## Common Methods + +### `T` Method + +The `T` method is an alias for the `Translate` method, and it's the name we recommend using most of the time. The `T` method can take a keyword name or be directly given template content, and it will automatically translate and return the translated string. + +Additionally, the `T` method can specify the target language name to be translated through a second language parameter. This name should match those in the configuration files/paths and is often a standardized international language abbreviation such as: `en/ja/ru/zh-CN/zh-TW`, etc. Otherwise, it will automatically use the language set in the `Manager` translation object for translation. + +Method definition: + +```go +// T translates with configured language and returns the translated content. +func T(ctx context.Context, content string) +``` + +**Keyword Translation** + +For keyword translation, simply pass the keyword to the `T` method, such as: `T(context.TODO(), "hello")`, `T(context.TODO(), "world")`. The `I18N` component will prioritize translating the given keyword and return the translated content; otherwise, the original content is displayed. + +**Template Content Translation** + +The `T` method supports template content translation, where keywords in the template are by default enclosed using `{#` and `}` tags. During template parsing, it will automatically replace the keyword contents within these tags. Example usage: + +#### 1) Directory Structure + +```bash +├── main.go +└── i18n + ├── en.toml + ├── ja.toml + ├── ru.toml + └── zh-CN.toml +``` + +#### 2) Translation Files + +`ja.toml` + +```bash +hello = "こんにちは" +world = "世界" +``` + +`ru.toml` + +```bash +hello = "Привет" +world = "мир" +``` + +`zh-CN.toml` + +```bash +hello = "你好" +world = "世界" +``` + +#### 3) Sample Code + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + ctx = gctx.New() + i18n = gi18n.New() + ) + + i18n.SetLanguage("en") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + i18n.SetLanguage("ja") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + i18n.SetLanguage("ru") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + ctx = gi18n.WithLanguage(ctx, "zh-CN") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) +} +``` + +After execution, the terminal output is: + +```bash +Hello +GF says: HelloWorld! +こんにちは +GF says: こんにちは世界! +Привет +GF says: Приветмир! +你好 +GF says: 你好世界! +``` + +### `Tf` Method + +We know that there will also be some variables in the template content, and these variables can be translated with the `Tf` method. + +`Tf` is an alias for `TranslateFormat`. This method supports formatting translated content, and the string formatting syntax refers to the `Sprintf` method of the standard library `fmt` package. + +**Method Definition:** + +```go +// Tf translates, formats and returns the with configured language +// and given . +func Tf(ctx context.Context, format string, values ...interface{}) string +``` + +Let's take a simple example. + +#### 1) Directory Structure + +```bash +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +#### 2) **Translation Files** + +`en.toml` + +```bash +OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f." +``` + +`zh-CN.toml` + +```bash +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +``` + +#### 3) **Sample Code** + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + orderId = 865271654 + orderAmount = 99.8 + ) + + i18n := gi18n.New() + i18n.SetLanguage("en") + fmt.Println(i18n.Tf(ctx, `{#OrderPaid}`, orderId, orderAmount)) + + i18n.SetLanguage("zh-CN") + fmt.Println(i18n.Tf(ctx, `{#OrderPaid}`, orderId, orderAmount)) +} +``` + +After execution, the terminal output is: + +```bash +You have successfully complete order #865271654 payment, paid amount: ¥99.80. +您已成功完成订单号 #865271654 支付,支付金额¥99.80。 +``` +:::note +For demonstration purposes, the handling of the payment amount in this example is quite simple. In actual projects, it is often necessary to automatically convert the currency unit according to the region in the business code before rendering the `i18n` display content. +::: +### Context setting for translation language + +We will make some changes to the above example for demonstration. + +#### 1) Directory Structure + +```bash +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +#### 2) Translation Files + +`en.toml` + +```bash +OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f." +``` + +`zh-CN.toml` + +```bash +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +``` + +#### 3) Sample Code + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + orderId = 865271654 + orderAmount = 99.8 + ) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `en`), + `{#OrderPaid}`, orderId, orderAmount, + )) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `zh-CN`), + `{#OrderPaid}`, orderId, orderAmount, + )) +} +``` + +After execution, the terminal output is: + +```bash +You have successfully complete order #865271654 payment, paid amount: ¥99.80. +您已成功完成订单号 #865271654 支付,支付金额¥99.80。 +``` +:::note +For demonstration purposes, the handling of the payment amount in this example is quite simple. In actual projects, it is often necessary to automatically convert the currency unit according to the region in the business code before rendering the `i18n` display content. +::: +## `I18N` and the View Engine + +`gi18n` is already integrated into the `GoFrame` framework's view engine by default, allowing you to directly use `gi18n` keyword tags in template files/content. We can also set the translation language of the current request through context variables. +:::tip +Additionally, we can set the template variable `I18nLanguage` to control the parsing language of the current template, allowing different template content to be parsed according to different international languages. +::: +Usage example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + r.SetCtx(gi18n.WithLanguage(r.Context(), r.Get("lang", "zh-CN").String())) + r.Middleware.Next() + }) + group.ALL("/", func(r *ghttp.Request) { + r.Response.WriteTplContent(`{#hello}{#world}!`) + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, visit the following pages, and the output will be: + +1. [http://127.0.0.1:8199](http://127.0.0.1:8199/) + + ```bash + 你好世界! + ``` + +2. [http://127.0.0.1:8199/?lang=ja](http://127.0.0.1:8199/?lang=ja) + + ```bash + こんにちは世界! + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..d3a964bf40e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,78 @@ +--- +slug: '/docs/core/gi18n-config' +title: 'I18N - Configuration' +sidebar_position: 0 +hide_title: true +keywords: [I18N Internationalization, Configuration Management, GoFrame Framework, File Format, Internationalization Component, Configuration File, Directory Structure, Resource Manager, Intelligent Recognition, Language Code] +description: "The I18N Internationalization component is one of the core components of the GoFrame framework, supporting multiple configuration file formats such as xml, ini, yaml, toml, etc., and compatible with multiple directory structures and file formats. Developers can achieve multilingual support through configuration. The system automatically recognizes language files and supports custom path settings. It is recommended to use standardized international language codes for file naming to ensure program multilingual compatibility." +--- + +As one of the core components of the framework, inheriting the consistent convenience design philosophy of `goframe`, the configuration management of the `I18N` internationalization component is very simple. + +## File Formats + +The `gi18n` internationalization component supports five common configuration file formats: `xml/ini/yaml/toml/json/properties` (for a more detailed list of file supports, please refer to the configuration management section: [Configuration](../配置管理/配置管理.md)). Similarly, like the configuration management module, the framework recommends using the `toml` file format. + +## Reading Path + +By default, `gi18n` will automatically look for and read the following directories under the source root directory of the current project (or the current `PWD` run directory): + +- `manifest/i18n` +- `i18n` + +The found directory is used as the storage directory for international translation files by default. Developers can also customize the storage directory path of `i18n` files through the `SetPath` method. + +## File Storage + +In the `i18n` directory, you can directly name files according to internationalization names such as `en.toml`/`ja.toml`/`zh-CN.toml`; you can also give a directory with the internationalization name and freely customize configuration files under that directory, such as `en/editor.toml`/`en/user.toml`, `zh-CN/editor.toml`/`zh-CN/user.toml`. You can manage with pure file management or add an additional directory level, and `gi18n` can intelligently recognize and load. +:::tip +Internationalized file/directory names can be defined and maintained by developers, primarily for settings and use in the program. It is recommended to name them according to standardized international regional language codes. For more details, refer to WIKI: [https://zh.wikipedia.org/wiki/ISO\_639-1](https://zh.wikipedia.org/wiki/ISO_639-1) +::: +For example, the following `i18n` directory structure and file format are supported. + +**Distinguish different languages through separate `i18n` files** + +``` +└── i18n + ├── en.toml + ├── ja.toml + ├── ru.toml + ├── zh-CN.toml + └── zh-TW.toml +``` + +**Distinguish different languages through different directory names** + +``` +└── i18n + ├── en + │ ├── hello.toml + │ └── world.toml + ├── ja + │ ├── hello.yaml + │ └── world.yaml + ├── ru + │ ├── hello.ini + │ └── world.ini + ├── zh-CN + │ ├── hello.json + │ └── world.json + └── zh-TW + ├── hello.xml + └── world.xml +``` + +**Different languages can have different file formats** + +``` +└── i18n + ├── en.toml + ├── ja.yaml + ├── ru.ini + ├── zh-CN.json + └── zh-TW.xml +``` + +## Resource Manager + +`gi18n` supports the resource manager by default (for more details, refer to the section: [Resource](../资源管理/资源管理.md)). By default, it will retrieve the `manifest/i18n` and `i18n` directories from the `gres` configuration manager, or the `i18n` directory path set by the developer. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" new file mode 100644 index 00000000000..3699abcd878 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" @@ -0,0 +1,29 @@ +--- +slug: '/docs/core/gi18n' +title: 'I18N' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame, GoFrame framework, I18N, Internationalization, gi18n module, Go language, programming framework, open-source project, software development, multilingual support] +description: "The I18N internationalization component in the GoFrame framework is implemented by the gi18n module. By importing the relevant module, developers can easily implement multilingual support in software projects, thereby enhancing the internationalization capability of applications. Detailed interface documentation can be viewed through the provided URL for more technical details." +--- + +## Introduction + +The `GoFrame` framework provides a commonly used `I18N` internationalization component, implemented by the `gi18n` module. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/i18n/gi18n" +``` + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/i18n/gi18n](https://pkg.go.dev/github.com/gogf/gf/v2/i18n/gi18n) + + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" new file mode 100644 index 00000000000..b482cc2e0c8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" @@ -0,0 +1,81 @@ +--- +slug: '/docs/core/gcmd-parser' +title: 'Command - Args Parsing' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Line Parsing, gcmd, Parser Parsing, Go Development, Code Example, Option Management, Command Line Options, Development Documentation] +description: "The command line parsing feature of the GoFrame framework, focusing on the Parser parsing method of the gcmd component. By customizing option names and value parsing, it can efficiently manage and parse command line options. Includes code examples and detailed API documentation to help developers understand and apply it in actual projects." +--- + +## Introduction + +The main aspect of command line parsing is option parsing. The `gcmd` component provides a `Parse` method for customizing option parsing, including specifying which option names exist and whether each option has values. With this configuration, all parameters and options can be parsed and categorized. +:::tip +In most cases, we do not need to explicitly create a `Parser` object, as we manage multiple commands through hierarchical management and object management. However, the underlying implementation still uses the `Parser` approach, so understanding the principle in this chapter will suffice. +::: +Related methods: + +For more `Parser` methods, please refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd#Parser](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd#Parser) + +```go +func Parse(supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) +func ParseWithArgs(args []string, supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) +func ParserFromCtx(ctx context.Context) *Parser +func (p *Parser) GetArg(index int, def ...string) *gvar.Var +func (p *Parser) GetArgAll() []string +func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var +func (p *Parser) GetOptAll() map[string]string +``` + +Where `ParserOption` is as follows: + +```go +// ParserOption manages the parsing options. +type ParserOption struct { + CaseSensitive bool // Marks options parsing in case-sensitive way. + Strict bool // Whether stops parsing and returns error if invalid option passed. +} +``` + +Parsing example: + +```go +parser, err := gcmd.Parse(g.MapStrBool{ + "n,name": true, + "v,version": true, + "a,arch": true, + "o,os": true, + "p,path": true, +}) +``` + +As you can see, the option input parameter is actually of `map` type. The key is the option name, with different names for the same option separated by the `,` symbol. For instance, in this example, the `n` and `name` options are the same option. When the user inputs `-n john`, both `n` and `name` options will receive the data `john`. + +The value is a boolean type indicating whether the option requires value parsing. This option configuration is crucial, as some options do not need to receive data, serving merely as a flag. For example, with the input `-f force`, if data needs to be parsed, the value for option `f` is `force`; otherwise, `force` is a command line argument rather than an option. + +## Usage Example + +```go +func ExampleParse() { + os.Args = []string{"gf", "build", "main.go", "-o=gf.exe", "-y"} + p, err := gcmd.Parse(g.MapStrBool{ + "o,output": true, + "y,yes": false, + }) + if err != nil { + panic(err) + } + fmt.Println(p.GetOpt("o")) + fmt.Println(p.GetOpt("output")) + fmt.Println(p.GetOpt("y") != nil) + fmt.Println(p.GetOpt("yes") != nil) + fmt.Println(p.GetOpt("none") != nil) + + // Output: + // gf.exe + // gf.exe + // true + // true + // false +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..eff08f44f52 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" @@ -0,0 +1,202 @@ +--- +slug: '/docs/core/gcmd-command' +title: 'Command - Object' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, command line management, gcmd, Command object, callback methods, Parser parsing, start command, hierarchical command, subcommand, help information] +description: "Manage command line objects and commands using the gcmd library in the GoFrame framework, covering the definition of Command object, the use of callback methods, and hierarchical management of commands. It also provides examples of starting HTTP and gRPC services via command line within the GoFrame framework, demonstrating how to add subcommands to commands and automatically generate help information." +--- + +## Introduction + +In most scenarios, we manage single or multiple commands using the `Command` command line object, and the default command line parsing rules (without explicitly using the `Parser` parser) are sufficient. The `Command` object is defined as follows: + +For detailed information, refer to the interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd@master#Command](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd@master#Command) + +```go +// Command holds the info about an argument that can handle custom logic. +type Command struct { + Name string // Command name(case-sensitive). + Usage string // A brief line description about its usage, eg: gf build main.go [OPTION] + Brief string // A brief info that describes what this command will do. + Description string // A detailed description. + Arguments []Argument // Argument array, configuring how this command act. + Func Function // Custom function. + FuncWithValue FuncWithValue // Custom function with output parameters that can interact with command caller. + HelpFunc Function // Custom help function + Examples string // Usage examples. + Additional string // Additional info about this command, which will be appended to the end of help info. + Strict bool // Strict parsing options, which means it returns error if invalid option given. + Config string // Config node name, which also retrieves the values from config component along with command line. + parent *Command // Parent command for internal usage. + commands []*Command // Sub commands of this command. +} +``` + +As each object has detailed comments, we will not elaborate further here. + +## Callback Methods + +The `Command` object supports `3` callback methods: + +- `Func`: We usually customize this callback method to implement the command execution operation. +- `FuncWithValue`: Method similar to `Func`, but supports return values, often used in scenarios where command lines call each other. Typically not needed in general projects. +- `HelpFunc`: Custom help information. Generally unnecessary, as the `Command` object can automatically generate help information. + +We focus mainly on the `Func` callback method. Other methods can be explored if interested. + +## `Func` Callback + +Method definition: + +```go +// Function is a custom command callback function that is bound to a certain argument. +type Function func(ctx context.Context, parser *Parser) (err error) +``` + +As seen, within the callback method, we use the `parser` object to obtain parsing parameters and options and return `error` to inform the upper-level calling method whether the execution was successful. + +Example usage: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "start http server", + Description: "this is the command entry for starting your http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello world") + }) + s.SetPort(8199) + s.Run() + return + }, + } +) + +func main() { + Main.Run(gctx.New()) +} +``` + +This is what most projects’ command line objects for starting up would look like. Most projects have only one entry point and only one callback method implementation. + +## Help Information Generation + +Although the `Command` object can customize the `HelpFunc` help callback method, the `Command` object can automatically generate `Help` usage help information. In most scenarios, customization is unnecessary. Moreover, the `gcmd` component has built-in support for `h/help` options by default, so programs using the `gcmd` component can automatically generate `Help` help information using these two options. + +Let’s look at an example. We first build the previous example into a binary `main` file using `go build main.go`, and then take a quick look at the automatically generated help information when there is only one command: + +```bash +$ ./main -h +USAGE + main [OPTION] + +DESCRIPTION + this is the command entry for starting your http server +``` + +## Hierarchical Command Management + +### Parent Commands and Subcommands + +A `Command` command can add subcommands. When a `Command` has subcommands, it becomes a parent command. Subcommands can also add their own subcommands, forming a hierarchical command relationship. Both parent commands and subcommands can have their own callback methods, but in most scenarios, once a `Command` becomes a parent command, callback methods often become unnecessary. We typically add subcommands to a `Command` using the `AddCommand` method: + +```go +// AddCommand adds one or more sub-commands to current command. +func (c *Command) AddCommand(commands ...*Command) error +``` + +### Hierarchical Command Usage Example + +Let us demonstrate an example of multi-command management. We will improve the previous example by adding two subcommands. + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "start http server", + Description: "this is the command entry for starting your process", + } + Http = &gcmd.Command{ + Name: "http", + Brief: "start http server", + Description: "this is the command entry for starting your http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + fmt.Println("start http server") + return + }, + } + Grpc = &gcmd.Command{ + Name: "grpc", + Brief: "start grpc server", + Description: "this is the command entry for starting your grpc server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + fmt.Println("start grpc server") + return + }, + } +) + +func main() { + err := Main.AddCommand(Http, Grpc) + if err != nil { + panic(err) + } + Main.Run(gctx.New()) +} +``` + +As seen, we use the `AddCommand` command to add two subcommands `http/grpc` to the main command, used respectively for starting `http/grpc` services. When subcommands exist, parent command often has no need for a `Func` callback definition, so we removed the `Func` definition of the `main` command here. + +After compilation, let us execute to see the effect: + +```bash +$ main +USAGE + main COMMAND [OPTION] + +COMMAND + http start http server + grpc start grpc server + +DESCRIPTION + this is the command entry for starting your process +``` + +Using the `http` command: + +```bash +$ main http +start http server +``` + +Using the `grpc` command: + +```bash +$ main grpc +start grpc server +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" new file mode 100644 index 00000000000..fc2eef33bac --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/core/gcmd-intro' +title: 'Command - Concepts' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Line Arguments, Option Management, Arguments and Options, Command Line Parsing, gcmd Component, Command Line Optimization, Option Positioning, gf Command] +description: "Basic concepts of command line arguments and options in the GoFrame framework, discussing the definition and distinction of parameters and options and how they are represented in the command line. It explains in detail the parsing rules of options and parameters, including the use of the equal sign to connect options and data. Also provides implementation details and usage examples of the gcmd component in GoFrame to help users better manage command line inputs." +--- + +## Basic Concepts + +### Argument + +Data passed sequentially in the program command line without name identifiers is called an Argument. The input of arguments is sequential. + +### Option + +Additional input that controls the program logic and has a name identifier is called Options. Option names are prefixed with `-` or `--`. Options are unordered and can be placed in any position in the command line. Options may or may not have associated data. In other similar third-party functional components, options work similarly to Flags. + +Additionally, following the traditional command line management practice, options can have shorthand aliases to simplify command line argument input. Shorthand aliases are often set as a single letter. + +## Option Positioning and `=` Sign + +The `gcmd` component supports options being placed anywhere in the command line, meaning the following command line option inputs are essentially the same: + +```bash +gf build main.go -a amd64 -o linux -n app -yes +gf -a amd64 -o linux build main.go -yes -n app +gf -yes -n app build -o linux -a amd64 main.go +``` + +In these examples, + +- `gf` / `build` / `main.go` are arguments with indices `0`, `1`, `2` respectively; since arguments are ordered, changing the order in the command line does not change the order of these three. +- `a` / `o` / `n` are options with data. Since the order is irrelevant, data is retrieved by option name, hence they can be placed freely. +- `yes` is an option without data and can also be placed freely. + +Options and their data can be connected with a space or an `=` sign, like: + +```bash +gf build main.go -a=amd64 -o=linux -n=app -yes +``` + +## Default Parsing Rules + +The `gcmd` module provides several package methods to obtain default command line parsing rules. By default, it will automatically recognize arguments and options. + +### Situations with the `=` Sign in the Command Line + +```bash +gf build main.go -a=amd64 -o=linux -n=app -yes +``` + +By default, + +- `gf` / `build` / `main.go` are arguments, with indices `0`, `1`, `2`. +- `a` / `o` / `n` / `yes` will be parsed as options, with `yes` being an option without data. + +### Not Using `=` Sign to Connect Option Parameters + +```bash +gf build main.go -a amd64 -o linux -n app -yes +``` + +By default, + +- `gf` / `build` / `main.go` are arguments, with indices `0`, `1`, `2`. +- `a` / `o` / `n` / `yes` will be parsed as options, with `yes` being an option without data. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..d223eb39c2f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" @@ -0,0 +1,125 @@ +--- +slug: '/docs/core/gcmd-funcs' +title: 'Command - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Management, gcmd Component, Basic Methods, Command Line Arguments, Command Line Options, Argument Retrieval, Option Retrieval, Custom Command Line] +description: "Basic functions of the gcmd component in the GoFrame framework, including how to retrieve command line arguments and options and their common methods. Examples demonstrate how to use the Init method to customize command line data, and how to utilize GetArg and GetOpt methods to retrieve command line arguments and options respectively. It details the implementation of argument retrieval and option retrieval, helping developers quickly master command management features in GoFrame." +--- + +The `gcmd` component provides commonly used basic package methods that allow you to directly retrieve command line arguments and options according to default parsing rules. + +## Common Methods + +For more component methods, please refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd) + +```go +func Init(args ...string) + +func GetArg(index int, def ...string) *gvar.Var +func GetArgAll() []string + +func GetOpt(name string, def ...string) *gvar.Var +func GetOptAll() map[string]string +``` + +## `Init` Custom Command Line + +By default, the `gcmd` component automatically parses and retrieves parameters and data from `os.Args`. You can customize command line data using the `Init` method. Example usage: + +```go +func ExampleInit() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetArgAll()) + + // Output: + // []string{"gf", "build", "main.go"} +} +``` + +## `GetArg*` Argument Retrieval + +Argument retrieval can be done through the following two methods: + +1. The `GetArg` method is used to retrieve the command line arguments parsed by default. Arguments are retrieved by input index position starting from `0`, though often the arguments we need to get start from `1` since the argument at index `0` is the program name. +2. The `GetArgAll` method retrieves all command line arguments. + +Example usage: + +```go +func ExampleGetArg() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf( + `Arg[0]: "%v", Arg[1]: "%v", Arg[2]: "%v", Arg[3]: "%v"`, + gcmd.GetArg(0), gcmd.GetArg(1), gcmd.GetArg(2), gcmd.GetArg(3), + ) + + // Output: + // Arg[0]: "gf", Arg[1]: "build", Arg[2]: "main.go", Arg[3]: "" +} + +func ExampleGetArgAll() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetArgAll()) + + // Output: + // []string{"gf", "build", "main.go"} +} +``` + +## `GetOpt*` Option Retrieval + +Option retrieval can be done through the following two methods: + +1. The `GetOpt` method is used to retrieve the command line options parsed by default. Options are retrieved by name, and their input is not ordered, meaning they can be input at any command line position. If the data for a given option name does not exist, it returns `nil`. Note that to check whether an option without data exists, you can use `GetOpt(name) != nil`. +2. The `GetOptAll` method retrieves all options. + +Example usage: + +```go +func ExampleGetOpt() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf( + `Opt["o"]: "%v", Opt["y"]: "%v", Opt["d"]: "%v"`, + gcmd.GetOpt("o"), gcmd.GetOpt("y"), gcmd.GetOpt("d", "default value"), + ) + + // Output: + // Opt["o"]: "gf.exe", Opt["y"]: "", Opt["d"]: "default value" +} + +func ExampleGetOptAll() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetOptAll()) + + // May Output: + // map[string]string{"o":"gf.exe", "y":""} +} +``` + +## `GetOptWithEnv` Feature + +```go +func GetOptWithEnv(key string, def ...interface{}) *gvar.Var +``` + +This method retrieves the specified option value from the command line and if the option does not exist, it reads from the environment variable instead. However, the naming rules differ. For example, `gcmd.GetOptWithEnv("gf.debug")` first reads the `gf.debug` option from the command line and if it does not exist, it reads the `GF_DEBUG` environment variable. + +Note the parameter naming conversion rules: + +- Environment variables convert names to uppercase, and `.` characters in names become `_`. +- On the command line, names are converted to lowercase and `_` characters in names become `.`. + +Example usage: + +```go +func ExampleGetOptWithEnv() { + fmt.Printf("Opt[gf.test]:%s\n", gcmd.GetOptWithEnv("gf.test")) + _ = genv.Set("GF_TEST", "YES") + fmt.Printf("Opt[gf.test]:%s\n", gcmd.GetOptWithEnv("gf.test")) + + // Output: + // Opt[gf.test]: + // Opt[gf.test]:YES +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" new file mode 100644 index 00000000000..9a4cfaeebf3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gcmd-scan' +title: 'Command - Interaction' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Management, Terminal Interaction, gcmd Component, User Input, Scan Method, Scanf Method, Terminal Reading, Interaction Example] +description: "The gcmd component in the GoFrame framework is used for interacting with users in a terminal environment. It primarily offers two core methods, Scan and Scanf, which facilitate convenient reading of user input from the terminal and interactive display in the command line interface. These practical features are suitable for the development of command-line programs that require interaction with user data input." +--- + +## Introduction + +The `gcmd` component supports reading user input data from the terminal, commonly used in terminal interaction scenarios. + +Related methods: + +```go +func Scan(info ...interface{}) string +func Scanf(format string, info ...interface{}) string +``` + +Both methods display the given information to the terminal and automatically read user input from the terminal, returning it upon pressing the Enter key. + +## `Scan` Usage + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" +) + +func main() { + name := gcmd.Scan("What's your name?\n") + fmt.Println("Your name is:", name) +} +``` + +After execution, interaction example: + +``` +> What's your name? +john +> Your name is: john +``` + +## `Scanf` Usage + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" +) + +func main() { + name := gcmd.Scan("> What's your name?\n") + age := gcmd.Scanf("> Hello %s, how old are you?\n", name) + fmt.Printf("> %s's age is: %s", name, age) +} +``` + +After execution, interaction example: + +``` +> What's your name? +john +> Hello john, how old are you? +18 +> john's age is: 18 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..31e52d6a084 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" @@ -0,0 +1,237 @@ +--- +slug: '/docs/core/gcmd-struct' +title: 'Command - Structure' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Command Line Management,Structured Parameters,Automatic Data Conversion,Parameter Validation,Command Line Tools,Data Type Conversion,Configuration Reading,Framework Development] +description: "Structured parameter processing for command line management through the GoFrame framework. Manage parent and child commands through object-oriented management, define standardized input parameter objects, and implement automatic data conversion and validation features for command lines. Using GoFrame's framework development tools, users can easily manage multiple command-line projects, support data reading from configurations, and improve project development efficiency and stability." +--- + +## Pain Points in Command Line Management + +Previously, we introduced command line management by obtaining parsed parameters and option data through the `parser` object of the callback function. The following pain points exist when using it: + +- Need to manually pass hard-coded parameter index or option name information to obtain data. +- Difficult to define descriptions and introductions for parameters/options. +- Difficult to define data types for parameters/options. +- Difficult to perform general data validation on parameters/options. +- It is a disaster for projects that need to manage a large number of command lines. + +## Object-Oriented Command Management + +Let's take a simple example of structured parameter management. We transform the previously introduced `Command` example into structured management: + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +type cMain struct { + g.Meta `name:"main"` +} + +type cMainHttpInput struct { + g.Meta `name:"http" brief:"start http server"` +} +type cMainHttpOutput struct{} + +type cMainGrpcInput struct { + g.Meta `name:"grpc" brief:"start grpc server"` +} +type cMainGrpcOutput struct{} + +func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) { + fmt.Println("start http server") + return +} + +func (c *cMain) Grpc(ctx context.Context, in cMainGrpcInput) (out *cMainGrpcOutput, err error) { + fmt.Println("start grpc server") + return +} + +func main() { + cmd, err := gcmd.NewFromObject(cMain{}) + if err != nil { + panic(err) + } + cmd.Run(gctx.New()) +} +``` + +As you can see, we manage the parent command in the form of an object, manage its subcommands in the form of methods, and define the description/parameters/options of subcommands through standardized `Input` input parameter objects. In most scenarios, you can ignore the use of the `Output` return object, but for standardization and extensibility, it must be retained. If not used, just return `nil` for this return parameter. The struct tags used will be introduced later. + +We compile the example code and run it to see the effect: + +```bash +$ main +USAGE + main COMMAND [OPTION] + +COMMAND + http start http server + grpc start grpc server + +DESCRIPTION + this is the command entry for starting your process +``` + +Using the `http` command: + +```bash +$ main http +start http server +``` + +Using the `grpc` command: + +```bash +$ main grpc +start grpc server +``` + +The effect is consistent with the previously introduced example. + +## Structured Parameter Management + +Since the command line is managed through objects, let's carefully look at how parameters/options are managed through structure. + +We simplify the above instance a bit for a simple example of starting an `http` service through the `http` command: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +type cMain struct { + g.Meta `name:"main" brief:"start http server"` +} + +type cMainHttpInput struct { + g.Meta `name:"http" brief:"start http server"` + Name string `v:"required" name:"NAME" arg:"true" brief:"server name"` + Port int `v:"required" short:"p" name:"port" brief:"port of http server"` +} +type cMainHttpOutput struct{} + +func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) { + s := g.Server(in.Name) + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello world") + }) + s.SetPort(in.Port) + s.Run() + return +} + +func main() { + cmd, err := gcmd.NewFromObject(cMain{}) + if err != nil { + panic(err) + } + cmd.Run(gctx.New()) +} +``` + +We defined two input parameters for the `http` command: + +- `NAME` The name of the service, entered through a parameter. The uppercase form is used here for easy display in the automatically generated help information. +- `port` The port of the service, entered through the `p/port` option. + +We also use the `v:"required"` validation tag to bind mandatory validation rules for these two parameters. Yes, in the `GoFrame` framework, a unified validation component is used wherever validation is involved. For details, please refer to the chapter: [Data Validation](../数据校验/数据校验.md) + +Let's compile it and see the effect: + +```bash +$ main http +arguments validation failed for command "http": The Name field is required +1. arguments validation failed for command "http" + 1). github.com/gogf/gf/v2/os/gcmd.newCommandFromMethod.func1 + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_object.go:290 + 2). github.com/gogf/gf/v2/os/gcmd.(*Command).doRun + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:120 + 3). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValueError + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:77 + 4). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValue + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:32 + 5). github.com/gogf/gf/v2/os/gcmd.(*Command).Run + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/test.go:38 +2. The Name field is required +``` + +Upon execution, there's an error due to data validation indicating that both mandatory parameters (`Name/Port`) must be passed. +:::tip +The error here prints stack information because the `GoFrame` framework uses a full error stack design, where all component errors come with a bottom-up error stack to facilitate quick error localization. Of course, we can obtain the returned error object and disable the stack information through the `RunWithError` method. +::: +Let's add parameter input and try again: + +```bash +$ main http my-http-server -p 8199 +2022-01-19 22:52:45.808 [DEBU] openapi specification is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + my-http-server | default | :8199 | ALL | / | main.(*cMain).Http.func1 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + my-http-server | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-01-19 22:52:45.810 66292: http server started listening on [:8199] +``` + +Yes, that's correct. + +## Complete Usage Example + +The development tool of the `GoFrame` framework typically uses object-oriented, structured command line management. If interested, you can check the source code for more understanding: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +![](/markdown/fa0dce21882f5ac6fb8014b287122e73.png) + +## Predefined Tags + +In structured design, we use some struct tags, most of which originate from the attributes of the `Command` command. Let's introduce them here: + +| Tag | Abbreviation | Description | Note | +| --- | --- | --- | --- | +| `name` | - | Name | If it is an input parameter structure, it will automatically read the **method name** as `name` when `name` is not specified | +| `short` | - | Command abbreviation | | +| `usage` | - | Command usage | | +| `brief` | - | Command description | | +| `arg` | - | Indicates the input parameter is from a parameter rather than an option | Only for attribute tags | +| `orphan` | - | Indicates the option is without parameters | Attributes are usually of `bool` type | +| `description` | `dc` | Detailed description of the command | | +| `additional` | `ad` | Additional description information of the command | | +| `examples` | `eg` | Usage examples of the command | | +| `root` | - | Specifies the subcommand name as the parent command, and other methods as its subcommands | Only for **main command** object struct `Meta` tags | +| `strict` | - | Indicates the command strictly parses parameters/options, returning an error when unsupported parameters/options are input | Only for object struct `Meta` tags | +| `config` | - | Indicates that option data for the command supports reading from a specified configuration, sourced from the default global singleton configuration object | Only for method input struct `Meta` tags | + +## Advanced Features + +### Automatic Data Conversion + +Structured parameter input supports automatic data type conversion. You just need to define the data types, and the rest is handled by the framework components. Automatic data type conversion is present in many components of the framework, especially in parameter inputs for `HTTP/GRPC` services. The underlying data conversion component used is: [Type Conversion](../类型转换/类型转换.md) +:::tip +The command line parameter data conversion uses **case insensitive, and ignores special characters** rules to match attribute fields. For example, if there is a `Name` field property in the input parameter structure, no matter whether the command line inputs `name` or `NAME` as a named parameter, it will be received by the `Name` field property. +::: +### Automatic Data Validation + +Similarly, the data validation component is also a unified component. Please refer to the chapter: [Data Validation](../数据校验/数据校验.md) for details. + +### Reading Data from Configuration + +When the corresponding data is not passed in the command line, the input parameter's structure data supports automatic acquisition from the configuration component, which only needs to set the `config` tag in `Meta`. The configuration source is the default global singleton configuration object. You can refer to the example in the `GoFrame` framework development tool source code: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..b421e7659e4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/core/gcmd-tracing' +title: 'Command - Tracing' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Management, Tracing, OpenTelemetry, Main Process, Sub Process, gcmd, gproc, gctx] +description: "Using the command management component of the GoFrame framework for tracing. Through the OpenTelemetry specification, GoFrame can achieve cross-process tracing, which is especially suitable for processes that run temporarily. The example demonstrates how to call sub-processes from the main process and automatically pass link information." +--- + +## Introduction + +The `GoFrame` command management component also supports cross-process tracing features, which are particularly useful for processes that run temporarily. + +The overall tracing of the framework adopts the `OpenTelemetry` specification. + +## Usage Example + +### Main Process + +`main.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "main process", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + g.Log().Debug(ctx, `this is main process`) + return gproc.ShellRun(ctx, `go run sub.go`) + }, + } +) + +func main() { + Main.Run(gctx.GetInitCtx()) +} +``` + +### Sub Process + +`sub.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Sub = &gcmd.Command{ + Name: "sub", + Brief: "sub process", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + g.Log().Debug(ctx, `this is sub process`) + return nil + }, + } +) + +func main() { + Sub.Run(gctx.GetInitCtx()) +} +``` + +### Execution Result + +After execution, the terminal output is as follows: + +```bash +$ go run main.go +2022-06-21 20:35:06.196 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is main process +2022-06-21 20:35:07.482 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is sub process +``` + +You can see that the link information has been automatically passed to the sub-process. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..94c2160dad4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/core/gcmd' +title: 'Command' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gcmd,Command Line Management,Command Line Parameters,Command Line Parsing,Cobra,Parameter Validation,Tracing,Command Line Help] +description: "The command line management component gcmd in the GoFrame framework. gcmd provides powerful command line management features, including flexible parameter management, custom parsing, multi-level command management, automatic type conversion, parameter validation, reading configuration component parameters, tracing, and automatic generation of command line help information." +--- + +## Introduction + +Programs need to manage program start entries through command lines, making command line management components one of the core components of the framework. The `GoFrame` framework provides a powerful command line management module, implemented by the `gcmd` component. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gcmd" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd) + +## Features + +The `gcmd` component has the following notable features: + +- Easy to use and powerful +- Flexible command line parameter management +- Support for flexible custom parsing through `Parser` +- Support for multi-level command line management and richer command line information +- Structured input/output management for large-scale command lines using object mode +- Automatic type conversion and validation for structured parameters +- Ability to read data from configuration components in a structured manner +- Automatic generation of command line help information +- Terminal input functionality support + +## Comparison with `Cobra` + +`Cobra` is a widely used command line management library in `Golang`. The open-source project can be found here: [https://github.com/spf13/cobra](https://github.com/spf13/cobra) + +When compared to `Cobra`, the `gcmd` command line component of the `GoFrame` framework shows similarities in basic functions, but with significant differences in parameter management and observability support: + +- The `gcmd` component supports structured parameter management with hierarchical object command line management, and method automatic generation without manual definition and parsing of parameter variables by developers. +- The `gcmd` component supports automated parameter type conversion for both basic and complex types. +- The `gcmd` component provides configurable common parameter validation capabilities to enhance parameter maintenance efficiency. +- The `gcmd` component allows reading parameters via configuration components when there are no terminal arguments. +- The `gcmd` component supports tracing to facilitate the transfer of trace information between parent and child processes. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..9ed47f502a0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" @@ -0,0 +1,199 @@ +--- +slug: '/docs/core/g' +title: 'Objects' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,Object Management,Data Types,Common Objects,Singleton Pattern,Configuration Management,Log Management,Template Engine,WEB Server,Redis Client] +description: "The GoFrame framework provides a series of commonly used data types and object acquisition methods. Through the g module, it is easy to obtain commonly used objects, including HTTP client, data validation, configuration management, log management, template engine, WEB server, database ORM, Redis client, etc., to achieve simple and efficient object management to meet the needs of different application scenarios." +--- + +The `GoFrame` framework encapsulates some commonly used data types and object acquisition methods, which can be obtained through the `g.*` method. +:::tip +`g` is a highly coupled module aimed at providing convenience to developers when frequently using certain types/objects. +::: +**Usage:** + +```go +import "github.com/gogf/gf/v2/frame/g" +``` + +## Data Types + +Common data type aliases. + +```go +type ( + Var = gvar.Var // Var is a universal variable interface, like generics. + Ctx = context.Context // Ctx is alias of frequently-used context.Context. +) + +type ( + Map = map[string]interface{} // Map is alias of frequently-used map type map[string]interface{}. + MapAnyAny = map[interface{}]interface{} // MapAnyAny is alias of frequently-used map type map[interface{}]interface{}. + MapAnyStr = map[interface{}]string // MapAnyStr is alias of frequently-used map type map[interface{}]string. + MapAnyInt = map[interface{}]int // MapAnyInt is alias of frequently-used map type map[interface{}]int. + MapStrAny = map[string]interface{} // MapStrAny is alias of frequently-used map type map[string]interface{}. + MapStrStr = map[string]string // MapStrStr is alias of frequently-used map type map[string]string. + MapStrInt = map[string]int // MapStrInt is alias of frequently-used map type map[string]int. + MapIntAny = map[int]interface{} // MapIntAny is alias of frequently-used map type map[int]interface{}. + MapIntStr = map[int]string // MapIntStr is alias of frequently-used map type map[int]string. + MapIntInt = map[int]int // MapIntInt is alias of frequently-used map type map[int]int. + MapAnyBool = map[interface{}]bool // MapAnyBool is alias of frequently-used map type map[interface{}]bool. + MapStrBool = map[string]bool // MapStrBool is alias of frequently-used map type map[string]bool. + MapIntBool = map[int]bool // MapIntBool is alias of frequently-used map type map[int]bool. +) + +type ( + List = []Map // List is alias of frequently-used slice type []Map. + ListAnyAny = []MapAnyAny // ListAnyAny is alias of frequently-used slice type []MapAnyAny. + ListAnyStr = []MapAnyStr // ListAnyStr is alias of frequently-used slice type []MapAnyStr. + ListAnyInt = []MapAnyInt // ListAnyInt is alias of frequently-used slice type []MapAnyInt. + ListStrAny = []MapStrAny // ListStrAny is alias of frequently-used slice type []MapStrAny. + ListStrStr = []MapStrStr // ListStrStr is alias of frequently-used slice type []MapStrStr. + ListStrInt = []MapStrInt // ListStrInt is alias of frequently-used slice type []MapStrInt. + ListIntAny = []MapIntAny // ListIntAny is alias of frequently-used slice type []MapIntAny. + ListIntStr = []MapIntStr // ListIntStr is alias of frequently-used slice type []MapIntStr. + ListIntInt = []MapIntInt // ListIntInt is alias of frequently-used slice type []MapIntInt. + ListAnyBool = []MapAnyBool // ListAnyBool is alias of frequently-used slice type []MapAnyBool. + ListStrBool = []MapStrBool // ListStrBool is alias of frequently-used slice type []MapStrBool. + ListIntBool = []MapIntBool // ListIntBool is alias of frequently-used slice type []MapIntBool. +) + +type ( + Slice = []interface{} // Slice is alias of frequently-used slice type []interface{}. + SliceAny = []interface{} // SliceAny is alias of frequently-used slice type []interface{}. + SliceStr = []string // SliceStr is alias of frequently-used slice type []string. + SliceInt = []int // SliceInt is alias of frequently-used slice type []int. +) + +type ( + Array = []interface{} // Array is alias of frequently-used slice type []interface{}. + ArrayAny = []interface{} // ArrayAny is alias of frequently-used slice type []interface{}. + ArrayStr = []string // ArrayStr is alias of frequently-used slice type []string. + ArrayInt = []int // ArrayInt is alias of frequently-used slice type []int. +) +``` + +## Common Objects + +Common objects are often managed through the `singleton pattern`, where specific object instances can be obtained according to different singleton names, and corresponding configuration items in the configuration file will be automatically retrieved during object initialization. For specific configuration items, please refer to the respective object's chapter introduction. +:::info +Note: During runtime, every time a singleton object is obtained through the `g` module, there will be an internal global lock mechanism to ensure concurrent safety of operations and data. In theory, there may be lock competition in scenarios with high concurrency, but in most business scenarios, developers do not need to overly worry about the performance loss caused by lock competition. Additionally, developers can also save the obtained singleton objects into internal variables of specific modules for reuse, thereby avoiding runtime lock competition situations. +::: +### `HTTP` Client Object + +```go +func Client() *ghttp.Client +``` + +Creates a new `HTTP` client object. + +### `Validator` Object + +```go +func Validator() *gvalid.Validator +``` + +Creates a new data validation object. + +### (Singleton) Configuration Management Object + +```go +func Cfg(name ...string) *gcfg.Config +``` + +This singleton object will automatically retrieve configuration files based on file extensions `toml/yaml/yml/json/ini/xml/properties`. By default, it will automatically retrieve the following configuration files: + +- `config` +- `config.toml` +- `config.yaml` +- `config.yml` +- `config.json` +- `config.ini` +- `config.xml` +- `config.properties` + +and cache them. The cache will automatically refresh if the configuration files are modified externally. + +For convenient calling of configuration files in scenarios with multiple files, to simplify usage and improve development efficiency, the singleton object will automatically use the singleton name for file retrieval at the time of creation. For example: the singleton object retrieved by `g.Cfg("redis")` will, by default, automatically retrieve the following files: + +- `redis` +- `redis.toml` +- `redis.yaml` +- `redis.yml` +- `redis.json` +- `redis.ini` +- `redis.xml` +- `redis.properties` + +If the retrieval is successful, the file will be loaded into memory cache, and next time it will directly read from memory; if the file does not exist, it will use the default configuration file (`config.toml`). + +### (Singleton) Log Management Object + +```go +func Log(name ...string) *glog.Logger +``` + +This singleton object will automatically read the `logger` configuration item from the default configuration file, and only initialize the log object once. + +### (Singleton) Template Engine Object + +```go +func View(name ...string) *gview.View +``` + +This singleton object will automatically read the `viewer` configuration item from the default configuration file and only initialize the template engine object once. It uses a `lazy initialization` design internally, creating a lightweight template management object when obtaining the template engine object, and only initializing it when parsing template files. + +### (Singleton) `WEB Server` + +```go +func Server(name ...interface{}) *ghttp.Server +``` + +This singleton object will automatically read the `server` configuration item from the default configuration file and only initialize the `Server` object once. + +### (Singleton) `TCP Server` + +```go +func TcpServer(name ...interface{}) *gtcp.Server +``` + +### (Singleton) `UDP Server` + +```go +func UdpServer(name ...interface{}) *gudp.Server +``` + +### (Singleton) Database `ORM` Object + +```go +func DB(name ...string) *gdb.Db +``` + +This singleton object will automatically read the `database` configuration item from the default configuration file and only initialize the `DB` object once. + +Additionally, the following method can be used to create a `Model` object on the default database: + +```go +func Model(tables string, db ...string) *gdb.Model +``` + +### (Singleton) `Redis` Client Object + +```go +func Redis(name ...string) *gredis.Redis +``` + +This singleton object will automatically read the `redis` configuration item from the default configuration file and only initialize the `Redis` object once. + +### (Singleton) Resource Management Object + +```go +func Res(name ...string) *gres.Resource +``` + +### (Singleton) Internationalization Management Object + +```go +func I18n(name ...string) *gi18n.Manager +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..3574a79c22c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/core/gdb-context' +title: 'ORM - Context' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Context Variables, Asynchronous IO Control, Trace, Context Variables, Request Timeout, Nested Transactions, Model Context Operations, ORM Transaction Handling] +description: "How to support custom context variables through ORM in the GoFrame framework to achieve asynchronous IO control, trace, nested transactions, etc. With the Ctx method, developers can easily pass custom context variables to achieve more complex request control and tracing. The article provides specific examples and recommendations for request timeout control and model context operations." +--- + +`ORM` supports passing custom `context` variables for asynchronous `IO` control, context information transmission (especially transmission of trace information), and nested transaction support. + +We can pass custom context variables to an `ORM` object using the `Ctx` method. The `Ctx` method is essentially a chainable operation, and the context passed in is only effective for the current `DB` interface object. The method is defined as follows: + +```go +func Ctx(ctx context.Context) DB +``` + +## Request Timeout Control + +Let's look at an example of controlling the request timeout duration using context variables. + +```go +ctx, cancel := context.WithTimeout(context.Background(), time.Second) +defer cancel() +_, err := db.Ctx(ctx).Query("SELECT SLEEP(10)") +fmt.Println(err) +``` + +In this example, executing `sleep 10` seconds will inevitably cause a timeout. After execution, the output is: + +```html +context deadline exceeded, SELECT SLEEP(10) +``` + +## Trace Information + +Context variables can also pass trace information, and when combined with the logging component, can print trace information to logs (only when `ORM` logging is enabled). Please refer to the trace topic chapter: [Service Tracing](../../服务可观测性/服务链路跟踪/服务链路跟踪.md). + +## Model Context Operations + +Model objects also support context variable passing, using the `Ctx` method as well. Let's look at a simple example by modifying the example from the second section using model operations. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + _, err := g.DB().Model("user").Ctx(gctx.New()).All() + if err != nil { + panic(err) + } +} +``` + +After execution, the terminal output is: + +```html +2020-12-28 23:46:56.349 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 5 ms] [default] [rows:0 ] SHOW FULL COLUMNS FROM `user` +2020-12-28 23:46:56.354 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 5 ms] [default] [rows:100] SELECT * FROM `user` +``` +:::tip +The `SHOW FULL COLUMNS FROM `user`` query is used by the `ORM` component to fetch data table fields, which queries each table only once before execution and caches the results in memory. +::: +## Nested Transaction Support + +Support for nested transactions relies on hierarchical passing of `Context` variables. For details, please refer to the chapter: [ORM - Transaction](ORM事务处理/ORM事务处理.md). \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" new file mode 100644 index 00000000000..811d4fe22f3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" @@ -0,0 +1,316 @@ +--- +slug: '/docs/core/gdb-transaction-nested' +title: 'ORM Transaction - Nested' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Nested Transactions, Transaction Handling, Database, SQL, Transaction Save Point, Rollback, Closure] +description: "Using the ORM functionality in the GoFrame framework to handle nested transactions. The article explains in detail the basic principles, methods, and log information of nested transactions, provides examples of regular operations and closure operations, and points out potential issues. Finally, a reference example of nested transactions in a project is provided to help developers understand how to apply this in real-world projects." +--- + +The `GoFrame ORM` supports database nested transactions, which are commonly used in business projects, especially in mutual calls between business modules, to ensure that the database operations of each business module are within a transaction. This is achieved by implicitly passing and associating the same transaction object through the `context`. It should be noted that database services often do not support nested transactions but rely on the `ORM` component layer to implement this using the `Transaction Save Point` feature. Similarly, we recommend using the `Transaction` closure method to implement nested transactions. For completeness, we will still introduce nested transaction operations starting from the most basic transaction operation methods here. + +## 1. Example SQL + +A simple example `SQL`, containing two fields `id` and `name`: + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL COMMENT 'User ID', + `name` varchar(45) NOT NULL COMMENT 'User Name', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +## 2. Regular Operations (Not Recommended) + +```go +db := g.DB() + +tx, err := db.Begin() +if err != nil { + panic(err) +} +if err = tx.Begin(); err != nil { + panic(err) +} +_, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() +if err = tx.Rollback(); err != nil { + panic(err) +} +_, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() +if err = tx.Commit(); err != nil { + panic(err) +} +``` + +### 1. `db.Begin` and `tx.Begin` + +In our nested transaction example, we see `db.Begin` and `tx.Begin` as two ways to start transactions. What is the difference between them? `db.Begin` actually starts a transaction operation on the database service and returns a transaction operation object `tx`. All subsequent transaction operations are managed through this `tx` transaction object. `tx.Begin` starts a nested transaction in the current transaction operation, and by default uses automatic naming for the nested transaction `SavePoint`. The naming format is `transactionN`, where `N` represents the nesting level. If you see the log showing ``SAVEPOINT `transaction1` ``, it indicates the current nesting level is `2` (starting from `0`). + +### 2. More Detailed Logs + +`goframe`'s `ORM` has a very comprehensive logging mechanism. If you enable `SQL` logging, you will see the following log information displaying the detailed execution process of the entire database request: + +```html +2021-05-22 21:12:10.776 [DEBU] [ 4 ms] [default] [txid:1] BEGIN +2021-05-22 21:12:10.776 [DEBU] [ 0 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:12:10.789 [DEBU] [ 13 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:12:10.790 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:12:10.791 [DEBU] [ 1 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:12:10.791 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:12:10.792 [DEBU] [ 1 ms] [default] [txid:1] COMMIT +``` + +Here, `[txid:1]` indicates the transaction ID recorded by the `ORM` component, and each real transaction operation will have a different ID, while nested transactions within the same real transaction share the same transaction ID. + +After execution, query the database result: + +``` +mysql> select * from `user`; ++----+-------+ +| id | name | ++----+-------+ +| 2 | smith | ++----+-------+ +1 row in set (0.00 sec) +``` + +You can see that the first operation was successfully rolled back, and only the second operation was successfully executed and committed. + +## 3. Closure Operations (Recommended) + +We can also implement nested transactions through closure operations using the `Transaction` method. + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := tx.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := tx.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := tx.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := tx.Model(table).Ctx(ctx).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +In the nested transaction closure, you do not necessarily have to use the `tx` object and can instead directly use the `db` object or `dao` package. This approach is more common, especially in method-level calls, as it means developers do not have to worry about passing the `tx` object or determine whether the current transaction should be nested; everything is automatically managed by the component, greatly reducing the mental workload for developers. However, be sure to pass the `ctx` context variable through every level. For example: + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +If you have enabled `SQL` logging, you will see the following log information displaying the detailed execution process of the entire database request: + +```html +2021-05-22 21:18:46.672 [DEBU] [ 2 ms] [default] [txid:1] BEGIN +2021-05-22 21:18:46.672 [DEBU] [ 0 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:18:46.673 [DEBU] [ 0 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:18:46.674 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:18:46.674 [DEBU] [ 0 ms] [default] [txid:1] RELEASE SAVEPOINT `transaction0` +2021-05-22 21:18:46.675 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:18:46.675 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`name`,`id`) VALUES('smith',2) +2021-05-22 21:18:46.675 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:18:46.676 [DEBU] [ 1 ms] [default] [txid:1] ROLLBACK +``` + +:::warning +If the `ctx` context variable is not passed through each layer, the nested transaction will fail. Let's look at an erroneous example: + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +Open the `SQL` execution log, and after execution, you will see the following log content: + +```html +2021-05-22 21:29:38.841 [DEBU] [ 3 ms] [default] [txid:1] BEGIN +2021-05-22 21:29:38.842 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:29:38.843 [DEBU] [ 1 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:29:38.845 [DEBU] [ 2 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:29:38.845 [DEBU] [ 0 ms] [default] [txid:1] RELEASE SAVEPOINT `transaction0` +2021-05-22 21:29:38.846 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:29:38.847 [DEBU] [ 1 ms] [default] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:29:38.848 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:29:38.848 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK +``` + +You can see that the second `INSERT` operation ``INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') `` has no transaction ID printed, indicating it was not used within a transaction and hence will be truly committed to the database execution and cannot be rolled back. +::: +## 4. `SavePoint/RollbackTo` + +Developers can also flexibly use the `Transaction Save Point` feature and implement custom `SavePoint` naming and designated `Point` rollback operations. + +```go +tx, err := db.Begin() +if err != nil { + panic(err) +} +defer func() { + if err := recover(); err != nil { + _ = tx.Rollback() + } +}() +if _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert(); err != nil { + panic(err) +} +if err = tx.SavePoint("MyPoint"); err != nil { + panic(err) +} +if _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert(); err != nil { + panic(err) +} +if _, err = tx.Model(table).Data(g.Map{"id": 3, "name": "green"}).Insert(); err != nil { + panic(err) +} +if err = tx.RollbackTo("MyPoint"); err != nil { + panic(err) +} +if err = tx.Commit(); err != nil { + panic(err) +} +``` + +If you open the `SQL` log, you will see the following log information displaying the detailed execution process of the entire database request: + +```html +2021-05-22 21:38:51.992 [DEBU] [ 3 ms] [default] [txid:1] BEGIN +2021-05-22 21:38:52.002 [DEBU] [ 9 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:38:52.002 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:38:52.003 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `MyPoint` +2021-05-22 21:38:52.004 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:38:52.005 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(3,'green') +2021-05-22 21:38:52.006 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `MyPoint` +2021-05-22 21:38:52.006 [DEBU] [ 0 ms] [default] [txid:1] COMMIT +``` + +After execution, query the database result: + +``` +mysql> select * from `user`; ++----+------+ +| id | name | ++----+------+ +| 1 | john | ++----+------+ +1 row in set (0.00 sec) +``` + +You can see that by saving a `SavePoint` named `MyPoint` after the first `Insert` operation, the subsequent operations were all rolled back using the `RollbackTo` method, so only the first `Insert` operation was successfully committed and executed. + +## 5. Reference Example of Nested Transactions in a Project + +To simplify the example, let's use a user module-related example, such as user registration, saving user's basic information (`user`) and detailed information (`user_detail`) to two tables through transaction operations. If any table operation fails, the entire registration operation will fail. To demonstrate the nested transaction effect, we separate the basic information and detailed information management into two `dao` objects. + +Assume our project follows the `goframe` standard project structuring divided into three layers: `api-service-dao`; our nested transaction operation might look like this. + +### `controller` + +```go +// User registration HTTP interface +func (*cUser) Signup(r *ghttp.Request) { + // .... + service.User().Signup(r.Context(), userServiceSignupReq) + // ... +} +``` + +Indicates the handling of the HTTP request and passes the `Context` context variable to subsequent processes. + +### `service` + +```go +// User registration business logic handling +func (*userService) Signup(ctx context.Context, r *model.UserServiceSignupReq) { + // .... + dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + err := dao.User.Ctx(ctx).Save(r.UserInfo) + if err != nil { + return err + } + err := dao.UserDetail.Ctx(ctx).Save(r.UserDetail) + if err != nil { + return err + } + return nil + }) + // ... +} +``` + +As you can see, the `user` table and `user_detail` table internally use nested transactions to perform unified transaction operations. Note that within the closure, use the `Ctx` method to pass the context variable to the next level. If there are calls to other `service` objects within the closure, you also need to pass the `ctx` variable, for example: + +```go +func (*userService) Signup(ctx context.Context, r *model.UserServiceSignupReq) { + // .... + dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) (err error) { + if err = dao.User.Ctx(ctx).Save(r.UserInfo); err != nil { + return err + } + if err = dao.UserDetail.Ctx(ctx).Save(r.UserDetail); err != nil { + return err + } + if err = service.XXXA().Call(ctx, ...); err != nil { + return err + } + if err = service.XXXB().Call(ctx, ...); err != nil { + return err + } + if err = service.XXXC().Call(ctx, ...); err != nil { + return err + } + // ... + return nil + }) + // ... +} +``` + +### `dao` + +The code for the `dao` layer can be fully automated and maintained by `goframe cli` tools. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..eb4f607b8b9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/core/gdb-transaction-basic' +title: 'ORM Transaction - Methods' +sidebar_position: 0 +hide_title: true +keywords: [transaction operations, Begin, Commit, Rollback, GoFrame, database operations, Transaction, closure method, gdb.Tx, chain operations] +description: "Basic operations for ORM transaction handling in the GoFrame framework, including how to use the Begin, Commit, and Rollback methods to start, commit, and rollback transactions. It is particularly important to close transactions promptly after use to avoid resource leaks, and it is recommended to use the Transaction closure method for safe transaction operations." +--- + +The usual transaction operation methods are `Begin/Commit/Rollback`, with each method specifying a specific transaction operation. A transaction operation can be initiated by executing the `db.Begin` method, which returns an interface for transaction operations, with the type `gdb.Tx`. You can perform subsequent database operations through this object, and you can submit changes with `tx.Commit` or rollback changes with `tx.Rollback`. +:::warning +Common Issues to Note: After initiating a transaction operation, be sure to close the transaction when no longer needed using `Commit`/`Rollback` operations. It is advisable to make good use of the `defer` method. If the transaction is not closed after use, it can lead to uncontrolled growth in goroutines on the application side and saturation of transaction threads on the database side, causing subsequent transaction requests to timeout. Furthermore, it is recommended to use the `Transaction` closure method introduced later for safely implementing transaction operations: [ORM Transaction - Closure](ORM事务处理-闭包操作.md) +::: +## 1. Initiating a Transaction + +```go +db := g.DB() + +if tx, err := db.Begin(ctx); err == nil { + fmt.Println("Initiating a transaction") +} +``` + +The transaction operation object can perform all methods of the `db` object. For more details, please refer to the [API documentation](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb). + +## 2. Transaction Rollback Operation + +```go +if tx, err := db.Begin(ctx); err == nil { + r, err := tx.Save("user", g.Map{ + "id" : 1, + "name" : "john", + }) + if err != nil { + tx.Rollback() + } + fmt.Println(r) +} +``` + +## 3. Transaction Commit Operation + +```go +if tx, err := db.Begin(ctx); err == nil { + r, err := tx.Save("user", g.Map{ + "id" : 1, + "name" : "john", + }) + if err == nil { + tx.Commit() + } + fmt.Println(r) +} +``` + +## 4. Transaction Chain Operations + +The transaction operation object can still return a chain operation object via the `tx.Model` method. This object is the same as the one returned by the `db.Model` method, except that database operations are performed on the transaction and can be committed or rolled back. + +```go +if tx, err := db.Begin(); err == nil { + r, err := tx.Model("user").Data(g.Map{"id":1, "name": "john_1"}).Save() + if err == nil { + tx.Commit() + } + fmt.Println(r) +} +``` + +For other chain operations, please refer to the [ORM - Model 🔥](../ORM链式操作/ORM链式操作.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..c0ade0abbec --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" @@ -0,0 +1,60 @@ +--- +slug: '/docs/core/gdb-transaction-closure' +title: 'ORM Transaction - Closure' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, ORM transaction handling, closure operation, transaction management, code simplification, nested transactions, automatic commit, automatic rollback, context parameters] +description: "The advantages of using closure operations for ORM transaction handling in the GoFrame framework, including reducing redundant code, lowering operational risks, and simplifying nested transactions, achieving automatic commit and rollback functions, and introducing the role of context parameters in nested transaction management to ensure transaction processing safety and simplicity." +--- + +## 1. Pain Points + +It can be seen that there are some issues with managing transactions using conventional transaction methods: + +- **Redundant code**. There are many repetitive `tx.Commit/Rollback` operations in the code. +- **High operational risk**. It is very easy to forget to execute `tx.Commit/Rollback` operations, or due to code logic bugs, causing the transaction operation not to close properly. In cases of self-managing transaction operations, most programmers encounter this pitfall. The author updates this description (2023-08-09) because a friend encountered a production incident due to improper handling of `tx.Commit/Rollback` in their transaction management. +- **Complex nested transaction implementation**. If there are multiple levels of transaction processing (nested transactions) in the business logic, it requires considering how to pass the `tx` object down, making it more cumbersome. + +## 2. Closure Operation + +Therefore, to facilitate safe transaction operations, the `ORM` component also provides a closure operation for transactions, implemented via the `Transaction` method, which is defined as follows: + +```go +func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) +``` + +When the `error` returned by the given closure method is `nil`, the current transaction automatically executes the `Commit` operation once the closure completes; otherwise, it automatically executes the `Rollback` operation. The `context.Context` parameter in the closure is a context variable introduced in `goframe v1.16`, mainly for link tracing transmission and nested transaction management. As the context variable is an important parameter for nested transaction management, it is defined for explicit parameter passing. +:::tip +If a `panic` interruption occurs within the closure operation, the transaction will also automatically rollback to ensure operation safety. +::: +Usage example: + +```go +g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error { + // user + result, err := tx.Ctx(ctx).Insert("user", g.Map{ + "passport": "john", + "password": "12345678", + "nickname": "JohnGuo", + }) + if err != nil { + return err + } + // user_detail + id, err := result.LastInsertId() + if err != nil { + return err + } + _, err = tx.Ctx(ctx).Insert("user_detail", g.Map{ + "uid": id, + "site": "https://johng.cn", + "true_name": "GuoQiang", + }) + if err != nil { + return err + } + return nil +}) +``` + +The closure operation method allows for easy implementation of nested transactions, and it is essentially transparent to upper-level business developers. For more details, you can continue reading the chapter: [ORM Transaction - Nested](ORM事务处理-嵌套事务.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..6ef54d89eb2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -0,0 +1,23 @@ +--- +slug: '/docs/core/gdb-transaction' +title: 'ORM - Transaction' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, ORM transaction, transaction handling, closure operation, transaction safety, nested transaction, database, transaction interface, Go development] +description: "Using the ORM component of the GoFrame framework for transaction operations is very simple and safe, with two methods: conventional operation and closure operation. Conventional operation begins a transaction with Begin, returning a transaction interface gdb.TX, while closure operation encapsulates transaction logic using the Transaction method and automatically manages transaction closure, supporting nested transactions. It is recommended to use closure operation to ensure transaction safety." +--- + +Using the `GoFrame ORM` component for transaction operations is very simple and safe, and can be achieved through two methods. + +1. Conventional Operation: After starting a transaction through `Begin`, it returns a transaction operation interface `gdb.TX`, which can then be used for operations and chain operations as introduced in previous sections. Conventional operations are prone to missing transaction closures, posing certain transaction safety risks. +2. Closure Operation: Transactions are handled in the form of closure methods using `Transaction`, where all transaction logic is implemented within the closure, and the transaction is automatically closed after the closure ends to ensure transaction safety. Additionally, closure operations support very convenient **nested transactions**, which are transparent and seamless in business operations. + +:::tip +We recommend all transaction operations be uniformly implemented using the `Transaction` closure method. +::: + +Interface Documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#TX](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#TX) + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..75b2da0d6ad --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,39 @@ +--- +slug: '/docs/core/gdb-config-faq' +title: 'ORM Configuration - FAQ' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, database encryption, custom Driver, mysql, password decryption, configuration file encryption, ORM interface development, database account protection, database connection] +description: "Implement database account password encryption in configuration files within the GoFrame framework to prevent the leakage of sensitive information. Users can decrypt the encrypted fields when connecting to the database by customizing a Driver. Using mysql as an example, the code examples demonstrate how to wrap the mysql driver and override its Open method to ensure the security and flexibility of database connections." +--- + +## How to Implement Database Account Password Encryption in Configuration Files + +In certain scenarios, database account passwords cannot be configured in plaintext within configuration files and must be encrypted. During the database connection, the encrypted fields in the configuration file need to be decrypted. This requirement can be achieved by customizing a `Driver` (for detailed information about `Driver`, please refer to the chapter: [ORM - Interface](../ORM接口开发/ORM接口开发.md)). Taking `mysql` as an example, we can write our own `Driver`, wrap the `mysql driver` from the framework community components, and override its `Open` method. Code example: + +```go +import ( + "database/sql" + + "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/database/gdb" +) + +type MyBizDriver struct { + mysql.Driver +} + +// Open creates and returns an underlying sql.DB object for mysql. +// Note that it converts time.Time argument to local timezone in default. +func (d *MyBizDriver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { + config.User = d.decode(config.User) + config.Pass = d.decode(config.Pass) + return d.Driver.Open(config) +} + +func (d *MyBizDriver) decode(s string) string { + // Execute field decryption logic + // ... + return s +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..59b6f9a5c7f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" @@ -0,0 +1,144 @@ +--- +slug: '/docs/core/gdb-config-file' +title: 'ORM Configuration - File' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Database Configuration, Configuration File, ORM, Database Management, yaml Configuration, Data Format, Cluster Mode, Log Configuration] +description: "Manage database configurations using GoFrame framework's configuration component, including the recommended yaml format configuration file. Easily obtain singleton objects for database operations through the g object. Supports configurations in multiple data formats and simplified connection methods, enabling cluster mode and log output functionality." +--- + +We recommend using the configuration component to manage database configurations and the `g.DB("database group name")` method in the g object management module to obtain database operation objects. The database object will automatically read the corresponding configuration items from the configuration component and automatically initialize the singleton object for that database operation. The database configuration management functionality is implemented using a configuration management component (the configuration component adopts an interface design and uses a file system implementation by default), and also supports various data formats like `toml/yaml/json/xml/ini/properties`. The default and recommended configuration file data format is `yaml`. + +## Simple Configuration +:::tip +From version `v2.2.0`, when using `link` for database configuration, the database component unifies the configuration formats for different database types to simplify configuration management. +::: +The simplified configuration is specified through the `link` configuration item, with the format as follows: + +```text +type:username:password@protocol(address)[/dbname][?param1=value1&...¶mN=valueN] +``` + +That is: + +```text +Type:Account:Password@Protocol(Address)/DatabaseName?FeatureConfiguration +``` + +Where: + +- **DatabaseName** and **FeatureConfiguration** are optional parameters; other parameters are required. +- **Protocol** optional configurations are: `tcp/udp/unix/file`, commonly `tcp`. +- **FeatureConfiguration** is defined by the third-party driver implemented by the database type, check the third-party driver's official website for specifics. For example, for the `mysql` driver, the third-party driver used is: [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) supporting features like `multiStatements` and `loc`. + +Example: + +```yaml +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + user: + link: "sqlite::@file(/var/data/db.sqlite3)" + local: + link: "mysql:username:password@unix(/tmp/mysql.sock)/dbname" +``` + +`link` examples for different data types are as follows: + +| Type | link Example | extra parameters | +| --- | --- | --- | +| `mysql` | ```mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `mariadb` | ```mariadb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `tidb` | ```tidb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `pgsql` | ```pgsql:root:12345678@tcp(127.0.0.1:5432)/test``` | [pq](https://github.com/lib/pq) | +| `mssql` | ```mssql:root:12345678@tcp(127.0.0.1:1433)/test?encrypt=disable``` | [go-mssqldb](https://github.com/microsoft/go-mssqldb) | +| `sqlite` | ```sqlite::@file(/var/data/db.sqlite3)``` | pure go:[go-sqlite](https://github.com/glebarez/go-sqlite)
32bit-cgo:[go-sqlite3](https://github.com/mattn/go-sqlite3) | +| `oracle` | ```oracle:root:12345678@tcp(127.0.0.1:5432)/test``` | [go-ora](https://github.com/sijms/go-ora) | +| `clickhouse` | ```clickhouse:root:12345678@tcp(127.0.0.1:9000)/test``` | [clickhouse-go](https://github.com/ClickHouse/clickhouse-go) | +| `dm` | ```dm:root:12345678@tcp(127.0.0.1:5236)/test``` | [dm](https://gitee.com/chunanyong/dm) | + +:::tip +For more database types supported by the framework, please refer to: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) +::: +## Complete Configuration + +The data format of the complete `config.yaml` database configuration item is as follows: + +```yaml +database: + default: # Group Name, customizable, default is default + host: "127.0.0.1" # Address + port: "3306" # Port + user: "root" # Username + pass: "your_password" # Password + name: "your_database" # Database Name + type: "mysql" # Database Type (e.g., mariadb/tidb/mysql/pgsql/mssql/sqlite/oracle/clickhouse/dm) + link: "" # (Optional) Custom database link information, if this field is set, the above link fields (Host, Port, User, Pass, Name, Type) will be invalid + extra: "" # (Optional) Additional feature configurations for different databases, defined by the underlying database driver, please see the specific database driver overview for what configurations are available + role: "master" # (Optional) Database master-slave role (master/slave), default is master. If not using application master-slave mechanism please do not configure or leave empty. + debug: false # (Optional) Enable debug mode + prefix: "gf_" # (Optional) Table name prefix + dryRun: false # (Optional) ORM Dry Run (read-only, no writing) + charset: "utf8" # (Optional) Database encoding (e.g., utf8mb4/utf8/gbk/gb2312), usually set to utf8mb4. Default is utf8. + protocol: "tcp" # (Optional) Database connection protocol, default is TCP + weight: 100 # (Optional) Load balancing weight, used for load balancing control, not using application layer load balancing mechanism please leave empty + timezone: "Local" # (Optional) Time zone configuration, e.g., Local + namespace: "" # (Optional) To support the catalog & schema distinction problem of some database services, the original schema represents the database name, while the NameSpace represents the schema of some database services + maxIdle: 10 # (Optional) Maximum number of idle connections in the connection pool (default 10) + maxOpen: 100 # (Optional) Maximum number of open connections in the connection pool (default unlimited) + maxLifetime: "30s" # (Optional) Duration of reuse for connection objects (default 30 seconds) + queryTimeout: "0" # (Optional) Query statement timeout duration (default unlimited, also affected by ctx timeout). The value is in time.Parse supported formats, such as 30s, 1m. + execTimeout: "0" # (Optional) Write statement timeout duration (default unlimited, also affected by ctx timeout). The value is in time.Parse supported formats, such as 30s, 1m. + tranTimeout: "0" # (Optional) Transaction processing timeout duration (default unlimited, also affected by ctx timeout). The value is in time.Parse supported formats, such as 30s, 1m. + prepareTimeout: "0" # (Optional) Prepared SQL statement execution timeout duration (default unlimited, also affected by ctx timeout). The value is in time.Parse supported formats, such as 30s, 1m. + createdAt: "created_at" # (Optional) Field name for automatic creation time + updatedAt: "updated_at" # (Optional) Field name for automatic update time + deletedAt: "deleted_at" # (Optional) Field name for soft delete time + timeMaintainDisabled: false # (Optional) Whether to completely disable the time update feature, when true CreatedAt/UpdatedAt/DeletedAt will all be invalid +``` +:::note +When using this configuration method, **to ensure database security, the underlying by default does not support multi-line `SQL` statement execution**. For more configuration control, refer to the recommended simplified `link` configuration, and understand the function of each connection parameter in the simplified configuration, as well as the official additional configuration parameters for the corresponding driver. +::: +## Cluster Mode + +`gdb` supports cluster mode configuration; each configuration item group in database configurations can be multiple nodes, supporting load balancing weight strategies, for example: + +```yaml +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "slave" + + user: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "slave" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "slave" +``` + +The database configuration example above includes two database groups `default` and `user`, where the `default` group contains one master and one slave, and the `user` group contains one master and two slaves. In the code, you can use `g.DB()` and `g.DB("user")` to get the corresponding database connection object. + +## Log Configuration + +`gdb` supports log output, using the `glog.Logger` object internally for log management, and it can be configured through configuration files. By default, `gdb` disables the `DEBUG` log output; to enable `DEBUG` messages, set the database's `debug` parameter to `true`. Below is a configuration file example: + +```yaml +database: + logger: + path: "/var/log/gf-app/sql" + level: "all" + stdout: true + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user_center" + debug: true +``` + +Where `database.logger` is the log configuration for `gdb`, which is a special configuration item. When this configuration does not exist, the default configuration of the logging component will be used. +For more details, refer to the [Logging - Configuration](../../../../docs/核心组件/日志组件/日志组件-配置管理.md) section. +:::warning +Please note: since the `ORM` uses a secure preprocessing execution method at the lower layer, the submitted `SQL` and parameters are actually separate. Therefore, the complete `SQL` recorded in logs is for reference and convenient manual reading only and is not the actual `SQL` submitted to the lower layer. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..4deb70d6cb4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/core/gdb-config-funcs' +title: 'ORM Configuration - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, ORM Usage Configuration, Database Node Management, Connection Property Configuration, Database Cluster, Relational Database, Read-Write Separation, Load Balancing, Configuration Management Features, GoFrame Framework] +description: "Configure the gdb database module in the GoFrame framework. It mainly covers data structure design, features, and specific configuration methods. By configuring ConfigNode and ConfigGroup, developers can flexibly manage multi-node database clusters to achieve efficient load balancing and read-write separation. Additionally, examples of default group and custom global configuration are provided." +--- +:::tip +Below is an introduction to the configuration of database underlying management. If you are interested in the underlying configuration management of databases, you can continue reading the following sections. +::: +## Data Structure + +The internal configuration management data structure of the `gdb` database management module is as follows: + +`ConfigNode` is used to store database node information; `ConfigGroup` manages configuration groups consisting of multiple database nodes (typically, a group corresponds to a business database cluster); `Config` manages multiple `ConfigGroup` configuration groups. + +**Configuration Management Features:** + +1. Supports management of multi-node database clusters; +2. Each node can individually configure connection properties; +3. Uses a singleton pattern to manage database instance objects; +4. Supports database cluster group management and access to instantiated database operation objects according to group names; +5. Supports management of various relational databases, configurable through the `ConfigNode.Type` attribute; +6. Supports `Master-Slave` read-write separation, configurable through the `ConfigNode.Role` attribute; +7. Supports client load balancing management, configurable through the `ConfigNode.Weight` attribute, where a higher value indicates a higher priority; + +```go +type Config map[string]ConfigGroup // Database configuration object +type ConfigGroup []ConfigNode // Database group configuration +// Database configuration item (one configuration item corresponds to multiple configuration items) +type ConfigNode struct { + Host string // Address + Port string // Port + User string // User + Pass string // Password + Name string // Database name + Type string // Database type: mysql, sqlite, mssql, pgsql, oracle + Link string // (Optional) Custom link information. When set, the above link fields (Host,Port,User,Pass,Name) will be invalid (this field is an extension feature) + Extra string // (Optional) Additional feature configuration for different databases, defined by the underlying database driver + Role string // (Optional, default is master) Database role, used for master-slave operation separation, at least one master is required, parameter value: master, slave + Debug bool // (Optional) Enable debug mode + Charset string // (Optional, default is utf8) Encoding, default is utf8 + Prefix string // (Optional) Table name prefix + Weight int // (Optional) Weight for load balancing calculation. When there is only one node in the cluster, the weight is meaningless + MaxIdleConnCount int // (Optional) Maximum number of idle connections in the connection pool + MaxOpenConnCount int // (Optional) Maximum number of open connections in the connection pool + MaxConnLifetime time.Duration // (Optional, in seconds) Time duration for which a connection object can be reused +} +``` + +It is worth mentioning that the greatest **feature** of `gdb` configuration management is that (within the same process), all database cluster information is uniformly maintained by the same configuration management module, and **different businesses use different group names** for database cluster configuration and retrieval. + +## Configuration Method + +This is a native call to the `gdb` module to configure database management. Developers who want to control their database configuration management independently can refer to the following methods. If not needed, this section can be ignored. + +API Documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +```go +// Add a database node to a specified group +func AddConfigNode(group string, node ConfigNode) +// Add a configuration group to database configuration management (overwrites if the same name exists) +func AddConfigGroup(group string, nodes ConfigGroup) + +// Add a database node to the default group (default is 'default', can be changed) +func AddDefaultConfigNode(node ConfigNode) +// Add a configuration group to the database configuration management (default group is 'default', can be changed) +func AddDefaultConfigGroup(nodes ConfigGroup) + +// Set the default group name, which will be automatically read when obtaining the default database object +func SetDefaultGroup(groupName string) + +// Set the database configuration to defined configuration information, overriding the original configuration +func SetConfig(c Config) +``` + +The default group means that if you do not specify a configuration group name when obtaining a database object, the `gdb` will default to reading the configuration group. For example, `gdb.NewByGroup()` can be used to obtain a database object of the default group. As a simple approach, we can use the `SetConfig` configuration management method of the `gdb` package to perform custom global database configuration, for example: + +```go +gdb.SetConfig(gdb.Config { + "default" : gdb.ConfigGroup { + gdb.ConfigNode { + Host : "192.168.1.100", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "master", + Weight : 100, + }, + gdb.ConfigNode { + Host : "192.168.1.101", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "slave", + Weight : 100, + }, + }, + "user-center" : gdb.ConfigGroup { + gdb.ConfigNode { + Host : "192.168.1.110", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "master", + Weight : 100, + }, + }, +}) +``` + +Subsequently, we can use `gdb.NewByGroup("database group name")` to obtain a database operation object. This object is used for a series of subsequent database methods/chain operations. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..422cd0bc83d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-config' +title: 'ORM - Configuration' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Configuration, Database Configuration, gdb, Configuration Guide, ORM Usage, GoFrame Database, GoFrame ORM, Data Storage] +description: "In the GoFrame framework, perform ORM configuration to provide developers with a detailed database configuration guide, helping users better manage data storage using GoFrame." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..a19bcfbe8a7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,50 @@ +--- +slug: '/docs/core/gdb-faq' +title: 'ORM - FAQ' +sidebar_position: 11 +hide_title: true +keywords: [ORM FAQ, GoFrame, GoFrame framework, database connection pool, MaxLifeTime, SQL query, database driver, debug log, empty array condition, character set settings] +description: "Common issues that may be encountered while performing ORM operations with the GoFrame framework and their solutions, including connection errors caused by expired database connection pools, ineffective update and insert operations, inability to find database drivers, problems with query conditions having WHERE 0=1, and encoding issues with storing emojis in MySQL tables. Some configuration recommendations are also provided to optimize the experience." +--- + +## `driver: bad connection` + +![](/markdown/7b384b6f57115b11938d9c0a30dde732.png) + +If this error occurs during database execution, it may be due to the local database connection pool connections having expired. You can check if the `MaxLifeTime` configuration set on the client exceeds the maximum timeout set by the database server. For more client configurations, please refer to the section: [ORM - Configuration](./ORM使用配置/ORM使用配置.md) + +## `update/insert` operations ineffective + +When using `orm`, in the configuration file: + +```toml +dryRun = "(optional) ORM dry run (read-only, no write)" +``` + +This line of configuration must be deleted or set to 0 + +Otherwise, `update insert` operations will not be effective. + +## `cannot find database driver for specified database type "xxx", did you misspell type name "xxx" or forget importing the database driver?` + +The program code has not included the dependent database driver. It is important to note that from `GoFrame v2.1` onwards, community drivers need to be manually introduced. Please refer to: + +- [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## After enabling `DEBUG` log for the database, `SQL` statements show `WHERE 0=1` + +The occurrence of `WHERE 0=1` happens due to the presence of an array condition in the query condition with an array length of `0`. The `ORM` cannot automatically filter out these empty array conditions (such filtering might cause business exceptions). Developers need to explicitly call `OmitEmpty` or `OmitEmptyWhere` based on the business scenario to instruct the `ORM` to filter these empty array conditions. + +## Emoji storage in MYSQL tables results in encoding issues + +![](/markdown/867e951b823bb2652a6b7d62f70a1ff3.png) + +Solution: + +In the `config.toml` file, set the database configuration `charset` to `utf8mb4` instead of the default `utf8`. + +When storing emojis in `MySQL`, note the following: + +- Database encoding is `utf8mb4` +- Table encoding is `utf8mb4` +- Content fields in the table are `utf8mb4` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..982df8b29f2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" @@ -0,0 +1,94 @@ +--- +slug: '/docs/core/gdb-interface-callback' +title: 'ORM Interface - Callback' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,ORM Interface,SQL Statements,Custom Callback,Logging,Authentication Operations,Database Driver,MySQL Driver,gdb Interface,Framework Override] +description: "When developing ORM interfaces using the GoFrame framework, custom callback handling is used to log or authenticate SQL statements. By implementing and overriding interface methods like DoQuery, DoExec, etc., developers can inject custom logic into the default implementation. The example demonstrates how to customize a MySQL driver to log executed SQL statements and configure gdb to use that driver." +--- + +## Introduction + +Custom callback handling is the most common implementation in interface development, where it involves **replacing and modifying** some methods in the interface to inject custom logic into the default implementation of the driver. By referring to the interface relationship diagram ([ORM - Interface](ORM接口开发.md)), we understand that all `SQL` statement executions will pass through the `DoQuery`, `DoExec`, or `DoFilter` interfaces. Depending on the requirements, you can **implement and override** the relevant interface methods in the custom driver to achieve the desired functionality. + +A common use case is to perform **log recording or unified security checks** on `SQL` at the `ORM` lower layer. + +## Example Usage + +Let's look at an example of custom callback handling. Suppose we need to log all executed `SQL` statements into a `monitor` table for `SQL` auditing purposes. The easiest way to achieve this is by creating a custom `Driver` and then overriding the underlying interface methods of `ORM`. To simplify the example, the following code demonstrates a custom `MySQL` driver, which inherits from the `mysql` module's `Driver` under `drivers`. + +```go +package driver + +import ( + "context" + + "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/gtime" +) + +// MyDriver is a custom database driver, which is used for testing only. +// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver +// gdb.Driver and overwrites its functions DoQuery and DoExec. +// So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly +// and then gdb.Driver.DoQuery/gdb.Driver.DoExec. +// You can call it sql "HOOK" or "HiJack" as your will. +type MyDriver struct { + *mysql.Driver +} + +var ( + // customDriverName is my driver name, which is used for registering. + customDriverName = "MyDriver" +) + +func init() { + // It here registers my custom driver in package initialization function "init". + // You can later use this type in the database configuration. + if err := gdb.Register(customDriverName, &MyDriver{}); err != nil { + panic(err) + } +} + +// New creates and returns a database object for mysql. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &MyDriver{ + &mysql.Driver{ + Core: core, + }, + }, nil +} + +// DoCommit commits current sql and arguments to underlying sql driver. +func (d *MyDriver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) { + tsMilliStart := gtime.TimestampMilli() + out, err = d.Core.DoCommit(ctx, in) + tsMilliFinished := gtime.TimestampMilli() + _, _ = in.Link.ExecContext(ctx, + "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", + gdb.FormatSqlWithArgs(in.Sql, in.Args), + tsMilliFinished-tsMilliStart, + gtime.Now(), + err, + ) + return +} +``` + +We see that a custom driver is registered with a unique name in the package initialization function `init` using `gdb.Register("MyDriver", &MyDriver{})`. We can also override the existing framework `mysql` driver with our own driver using `gdb.Register("mysql", &MyDriver{})`. +:::tip +The driver name `mysql` is the default name for the framework's `DriverMysql`. +::: +Since we are using a new driver name `MyDriver`, it is necessary to specify this driver name in the `type` field in the `gdb` configuration. Below is an example configuration: + +```yaml +database: + default: + - link: "MyDriver:root:12345678@tcp(127.0.0.1:3306)/user" +``` + +## Notes + +In the implementation of interface methods, use the `Link` input object parameter to operate on the database. Using the `g.DB` method to get a database object for operations may lead to deadlock issues. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..b1c95ca7619 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/core/gdb-interface-driver' +title: 'ORM Interface - Driver' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, database driver, custom driver, interface development, driver development, database component, gdb module, Driver interface, ORM] +description: "ORM interface development in the GoFrame framework, specifically for database driver development and registration. By implementing the interface of the gdb module, you can add third-party database drivers not supported by GoFrame by default or customize existing drivers, ensuring consistency in upper-layer business operations. This document provides detailed steps and sample code to help developers get started quickly." +--- + +The significance of the framework's database component driver is that the various methods of upper-layer business operations with the database do not change, and you can switch to a new database by simply modifying the database type in the configuration. + +We can achieve the design of interfaces in the database component to add third-party database drivers not supported by the framework by default or customize existing drivers. The development of a driver is not about fully developing a database protocol implementation code, but rather using an existing third-party database driver and integrating it with the framework's database component by implementing its interface, ensuring consistency in upper-layer operations. + +## Driver Registration + +We previously mentioned the `Driver` interface. After implementing this interface, we can register a custom driver to the `gdb` module through the following method: + +```go +// Register registers custom database driver to gdb. +func Register(name string, driver Driver) error +``` + +The driver name `name` can be an existing driver name, such as `mysql`, `mssql`, `pgsql`, etc. When a driver with the same name is registered, the new driver will overwrite the old one. + +## Driver Implementation + +Developing a custom driver and registering it to the `gdb` module is very simple. You can refer to the database type code examples already connected in the `gdb` module source code: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +It should be noted that the most common way to develop or modify a driver is to directly inherit from the existing `*Core` type, as the `Driver` interface will pass this type of object, for example: + +```go +// DriverMysql is the driver for mysql database. +type DriverMysql struct { + *Core +} + +// New creates and returns a database object for mysql. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) { + return &DriverMysql{ + Core: core, + }, nil +} +``` + +## Considerations + +A new driver should at least implement the following interface methods: + +| Interface Method | Description | +| --- | --- | +| `Open` | Used to create a database connection. | +| `GetChars` | Used to get the safety/escape symbols of the database. | +| `Tables` | Returns the list of tables in the current/specified database. | +| `TableFields` | Returns field list information for the specified table. | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..d466fd4de6b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/core/gdb-interface' +title: 'ORM - Interface' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Interface, gdb Module, Database Operations, Interface Design, Open Method, Driver Interface, Custom Implementation, SQL Submission] +description: "In the GoFrame framework, the ORM interface development of the gdb module allows developers to customize database operation implementations easily through flexible interface design. The DB interface serves as the core interface, providing methods for database connection creation, querying, and execution, while the Driver interface enables users to define their own driver implementations. Detailed interface documentation and method descriptions will help you get started quickly and proceed with secondary development." +--- + +The `gdb` module uses highly flexible and extensible interface design, allowing developers to easily customize implementations and replace any methods defined in the interface. + +## `DB` Interface + +Interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB) + +The `DB` interface is the core interface for database operations and the most commonly used interface when using `ORM` to operate on databases. Here, we mainly explain several important methods of the interface: + +1. The `Open` method is used to create a specific database connection object, returning the standard library's `*sql.DB` general database object. +2. The first parameter `link` of the `Do*` series methods is a `Link` interface object, which may be a master node object or a slave node object in a `master-slave` mode. Therefore, when using this `link` parameter in inherited driver object implementations, be aware of the current running mode. In most database master-slave modes, `slave` nodes are often not writable. +3. The `HandleSqlBeforeCommit` method will be called for some pre-commit callback processing when each `SQL` is submitted to the database server for execution. +4. For other interface methods, please refer to the interface documentation or source files. + +## `DB` Interface Relationships + +![](/markdown/1f5e48cc947e21dbed2745f69254935a.png) + +`GoFrame ORM Relations` + +## `Driver` Interface + +Interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Driver](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Driver) + +Custom drivers need to implement the following interface: + +```go +// Driver is the interface for integrating sql drivers into package gdb. +type Driver interface { + // New creates and returns a database object for specified database server. + New(core *Core, node *ConfigNode) (DB, error) +} +``` + +The `New` method is used to create a database operation object corresponding to the driver based on the `Core` database base object and the `ConfigNode` configuration object. It should be noted that the returned database object needs to implement the `DB` interface. The database base object `Core` has already implemented the `DB` interface, so developers only need to "inherit" the `Core` object and then override the corresponding interface implementation methods as needed. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" new file mode 100644 index 00000000000..1b617b7caf0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/gdb-funcs' +title: 'ORM - Method Ops (Native)' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, SQL Operations, Database Query, Data Insertion, Data Update, Data Deletion, Batch Operations, Chain Operations] +description: "Using the GoFrame framework for native SQL ORM method operations. This guide explains how to execute complex SQL operations through method manipulation, including database queries, data insertion, updating, deletion, and batch operations, providing detailed code examples." +--- + +## Method Operations + +Method operations are used for native `SQL` execution, which are more foundational compared to chain operations. When `ORM` chain operations are unable to handle overly complex `SQL` operations, method operations can be used instead. + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB) + +**Common Methods:** + +The list of methods in this document may lag behind the code. For a detailed list of methods, please refer to the API documentation. The following methods are for reference only. + +```go +// SQL operation methods, returning native standard library sql objects +Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) +Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) +Prepare(ctx context.Context, query string) (*sql.Stmt, error) + +// Database record queries: +// Query single record, query multiple records, get record object, query single field value (similar to chain operations) +GetAll(ctx context.Context, sql string, args ...interface{}) (Result, error) +GetOne(ctx context.Context, sql string, args ...interface{}) (Record, error) +GetValue(ctx context.Context, sql string, args ...interface{}) (Value, error) +GetArray(ctx context.Context, sql string, args ...interface{}) ([]Value, error) +GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) +GetScan(ctx context.Context, objPointer interface{}, sql string, args ...interface{}) error + +// Single data operation +Insert(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) +Replace(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) +Save(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) + +// Data update/delete +Update(ctx context.Context, table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) +Delete(ctx context.Context, table string, condition interface{}, args ...interface{}) (sql.Result, error) +``` + +**Brief Description:** + +1. `Query` is the original data query method that returns the native standard library's result set object, requiring manual parsing. It is recommended to use the `Get*` methods, which automatically parse the results. +2. `Exec` is used for `SQL` operations related to writing/updating. +3. It is recommended to use the `Get*` series of query methods for data queries. +4. The `data` parameter in the `Insert`/`Replace`/`Save` methods supports data types: `string/map/slice/struct/*struct`. When passed as a `slice` type, it is automatically recognized as a batch operation, and the `batch` parameter is effective. + +## Operation Examples + +### 1. `ORM` Object + +```go +// Get the database object with default configuration (configuration name is "default") +db := g.DB() + +// Get the database object with the configuration group name "user-center" +db := g.DB("user-center") + +// Use the native singleton management method to get the database object singleton +db, err := gdb.Instance() +db, err := gdb.Instance("user-center") + +// Note that there is no need to use the Close method to close the database connection when not in use (and gdb does not provide a Close method). +// The underlying design of the database engine uses connection pooling, and the connection will automatically close when it is no longer in use. +``` + +### 2. Data Insertion + +```go +r, err := db.Insert(ctx, "user", gdb.Map { + "name": "john", +}) +``` + +### 3. Data Query (List) + +```go +list, err := db.GetAll(ctx, "select * from user limit 2") +list, err := db.GetAll(ctx, "select * from user where age > ? and name like ?", g.Slice{18, "%john%"}) +list, err := db.GetAll(ctx, "select * from user where status=?", g.Slice{1}) +``` + +### 4. Data Query (Single Record) + +```go +one, err := db.GetOne(ctx, "select * from user limit 2") +one, err := db.GetOne(ctx, "select * from user where uid=1000") +one, err := db.GetOne(ctx, "select * from user where uid=?", 1000) +one, err := db.GetOne(ctx, "select * from user where uid=?", g.Slice{1000}) +``` + +### 5. Data Save + +```go +r, err := db.Save(ctx, "user", gdb.Map { + "uid" : 1, + "name" : "john", +}) +``` + +### 6. Batch Operations + +The `batch` parameter is used to specify the number of records to be written in batches during batch operations (default is `10`). + +```go +_, err := db.Insert(ctx, "user", gdb.List { + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, + {"name": "john_4"}, +}, 10) +``` + +### 7. Data Update/Delete + +```go +// db.Update/db.Delete likewise +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", gdb.Map {"name": "john"}, "uid=?", 10000) +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", "name='john'", "uid=10000") +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", "name=?", "uid=?", "john", 10000) +``` + +Note that it is supported and recommended to use prepared statement mode (using `?` placeholders) for input to avoid `SQL` injection risks. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..fc9595e044a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" @@ -0,0 +1,99 @@ +--- +slug: '/docs/core/gdb-timezone' +title: 'ORM - Timezone' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame framework, ORM timezone handling, MySQL timezone, time.Time, database driver, timezone conversion, loc parameter, timezone settings, configuration file, time.Parse] +description: "Handles timezone issues in ORM operations within the GoFrame framework, especially for timezone conversion when using the MySQL database. We explain how to control the timezone processing of time.Time objects submitted to the database by setting the loc parameter, providing relevant code examples and configuration advice to help developers better manage timezone during database operations." +--- + +## Introduction + +Since this issue is frequently asked, we have created a separate chapter to detail how timezone handling works in `ORM`. Here, we use the `MySQL` database as an example to explain the timezone conversion, with our local timezone set to `+8` and the database timezone also `+8`. + +The most commonly used `MySQL` database driver is this third-party package: [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql), which contains a parameter: + +![](/markdown/86813361650854a9b17490267709df8a.png) + +In essence, this parameter is used to convert the timezone for the `time.Time` when you submit it as a time parameter. When connecting to the database with the `loc=Local` parameter, the `driver` will automatically convert your submitted `time.Time` parameters to the local timezone set by the program; if not manually set, it defaults to the `UTC` timezone. Let's look at two examples. + +## Conversion Examples + +### Example 1, Setting `loc=Local` + +**Configuration File** + +```yaml +database: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local" +``` + +**Code Example** + +```go +t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 10:00:00") +t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 11:00:00") +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 18:00:00' AND create_time<'2020-10-27 19:00:00' +``` + +Here, since the `time.Time` object created by `time.Parse` is in the `UTC` timezone, it will be modified to the `+8` timezone by the underlying `driver` when submitted to the database. + +```go +t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 10:00:00", time.Local) +t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 11:00:00", time.Local) +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 10:00:00' AND create_time<'2020-10-27 11:00:00' +``` + +Here, since the `time.Time` object created by `time.ParseInLocation` is in the `+8` timezone, which is consistent with the `loc=Local` timezone, it will not be modified by the underlying `driver` when submitted to the database. +:::warning +Note that when inserting data that includes `time.Time` parameters, attention should also be paid to timezone conversion. +::: +### Example 2, Not Setting the `loc` Parameter + +**Configuration File** + +```yaml +database: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +``` + +**Code Example** + +```go +t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 10:00:00") +t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 11:00:00") +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 10:00:00' AND create_time<'2020-10-27 11:00:00' +``` + +Since the `time.Time` object created by `time.Parse` is in the `UTC` timezone, it will not be modified by the underlying `driver` when submitted to the database. + +```go +t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 10:00:00", time.Local) +t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 11:00:00", time.Local) +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 02:00:00' AND create_time<'2020-10-27 03:00:00' +``` + +Since the `time.Time` object created by `time.ParseInLocation` is in the `+8` timezone, it will be modified to `UTC` timezone by the underlying `driver` when submitted to the database. +:::warning +Note that when inserting data that includes `time.Time` parameters, attention should also be paid to timezone conversion. +::: +## Improvement Suggestions + +It is recommended to consistently use the `loc=Local` configuration in your settings, for example (MySQL): `loc=Local&parseTime=true`. Here's a reference configuration: + +```yaml +database: + logger: + level: "all" + stdout: true + default: + link: "mysql:root:12345678@tcp(192.168.1.10:3306)/mydb?loc=Local&parseTime=true" + debug: true + order: + link: "mysql:root:12345678@tcp(192.168.1.20:3306)/order?loc=Local&parseTime=true" + debug: true +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..eedb686bc5e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/core/gdb-practice' +title: 'ORM - Best Practices' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Database Operations, ORM Best Practices, Database Components, GoFrame Database, Business Projects, Flexible Operations, Best Practice Examples, Reference Learning] +description: "The best practices for database operations using the GoFrame framework. GoFrame offers powerful and flexible database components, supporting database operations in various complex business projects. This documentation includes multiple best practice examples for developers to reference and learn, helping you achieve database operations more efficiently." +--- + +Database operations are often the most complex part of business projects. The `GoFrame` database components actually provide very powerful and flexible ways to operate. Here, we provide some best practice examples for everyone to reference and learn from. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..7a5279c6a73 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/core/gdb-practice-using-pointer-and-do-for-update-api' +title: 'Utilizing Pointer Properties and Do Objects for Flexible Modification Interfaces' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, do objects, pointer, API, user information modification, database update, request parameters, business logic, automatic filtering] +description: "Utilizing pointer and do objects in the GoFrame framework to implement flexible modification interface APIs. By using attributes with pointer types and do objects, developers can easily perform user information modification operations, including updates to fields like password, nickname, and status, effectively simplifying the complexity of database updates." +--- + +It is well known that the framework's built-in development tools can generate `do` object code, primarily used for operations such as query, modification, and writing that automatically filter `nil` operation fields. + +Today, I will teach you a new way to quickly implement flexible and convenient modification operations `API` implementation using pointers in combination with `do` objects. + +## Data Structure + +Here is the data structure of the user table we are using: + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'User ID', + `passport` varchar(32) NOT NULL COMMENT 'Account', + `password` varchar(32) NOT NULL COMMENT 'Password', + `nickname` varchar(32) NOT NULL COMMENT 'Nickname', + `status` varchar(32) NOT NULL COMMENT 'Status', + `brief` varchar(512) NOT NULL COMMENT 'Remarks', + `create_at` datetime DEFAULT NULL COMMENT 'Creation Time', + `update_at` datetime DEFAULT NULL COMMENT 'Modification Time', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_passport` (`passport`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +For the user status, we use a separate type definition to implement enumeration values: + +```go +type Status string + +const ( + StatusEnabled Status = "enabled" + StatusDisabled Status = "disabled" +) +``` + +Using the `gf gen dao` command, the automatically generated `do` objects are as follows: + +```go +type User struct { + g.Meta `table:"user" orm:"do:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + Status interface{} + Brief interface{} + CreatedAt interface{} + UpdatedAt interface{} +} +``` + +## Request API Definition + +Let's implement an API interface for modifying user information, which is a maintenance management interface that can modify user information through the user account name. The definition of this `API` is as follows: + +```go +type UpdateReq struct { + g.Meta `path:"/user/{Id}" method:"post" summary:"Modify user information"` + Passport string `v:"required" dc:"User account"` + Password *string `dc:"Modify user password"` + Nickname *string `dc:"Modify user nickname"` + Status *Status `dc:"Modify user status"` + Brief *string `dc:"Modify user description"` +} +``` + +Here, the user's modifiable information includes password, nickname, status, and description, which may be modified individually or in combination. Pointer-type attribute parameters are used here to implement: execute modification when the parameter is passed, do not modify when it is not passed. + +## Business Logic Implementation + +For simplicity, we directly pass pointer parameters to the `do` object within the controller. We know that when the parameter is not passed by the caller, the parameter is `nil`. Therefore, when the field is passed to the `do` object, it is still `nil`, and during the database update operation, the `nil` field in the `do` object will be automatically filtered out. + +```go +func (c *Controller) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Password: req.Password, + Nickname: req.Nickname, + Status: req.Status, + Brief: req.Brief, + }).Where(do.User{ + Passport: req.Passport, + }).Update() + return +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" new file mode 100644 index 00000000000..8402d524f36 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/core/gdb-practice-using-json-for-complicated-field' +title: 'Try to use JSON for complex types for storage, facilitating automatic conversion to objects upon scanning, avoiding custom parsing' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, JSON Storage, Data Automatic Conversion, Complex Types, Database Design, ORM Component, Go Language, Product Sale Specification, Data Query, Data Structure] +description: "The advantages of storing complex type data using JSON format in database design, primarily achieving automatic conversion through the GoFrame framework, thereby simplifying code. Taking product sale specifications as an example, the creation, deletion, updating, and querying of the database is realized by defining and using Go structs, avoiding the complexity of custom parsing. Additionally, it details how to write and query data in a Go application, ensuring an efficient data handling process." +--- + +Here's an example. Suppose we need to implement a product sale specification list that includes selectable shard numbers, shard capacity, and replica numbers, as shown in the image below (non-production code, for example learning only): + +![](/markdown/9876558f2195bcdad4d03060e9a15161.png) + +Our table design is as follows: + +```sql +CREATE TABLE `sell_spec` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'Primary Key', + `product` varchar(45) NOT NULL COMMENT 'Product Name', + `resources` json NOT NULL COMMENT 'Resource specifications (cpu:memory), for example: ["0:0.25", "0:1", "1:2"]', + `disk_min` int(10) DEFAULT NULL COMMENT 'Minimum disk capacity', + `disk_max` int(10) DEFAULT NULL COMMENT 'Maximum disk capacity', + `disk_step` int(10) DEFAULT NULL COMMENT 'Disk increment size', + `shards` json NOT NULL COMMENT 'Shard specifications, for example: [1,3,5,8,12,16,24,32,40,48,64,80,96,128]', + `replicas` json NOT NULL COMMENT 'Replica specifications, for example: [1,2,3,4,5,6,7,8,9,12]', + `created_at` datetime DEFAULT NULL COMMENT 'Creation Time', + `updated_at` datetime DEFAULT NULL COMMENT 'Update Time', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='Sale Specification Configuration'; +``` + +We define `resources, shards, replicas` in `json` format to store custom lists of specifications for resources, shards, and replicas (non-sequentially). The `go struct` definition is as follows: + +```go +// SellSpec is a data structure automatically generated by the GoFrame tool and maintained by the tool. +type SellSpec struct { + Id uint `description:"Primary Key"` + Product string `description:"Product Name"` + Resources string `description:"Resource specifications (cpu:memory), for example: [\"0:0.25\", \"0:1\", \"1:2\"]"` + DiskMin int `description:"Minimum disk capacity"` + DiskMax int `description:"Maximum disk capacity"` + DiskStep int `description:"Disk increment size"` + Shards string `description:"Shard specifications, for example: [1,3,5,8,12,16,24,32,40,48,64,80,96,128]"` + Replicas string `description:"Replica specifications, for example: [1,2,3,4,5,6,7,8,9,12]"` + CreatedAt *gtime.Time `description:"Creation Time"` + UpdatedAt *gtime.Time `description:"Update Time"` +} + +// SellSpecItem is a custom data structure extending the entity, +// some fields such as Resources/Shards/Replicas are overridden to array types for automatic type conversion when operating with ORM. +type SellSpecItem struct { + entity.SellSpec + Resources []string `dc:"Resource specifications"` + Shards []int `dc:"Shard specifications"` + Replicas []int `dc:"Replica specifications"` +} +``` + +In the program, we can write and query data records as follows. + +Data writing: + +```go +_, err = dao.SellSpec.Ctx(ctx).Data(v1.SellSpecItem{ + SellSpec: entity.SellSpec{ + Product: "redis", + DiskMin: 50, + DiskMax: 1000, + DiskStep: 10, + }, + Resources: []string{"1:2", "2:4", "4:8"}, + Shards: []int{1, 3, 5, 8, 12, 16, 24, 32, 40, 48, 64, 80, 96, 128}, + Replicas: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 12}, +}).Insert() +``` + +Data querying, the `ORM` component will automatically convert records in the data table to array type attributes corresponding to the `go struct`: + +```go +var items []v1.SellSpecItem +err = dao.SellSpec.Ctx(ctx).Scan(&items) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" new file mode 100644 index 00000000000..844449b7b2c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gdb-practice-avoid-object-initialization-and-sql-errnorows-error' +title: 'Avoid Object Initialization and sql.ErrNoRows Judgment in Queries' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, SQL Queries, Object Initialization, sql.ErrNoRows, Error Handling, Pointer Judgment, ORM Result Handling, Object Memory, Code Complexity] +description: "When using the GoFrame framework for SQL queries, avoid object initialization and sql.ErrNoRows error judgment issues. By not initializing query result objects and using nil pointer judgment, unify the logic for handling empty query results across the project, thus reducing the complexity of error handling in the code." +--- + +## Avoid Object Initialization and `sql.ErrNoRows` Judgment in Queries + +When executing SQL queries, avoid initializing query results in advance to prevent the influence of default values in the struct objects and unnecessary object memory creation. By judging a return object pointer as `nil`, avoid using `sql.ErrNoRows`, thereby reducing the code's complexity in handling `error` and unifying the project’s logic for handling empty query results. + +A counterexample: + +```go +func (s *sTask) GetOne(ctx context.Context, id uint64) (out *entity.ResourceTask, err error) { + out = new(model.TaskDetail) + err = dao.ResourceTask.Ctx(ctx).WherePri(id).Scan(out) + if err != nil { + if err == sql.ErrNoRows { + err = gerror.Newf(`record not found for "%d"`, id) + } + return + } + return +} +``` + +In this example, the returned `out` object actually has default values due to object initialization (regardless of whether the SQL query retrieves data), and the judgment of `sql.ErrNoRows` adds complexity to `error` handling logic in the code. + +Recommended improvement: + +```go +func (s *sTask) GetOne(ctx context.Context, id uint64) (out *entity.ResourceTask, err error) { + err = dao.ResourceTask.Ctx(ctx).WherePri(id).Scan(&out) + if err != nil { + return + } + if out == nil { + err = gerror.Newf(`record not found for "%d"`, id) + } + return +} +``` +:::warning +Note the use of `&out` in the code. +::: +For more information, please refer to: [ORM Result - Empty Check](../ORM结果处理/ORM结果处理-为空判断.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" new file mode 100644 index 00000000000..5f7caedbafa --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-model-generating' +title: 'ORM - Model Generation' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame framework,ORM,model generation,data table,gf toolchain,gen dao,development tools,auto generation,database] +description: "GoFrame framework provides a simple ORM data table model auto generation feature, implemented through the gf gen dao/model command. It is suitable for developers to quickly generate database models. For specific usage methods, please refer to the relevant development tools section to optimize development efficiency." +--- + +## Model Auto Generation + +The `GoFrame` framework supports highly convenient data table model generation features, implemented through the `gf gen dao/model` toolchain command. Please refer to the [CLI Tool](../../开发工具/开发工具.md) section for details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" new file mode 100644 index 00000000000..bebfb3e7f94 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/core/gdb-result-empty-check' +title: 'ORM Result - Empty Check' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Result Processing, Empty Check, Data Set, Data Record, Data Field Value, Struct Object, Struct Array] +description: "Using the GoFrame framework for empty checks in ORM result processing. This includes handling data sets, multiple data records, data field values, as well as result processing methods for Struct objects and Struct arrays. By using methods like IsEmpty and IsNil, you can easily determine if the query result is empty." +--- + + +Using `GoFrame ORM` for checking if the returned result is empty is very simple. In most scenarios, you can directly check if the returned data is `nil` or has a length of `0`, or use the `IsEmpty/IsNil` methods. + +## I. Data Sets (Multiple) + +```go +r, err := g.Model("order").Where("status", 1).All() +if err != nil { + return err +} +if len(r) == 0 { + // Result is empty +} +``` + +You can also use the `IsEmpty` method: + +```go +r, err := g.Model("order").Where("status", 1).All() +if err != nil { + return err +} +if r.IsEmpty() { + // Result is empty +} +``` + +## II. Data Records (Single) + +```go +r, err := g.Model("order").Where("status", 1).One() +if err != nil { + return err +} +if len(r) == 0 { + // Result is empty +} +``` + +You can also use the `IsEmpty` method: + +```go +r, err := g.Model("order").Where("status", 1).One() +if err != nil { + return err +} +if r.IsEmpty() { + // Result is empty +} +``` + +## III. Data Field Value + +The return is a "generic" variable, so you can only use `IsEmpty` to determine if it is empty. + +```go +r, err := g.Model("order").Where("status", 1).Value() +if err != nil { + return err +} +if r.IsEmpty() { + // Result is empty +} +``` + +## IV. Field Value Array + +The returned field value array is of type `[]gdb.Value`, so you can directly check if the length is `0`. + +```go +// Array/FindArray +r, err := g.Model("order").Fields("id").Where("status", 1).Array() +if err != nil { + return err +} +if len(r) == 0 { + // Result is empty +} +``` + +## V. `Struct` Object (🔥Note🔥) + +For `Struct` conversion object, there is a slight difference. Let's look at an example. + +When the passed object is **a null pointer**, if data is found in the query, this object will be **automatically created** internally. If no data is found, the null pointer remains a null pointer and no internal handling is done. + +```go +var user *User +err := g.Model("order").Where("status", 1).Scan(&user) +if err != nil { + return err +} +if user == nil { + // Result is empty +} +``` + +When the passed object is **already an initialized object**, if data is found, it will be assigned internally to this object. **If no data is found, you cannot use `nil` to check for an empty result**. Therefore, `ORM` will return an `sql.ErrNoRows` error, indicating to the developer that no data has been found and no assignments were made, allowing further empty result checks. + +```go +var user = new(User) +err := g.Model("order").Where("status", 1).Scan(&user) +if err != nil && err != sql.ErrNoRows { + return err +} +if err == sql.ErrNoRows { + // Result is empty +} +``` +:::tip +Therefore, we recommend that developers do not pass an already initialized object to `ORM`, but rather pass a pointer to a pointer of the object (`**struct` type). Internally, `ORM` will smartly perform automatic initialization based on the query result. +::: +## VI. `Struct` Array + +When the passed object array is initially an empty array (length `0`), if data is found, it will be automatically assigned internally to the array. If no data is found, the empty array remains an empty array, and no internal handling is done. + +```go +var users []*User +err := g.Model("order").Where("status", 1).Scan(&users) +if err != nil { + return err +} +if len(users) == 0 { + // Result is empty +} +``` + +When the passed object array is not an empty array, if data is found, it will be overridden internally onto the array from index `0`. If no data is found, you cannot judge the empty result using length `0`. Therefore, `ORM` will return an `sql.ErrNoRows` error, indicating that no data has been found and no assignments were made, allowing further empty result checks. + +```go +var users = make([]*User, 100) +err := g.Model("order").Where("status", 1).Scan(&users) +if err != nil { + return err +} +if err == sql.ErrNoRows { + // Result is empty +} +``` +:::warning +Due to `Golang` reflection features employed in `struct` conversion, there is a certain performance overhead. If you are dealing with conversions of large numbers of query result data into `struct` array objects and need to improve conversion performance, please refer to the custom implementation of the corresponding `struct` `UnmarshalValue` method: +[Type Conversion - Interface](../../类型转换/类型转换-UnmarshalValue.md) +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" new file mode 100644 index 00000000000..0860d8b023b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" @@ -0,0 +1,100 @@ +--- +slug: '/docs/core/gdb-result-empty-array' +title: 'ORM Result - Empty Array' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, ORM, Go Language, Database Operations, JSON Encoding, Frontend Development, Data Processing, Backend Development, Empty Array, GoFrame Framework] +description: "Handling ORM query results in the GoFrame framework by initializing an empty array to avoid returning a null value when no data is queried, thereby enhancing user-friendly interaction with the frontend. This improvement ensures the predictability and stability of return formats when data needs to be displayed on web pages." +--- + +## Pain Point Description + +As described in the previous chapters, if a given array is uninitialized (with a value of `nil`), the `ORM` will not automatically initialize that array when no data is queried based on the given conditions. Thus, if this uninitialized array result is encoded via `JSON`, it will be converted to a `null` value. + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + type User struct { + Id uint64 // Primary Key + Passport string // Account + Password string // Password + NickName string // Nickname + CreatedAt *gtime.Time // Creation Time + UpdatedAt *gtime.Time // Update Time + } + type Response struct { + Users []User + } + var res = &Response{} + err := g.Model("user").WhereGT("id", 10).Scan(&res.Users) + fmt.Println(err) + fmt.Println(gjson.MustEncodeString(res)) +} +``` + +After execution, the terminal displays the result as: + +```html + +{"Users":null} +``` + +In most scenarios, the data queried by the `ORM` needs to be rendered on a browser page, which means the returned data needs to be processed by frontend `JS`. To make it more friendly for frontend `JS` to handle backend return data, when no data is queried on the backend, it is expected to return an empty array structure instead of a `null` attribute value. + +## Improvement Plan + +In this scenario, you can provide an initialized empty array to the `Scan` method of the `ORM`. When the `ORM` doesn't query any data, the array attribute remains an empty array, rather than `nil`, and the returned JSON encoding will not be a `null` value. + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + type User struct { + Id uint64 // Primary Key + Passport string // Account + Password string // Password + NickName string // Nickname + CreatedAt *gtime.Time // Creation Time + UpdatedAt *gtime.Time // Update Time + } + type Response struct { + Users []User + } + var res = &Response{ + Users: make([]User, 0), + } + err := g.Model("user").WhereGT("id", 10).Scan(&res.Users) + fmt.Println(err) + fmt.Println(gjson.MustEncodeString(res)) +} +``` + +After execution, the terminal displays the result as: + +```html + +{"Users":[]} +``` + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..f2ba86fc677 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/core/gdb-result-types' +title: 'ORM Result - Types' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Data Structure, ORM, Result Types, Record, Result, gdb, Database, Go Language, Data Handling] +description: "Several result types for ORM result processing in the GoFrame framework, including the data structure definitions for Value, Record, and Result. Through examples, it details how to convert database table records to struct objects and the application of Result/Record types in specific field retrieval scenarios." +--- + +## 1. Data Structures + +The data structures of the query results are as follows: + +```go +type Value = *gvar.Var // Returns table record value +type Record map[string]Value // Returns table record key-value pairs +type Result []Record // Returns a list of table records +``` + +1. `Value/Record/Result` are result data types for ORM operations. +2. `Result` represents **a list of table records**, `Record` represents **a single table record**, and `Value` represents **a single key-value pair** in a record. +3. `Value` is an alias type for `*gvar.Var`, a runtime generic type to support different field types in database tables, facilitating subsequent data type conversion. + +For example: + +![](/markdown/c4af671f6f43d161fc776afdffaaa047.png) + +![](/markdown/73f857180655a5dc19eb8deb79d3a774.png) + +![](/markdown/d8aedba99def08d9ad5e244dd0bde66a.png) + +## 2. `Record` Data Record + +API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +`gdb` provides a high degree of flexibility and simplicity for table record operations, supporting not only access/manipulation of table records in the form of `map` but also converting them to `struct` for processing. Here, we will demonstrate this feature with a simple example. + +First, our user table structure is like this (a sample table for simple design): + +```sql +CREATE TABLE `user` ( + `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(30) NOT NULL DEFAULT '' COMMENT 'Nickname', + `site` varchar(255) NOT NULL DEFAULT '' COMMENT 'Homepage', + PRIMARY KEY (`uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +Next, our table data is as follows: + +``` +uid name site +1 john https://goframe.org +``` + +Finally, our example program is as follows: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int + Name string +} + +func main() { + var ( + user *User + ctx = gctx.New() + ) + err := g.DB().Model("user").Where("uid", 1).Scan(&user) + if err != nil { + g.Log().Header(false).Fatal(ctx, err) + } + if user != nil { + g.Log().Header(false).Print(ctx, user) + } +} +``` + +After execution, the output is: + +```json +{"Uid":1,"Name":"john"} +``` + +Here, we define a custom `struct` containing only `Uid` and `Name` properties. As you can see, its properties do not match the fields of the data table, which is one of the flexible features of `ORM`: supporting specified attribute retrieval. + +The `gdb.Model.Scan` method can convert the queried records into `struct` objects or arrays of `struct` objects. Since the parameter passed here is `&user` i.e., `**User` type, it will be converted into a **struct object**. If a `[]*User` type parameter is passed, it will be converted into a **struct array**. Please see subsequent examples for more. For a detailed introduction to the method, please refer to the chaining operations section. + +**Attribute Field Mapping Rules:** + +Note that the key names in `map` are `uid,name,site`, while the attributes in `struct` are `Uid,Name`. How are they mapped? There are a few simple rules: + +1. The matching attribute in `struct` must be `public` (starting with a capital letter). +2. Key names in the results will automatically match `struct` attributes in a **case-insensitive**, and **ignoring `-/_/space` symbols** manner. +3. If the match is successful, the key value is assigned to the attribute. If it cannot be matched, the key value is ignored. + +Here are a few matching examples: + +```plaintext +Key Name Struct Attribute Match +name Name match +Email Email match +nickname NickName match +NICKNAME NickName match +Nick-Name NickName match +nick_name NickName match +nick_name Nick_Name match +NickName Nick_Name match +Nick-Name Nick_Name match +``` +:::tip +The conversion from a database result set to `struct` relies on the `gconv.Struct` method. Therefore, if you want to achieve **custom attribute conversion**, and for more detailed mapping rules, please refer to the section on [Type Conversion - Struct](../../类型转换/类型转换-Struct转换.md). +::: +## 3. `Result` Data Collection + +The `Result/Record` data types, based on the requirement to manipulate the result set, often need to use **specific fields** in the records as keys for data retrieval. They include multiple methods for converting to `Map/List`, as well as common conversions to `JSON/XML` data structures. + +API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +Due to the simplicity of the methods, there are no examples here. However, pay attention to two frequently used methods: `Record.Map` and `Result.List`, which are used to convert the `ORM` query result information into data types suitable for display. Since the field values of the result set are natively of `[]byte` type, although a new `Value` type has been encapsulated and provides dozens of common type conversion methods (for more, please read the section on [Generic](../../../组件列表/数据结构/泛型类型-gvar/泛型类型-gvar.md)), most often we need to directly return the result `Result` or `Record` as `json` or `xml` data structures, requiring conversion. + +Usage example: + +```go +package main + +import ( + "database/sql" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int + Name string + Site string +} + +func main() { + var ( + user []*User + ctx = gctx.New() + ) + err := g.DB().Model("user").Where("uid", 1).Scan(&user) + if err != nil && err != sql.ErrNoRows { + g.Log().Header(false).Fatal(ctx, err) + } + if user != nil { + g.Log().Header(false).Print(ctx, user) + } +} +``` + +After execution, the output is: + +```json +[{"Uid":1,"Name":"john","Site":"https://goframe.org"}] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..5acc315407c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-result' +title: 'ORM - Result Handling' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Result Processing, Database Handling, gdb, Data Retrieval, Result Conversion, SQL Query, Data Manipulation, Web Development] +description: "Using the functionality provided by the GoFrame framework for ORM result processing, this document details the process of retrieving and converting database results, helping developers perform data manipulation and management more efficiently, thereby enhancing the development efficiency of web applications." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..ee6954e2de8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gdb-chaining-handler' +title: 'ORM Model - Handler' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Chaining Operation, Handler Feature, Query Example, Pagination Example, Code Reuse, Database Operations, Go Language, gdb Library] +description: "Using ORM chaining operations in the GoFrame framework to implement the Handler feature, examples demonstrate how to reuse common query logic and pagination operations, simplifying code and improving development efficiency. The Handler feature allows developers to define common logic and apply it to database models, achieving a cleaner and more maintainable code structure." +--- + +The `Handler` feature allows you to easily reuse common logic. + +## Example 1: Query + +```go +func AmountGreaterThan1000(m *gdb.Model) *gdb.Model { + return m.WhereGT("amount", 1000) +} + +func PaidWithCreditCard(m *gdb.Model) *gdb.Model { + return m.Where("pay_mode_sign", "credit_card") +} + +func PaidWithCod(m *gdb.Model) *gdb.Model { + return m.Where("pay_mode_sign", "cod") +} + +func OrderStatus(statuses []string) func(m *gdb.Model) *gdb.Model { + return func(m *gdb.Model) *gdb.Model { + return m.Where("status", statuses) + } +} + +var ( + m = g.Model("product_order") +) + +m.Handler(AmountGreaterThan1000, PaidWithCreditCard).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `pay_mode_sign`='credit_card' +// Find all credit card orders with an amount greater than 1000 + +m.Handler(AmountGreaterThan1000, PaidWithCod).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `pay_mode_sign`='cod' +// Find all COD orders with an amount greater than 1000 + +m.Handler(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `status` IN('paid','shipped') +// Find all orders with an amount greater than 1000 that are paid or shipped +``` + +## Example 2: Pagination + +```go +func Paginate(r *ghttp.Request) func(m *gdb.Model) *gdb.Model { + return func(m *gdb.Model) *gdb.Model { + type Pagination struct { + Page int + Size int + } + var pagination Pagination + _ = r.Parse(&pagination) + switch { + case pagination.Size > 100: + pagination.Size = 100 + + case pagination.Size <= 0: + pagination.Size = 10 + } + return m.Page(pagination.Page, pagination.Size) + } +} + +m.Handler(Paginate(r)).Scan(&users) +m.Handler(Paginate(r)).Scan(&articles) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..010bc8cf22a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" @@ -0,0 +1,67 @@ +--- +slug: '/docs/core/gdb-chaining-hook' +title: 'ORM Model - Hook' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame framework, ORM chaining operation, Hook feature, CRUD hooks, Model, database query, gdb, Go language, programming examples, code optimization] +description: "Using the Hook feature in the GoFrame framework to bind CRUD hooks to Model objects, thereby enhancing and optimizing database operations. The article provides a detailed introduction to relevant definitions, Hook registration methods, and usage examples, demonstrating query operations through hook functions." +--- + +The `Hook` feature allows us to bind `CRUD` hooks to the `Model` of the feature. + +## Relevant Definitions + +Relevant `Hook` functions: + +```go +type ( + HookFuncSelect func(ctx context.Context, in *HookSelectInput) (result Result, err error) + HookFuncInsert func(ctx context.Context, in *HookInsertInput) (result sql.Result, err error) + HookFuncUpdate func(ctx context.Context, in *HookUpdateInput) (result sql.Result, err error) + HookFuncDelete func(ctx context.Context, in *HookDeleteInput) (result sql.Result, err error) +) + +// HookHandler manages all supported hook functions for Model. +type HookHandler struct { + Select HookFuncSelect + Insert HookFuncInsert + Update HookFuncUpdate + Delete HookFuncDelete +} +``` + +`Hook` registration method: + +```go +// Hook sets the hook functions for current model. +func (m *Model) Hook(hook HookHandler) *Model +``` + +## Usage Example + +When querying the `birth_day` field, also calculate the current user's age: + +```go +// Hook function definition. +var hook = gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + for i, record := range result { + if !record["birth_day"].IsEmpty() { + age := gtime.Now().Sub(record["birth_day"].GTime()).Hours() / 24 / 365 + record["age"] = gvar.New(age) + } + result[i] = record + } + return + }, +} +// It registers the hook function, creates and returns a new `model`. +model := g.Model("user").Hook(hook) + +// The hook function takes effect on each ORM operation when using the `model`. +all, err := model.Where("status", "online").OrderAsc(`id`).All() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" new file mode 100644 index 00000000000..6a6086a42da --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gdb-chaining-master-slave' +title: 'ORM Model - Master/Slave' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gdb, Master-Slave Switching, Read-Write Separation, Database Configuration, ORM Chaining Operations, SQL Requests, Node Switching, Database Load Balancing] +description: "Implement master-slave configuration and read-write separation at the application layer using gdb in the GoFrame framework. With simple configuration, gdb can automatically switch between master and slave, significantly improving database performance and availability. This article also provides examples of using Master and Slave methods for custom node operations, helping developers better address issues related to master-slave synchronization delay and ensure data timeliness and accuracy." +--- + +From the previous introduction, we know that `gdb` supports master-slave configuration and read-write separation at the application layer, and all features can be achieved through simple configuration. `gdb` will automatically switch between master and slave for SQL requests. The following is a simple master-slave configuration with one master and one slave: + +```yaml +database: + default: + - type: "mysql" + link: "root:12345678@tcp(192.168.1.1:3306)/test" + role: "master" + - type: "mysql" + link: "root:12345678@tcp(192.168.1.2:3306)/test" + role: "slave" +``` + +In most scenarios, our write requests are directed to the `Master` node, while read requests are directed to the `Slave` nodes. This approach helps distribute the load on the database and improves its availability. However, in some cases, we want read operations to be executed on the `Master` node, especially in scenarios with high immediacy requirements (as there is some latency in data synchronization between master and slave nodes). + +Developers can customize which node a chaining operation is executed on using the `Master` and `Slave` methods. + +Let's look at a simple example. We have an order system with high daily traffic, often causing a `1-500ms` delay in database master-slave synchronization. The business requirement is to display the order list page immediately after creating an order. If the order list page reads data from the slave node by default, users may not see the newly created order due to the database master-slave synchronization delay. To solve this issue, we can configure the order list page to read data from the master node. + +1. When creating an order, there is no need to specify the node for the operation because write operations are executed on the master node by default. To simplify the example, we only show the key code here: + + ```go + g.Model("order").Data(g.Map{ + "uid" : 1000, + "price" : 99.99, + // ... + }).Insert() + ``` + +2. When querying on the order list page, we need to use the `Master` method to specify that the query operation should be conducted on the master node to avoid read delays. + + ```go + g.Model("order").Master().Where("uid", 1000).All() + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..dbb55d08281 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -0,0 +1,111 @@ +--- +slug: '/docs/core/gdb-chaining-transaction' +title: 'ORM Model - Transaction' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, Transaction Handling, ORM Chaining, GoFrame Framework, Transaction, TX Interface, Database Object, Transaction Object, Chaining Operations, Commit/Rollback] +description: "Using transaction handling in the GoFrame framework for ORM chaining methods. Through the Transaction and TX interfaces, transactional operations on the database can be achieved to ensure data consistency and reliability. Provides a detailed explanation of using the TX interface to create Model objects and the commit and rollback mechanisms in transaction handling." +--- + +`Model` objects can also be created through the `TX` transaction interface. The functionality of a `Model` object created through a transaction object is the same as that of one created through a `DB` database object, except that the former's operations are based on transactions. Once the transaction is committed or rolled back, the corresponding `Model` object cannot be used further; otherwise, an error will be returned. Because the `TX` interface cannot be reused, a transaction object corresponds to a single transaction process and ends after `Commit`/`Rollback`. + +This chapter provides a simple introduction to transaction handling methods involved in chaining operations. For more detailed information, please refer to the [ORM - Transaction](../ORM事务处理/ORM事务处理.md) chapter. + +## Example 1: Using `Transaction` + +To facilitate transaction operations, `gdb` provides a closure operation for transactions, implemented through the `Transaction` method, which is defined as follows: + +```go +func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) +``` + +If the `error` returned by the given closure method is `nil`, the `Commit` operation is automatically executed after the closure finishes execution; otherwise, it automatically performs a `Rollback`. +:::tip +If a `panic` occurs within the closure operation, the transaction will also be rolled back. +::: +```go +func Register() error { + return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + var ( + result sql.Result + err error + ) + // Insert basic user data + result, err = tx.Table("user").Insert(g.Map{ + "name": "john", + "score": 100, + //... + }) + if err != nil { + return err + } + // Insert user detail data, utilizing the user uid from the previous insertion + result, err = tx.Table("user_detail").Insert(g.Map{ + "uid": result.LastInsertId(), + "phone": "18010576258", + //... + }) + return err + }) +} +``` + +## Example 2: Using `TX` Chaining Operations + +We can also switch the bound transaction object within chaining operations using the `TX` method. Multiple chaining operations can bind to the same transaction object, executing the corresponding chaining operations within that transaction object. + +```go +func Register() error { + var ( + uid int64 + err error + ) + tx, err := g.DB().Begin() + if err != nil { + return err + } + // Validate the return value upon method exit, + // If successful, execute tx.Commit() to commit, + // Otherwise, execute tx.Rollback() to roll back. + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + // Insert basic user data + uid, err = AddUserInfo(tx, g.Map{ + "name": "john", + "score": 100, + //... + }) + if err != nil { + return err + } + // Insert user detail data, using the user uid from the previous insertion + err = AddUserDetail(tx, g.Map{ + "uid": uid, + "phone": "18010576259", + //... + }) + return err +} + +func AddUserInfo(tx gdb.TX, data g.Map) (int64, error) { + result, err := g.Model("user").TX(tx).Data(data).Insert() + if err != nil { + return 0, err + } + uid, err := result.LastInsertId() + if err != nil { + return 0, err + } + return uid, nil +} + +func AddUserDetail(tx gdb.TX, data g.Map) error { + _, err := g.Model("user_detail").TX(tx).Data(data).Insert() + return err +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" new file mode 100644 index 00000000000..12c16a2f40a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" @@ -0,0 +1,186 @@ +--- +slug: '/docs/core/gdb-chaining-insert-save' +title: 'ORM Model - Insert/Save' +sidebar_position: 1 +hide_title: true +keywords: [ORM Chained Operations, Data Insertion, GoFrame, Insert, Replace, Save, Database, Batch Operations, SQL, RawSQL] +description: "Insert, Replace, Save methods usage in GoFrame's ORM chained operations, supporting automatic single or batch data insertion across various database environments. Detailed examples demonstrate how to use these methods with the Data method for data operations." +--- + +## Common Methods + +### `Insert/Replace/Save` + +These chained operation methods are used for data insertion and support automatic single or batch data insertion, with differences as follows: + +1. `Insert` + + Uses the `INSERT INTO` statement for database insertion. If the inserted data contains a primary key or unique index, it returns failure; otherwise, a new record is inserted. + +2. `Replace` + + Uses the `REPLACE INTO` statement for database insertion. If the inserted data contains a primary key or unique index, it deletes the existing record, ensuring that a new record is inserted. + +3. `Save` + + Uses the `INSERT INTO` statement for database insertion. If the inserted data contains a primary key or unique index, it updates the existing data; otherwise, it inserts a new record. For certain databases, such as `PgSQL, SQL server, Oracle`, you can use the `OnConflict` method to specify conflict keys. + + ```go + db.Model(table).Data(g.Map{ + "id": 1, + "passport": "p1", + "password": "pw1", + }).OnConflict("id").Save() + ``` + +> Some database types do not support `Replace/Save` methods. Refer to the [ORM - Model 🔥](../ORM链式操作/ORM链式操作.md) section for details. + +These methods need to be used in conjunction with the `Data` method, which is used to pass data parameters for data insertion/updating and other write operations. + +### `InsertIgnore` + +This method is used to ignore errors and continue with insertion if the inserted data contains a primary key or unique index. The method is defined as follows: + +```go +func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) +``` + +### `InsertAndGetId` + +This method is used to insert data while directly returning the auto-increment field's `ID`. The method is defined as follows: + +```go +func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) +``` + +### `OnDuplicate/OnDuplicateEx` + +`OnDuplicate/OnDuplicateEx` methods need to be used together with the `Save` method to specify the fields to be updated/not updated for the `Save` operation. Parameters can be strings, string arrays, or `Map`. For example: + +```go +OnDuplicate("nickname, age") +OnDuplicate("nickname", "age") +OnDuplicate(g.Map{ + "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"), +}) +OnDuplicate(g.Map{ + "nickname": "passport", +}) +``` + +`OnDuplicateEx` is used to exclude specified fields from being updated, with excluded fields already in the data set being inserted. + +## Usage Examples + +### Example 1: Basic Usage + +Data insertion/save methods need to be used with the `Data` method. The parameter type can be `Map/Struct/Slice`: + +```go +// INSERT INTO `user`(`name`) VALUES('john') +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`uid`,`name`) VALUES(10000,'john') +g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`uid`,`name`) VALUES(10000,'john') +g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`uid`,`name`) VALUES(10001,'john') ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`) +g.Model("user").Data(g.Map{"uid": 10001, "name": "john"}).Save() +``` + +You can also directly pass data parameters to the insertion/save methods without using the `Data` method: + +```go +g.Model("user").Insert(g.Map{"name": "john"}) +g.Model("user").Replace(g.Map{"uid": 10000, "name": "john"}) +g.Model("user").Save(g.Map{"uid": 10001, "name": "john"}) +``` + +Data parameters are often `struct` types, for example, when the table fields are `uid/name/site`: + +```go +type User struct { + Uid int `orm:"uid"` + Name string `orm:"name"` + Site string `orm:"site"` +} +user := &User{ + Uid: 1, + Name: "john", + Site: "https://goframe.org", +} +// INSERT INTO `user`(`uid`,`name`,`site`) VALUES(1,'john','https://goframe.org') +g.Model("user").Data(user).Insert() +``` + +### Example 2: Batch Data Insertion + +Batch insertion is achieved by passing a `Slice` array type parameter to the `Data` method. Array elements need to be of `Map` or `Struct` type to automatically derive field information and generate batch operation `SQL`. + +```go +// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2'),('john_3') +g.Model("user").Data(g.List{ + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, +}).Insert() +``` + +You can specify the number of records to be written in batches using the `Batch` method (default is `10`). The following example will be split into two insertion requests: + +```go +// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2') +// INSERT INTO `user`(`name`) VALUES('john_3') +g.Model("user").Data(g.List{ + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, +}).Batch(2).Insert() +``` + +### Example 3: Batch Data Save + +The principle of batch save operations is the same as single save operations. When the inserted data contains a primary key or unique index, it updates the existing record; otherwise, a new record is inserted. + +> `oracle, dm, mssql` do not support batch savings. + +```go +// INSERT INTO `user`(`uid`,`name`) VALUES(10000,'john_1'),(10001,'john_2'),(10002,'john_3') +// ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`) +g.Model("user").Data(g.List{ + {"uid":10000, "name": "john_1"}, + {"uid":10001, "name": "john_2"}, + {"uid":10002, "name": "john_3"}, +}).Save() +``` + +## `RawSQL` Statement Embedding + +`gdb.Raw` is a string type whose parameters will be directly embedded as `SQL` fragments into the final `SQL` statement submitted to the underlying database, not automatically converted to string parameter types, nor treated as preprocessed parameters. For more details, refer to the section: [ORM Senior - RawSQL](../ORM高级特性/ORM高级特性-RawSQL.md). For example: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES('id+2','john','123456','now()') +g.Model("user").Data(g.Map{ + "id": "id+2", + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": "now()", +}).Insert() +// Execution Error: Error Code: 1136. Column count doesn't match value count at row 1 +``` + +Revised using `gdb.Raw`: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES(id+2,'john','123456',now()) +g.Model("user").Data(g.Map{ + "id": gdb.Raw("id+2"), + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": gdb.Raw("now()"), +}).Insert() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" new file mode 100644 index 00000000000..01d0fb1873f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gdb-chaining-fields-retrieving' +title: 'ORM Model - Fields Retrieval' +sidebar_position: 7 +hide_title: true +keywords: [ORM, chaining operations, field retrieval, FieldsStr, FieldsExStr, GoFrame, GoFrame framework, database fields, field prefix, field exclusion] +description: "Techniques for retrieving database table fields using ORM chaining operations in the GoFrame framework, including using FieldsStr and FieldsExStr methods to retrieve fields of a specified table and uses of field exclusion, supporting custom field prefixes to enhance development efficiency and code readability." +--- + +## `FieldsStr/FieldsExStr` Field Retrieval + +1. `FieldsStr` is used to retrieve fields of a specified table, with an optional field prefix. The fields are returned as a string, concatenated using a "," symbol. +2. `FieldsExStr` is used to retrieve fields from a specified table, excluding certain fields, with an optional field prefix. The fields are returned as a string, concatenated using a "," symbol. + +### `FieldsStr` Example + +1. Suppose the `user` table has 4 fields: `uid`, `nickname`, `passport`, `password`. +2. Retrieve fields: + ```go + // uid,nickname,passport,password + g.Model("user").FieldsStr() + ``` + +3. Retrieve fields with a specified prefix: + ```go + // gf_uid,gf_nickname,gf_passport,gf_password + g.Model("user").FieldsStr("gf_") + ``` + +### `FieldsExStr` Example + +1. Suppose the `user` table has 4 fields: `uid`, `nickname`, `passport`, `password`. +2. Retrieve fields excluding some: + ```go + // uid,nickname + g.Model("user").FieldsExStr("passport, password") + ``` + +3. Retrieve fields excluding some with a specified prefix: + ```go + // gf_uid,gf_nickname + g.Model("user").FieldsExStr("passport, password", "gf_") + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" new file mode 100644 index 00000000000..835d39b0fb3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" @@ -0,0 +1,237 @@ +--- +slug: '/docs/core/gdb-chaining-fields-filtering' +title: 'ORM Model - Fields Filtering' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Field Filtering, Fields, FieldsEx, OmitEmpty, OmitNil, GoFrame DAO, Table] +description: "How to perform field filtering when operating databases with the GoFrame framework. It describes in detail the purposes and examples of the Fields and FieldsEx methods, and explores how the OmitEmpty and OmitNil features help filter out empty data during database writing. Additionally, it discusses the impact of null values on query conditions." +--- + +## `Fields/FieldsEx` Field Filtering + +1. `Fields` is used to specify the fields of the table to be operated on, including query fields, write fields, update fields, etc.; +2. `FieldsEx` is used for specifying exception fields and can be used for query fields, write fields, update fields, etc.; + +### `Fields` Example + +1. Suppose the `user` table has 4 fields: `uid`, `nickname`, `passport`, `password`. +2. Query field filtering + ```go + // SELECT `uid`,`nickname` FROM `user` ORDER BY `uid` asc + g.Model("user").Fields("uid, nickname").Order("uid asc").All() + ``` + +3. Write field filtering + ```go + m := g.Map{ + "uid" : 10000, + "nickname" : "John Guo", + "passport" : "john", + "password" : "123456", + } + g.Model(table).Fields("nickname,passport,password").Data(m).Insert() + // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456') + ``` +4. Supports `gdb.Raw` input + ```go + // SELECT 1 FROM `user` WHERE `id`=10 + g.Model("user").Fields(gdb.Raw("1")).Where("id", 10).Value() + ``` + +### `FieldsEx` Example + +1. Suppose the `user` table has 4 fields: `uid`, `nickname`, `passport`, `password`. +2. Query field exclusion + + ```go + // SELECT `uid`,`nickname` FROM `user` + g.Model("user").FieldsEx("passport, password").All() + ``` + +1. Write field exclusion + ```go + m := g.Map{ + "uid" : 10000, + "nickname" : "John Guo", + "passport" : "john", + "password" : "123456", + } + g.Model(table).FieldsEx("uid").Data(m).Insert() + // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456') + ``` + + +## `OmitEmpty` Empty Value Filtering + +When there are empty values like `nil`, `""`, `0` in a `map`/ `struct`, by default, `gdb` will consider them as normal input parameters, and hence, they will be updated to the data table. The `OmitEmpty` feature helps filter out these empty fields before writing data to the database. + +Related methods: + +```go +func (m *Model) OmitEmpty() *Model +func (m *Model) OmitEmptyWhere() *Model +func (m *Model) OmitEmptyData() *Model +``` + +The `OmitEmpty` method filters out empty data fields from both `Where` and `Data`, while the `OmitEmptyWhere/OmitEmptyData` methods allow specific field filtering. + +### Insert/Update Operations + +Empty values affect insert/update operation methods like `Insert`, `Replace`, `Update`, `Save`. As in the following operation (taking `map` as an example, the same applies to `struct`): + +```go +// UPDATE `user` SET `name`='john',update_time=null WHERE `id`=1 +g.Model("user").Data(g.Map{ + "name" : "john", + "update_time" : nil, +}).Where("id", 1).Update() +``` + +To handle empty values, we can use the `OmitEmpty` method to filter them out. For example, the above example can be modified as follows: + +```go +// UPDATE `user` SET `name`='john' WHERE `id`=1 +g.Model("user").OmitEmpty().Data(g.Map{ + "name" : "john", + "update_time" : nil, +}).Where("id", 1).Update() +``` + +For `struct` data parameters, we can also perform empty value filtering. An operation example: + +```go +type User struct { + Id int `orm:"id"` + Passport string `orm:"passport"` + Password string `orm:"password"` + NickName string `orm:"nickname"` + CreateTime string `orm:"create_time"` + UpdateTime string `orm:"update_time"` +} +user := User{ + Id : 1, + NickName : "john", + UpdateTime: gtime.Now().String(), +} +g.Model("user").OmitEmpty().Data(user).Insert() +// INSERT INTO `user`(`id`,`nickname`,`update_time`) VALUES(1,'john','2019-10-01 12:00:00') +``` +:::warning +Note that in batch insert/update operations, the `OmitEmpty` method will be ineffective because fields for each inserted record must be consistent. +::: +Regarding the `omitempty` tag and the `OmitEmpty` method: + +1. For empty value filtering in `struct`, people might think of the `omitempty` tag. This tag is commonly used for filtering empty values in `json` conversion and in some third-party `ORM` libraries for filtering out `struct` fields with empty values, meaning when a property is empty, it is not converted. +2. The `omitempty` tag and the `OmitEmpty` method achieve the same effect. In `ORM` operations, we do not recommend using the `omitempty` tag on `struct` to control field empty value filtering. Instead, we suggest using the `OmitEmpty` method. Once the tag is applied, it binds to the `struct`, and there is no way to control it flexibly; however, with the `OmitEmpty` method, developers can selectively filter empty values based on the business scenario, allowing for more flexible operations. + +### Data Query Operations + +Empty values also affect data query operations, mainly impacting `where` condition parameters. We can filter empty values in condition parameters using the `OmitEmpty` method. + +Examples of use: + +```go +// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1 +r, err := g.Model("user").Where(g.Map{ + "nickname" : "", + "passport" : "john", +}).OmitEmpty().One() +``` + +```go +type User struct { + Id int `orm:"id"` + Passport string `orm:"passport"` + Password string `orm:"password"` + NickName string `orm:"nickname"` + CreateTime string `orm:"create_time"` + UpdateTime string `orm:"update_time"` +} +user := User{ + Passport : "john", +} +r, err := g.Model("user").OmitEmpty().Where(user).One() +// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1 +``` + +## `OmitNil` Empty Value Filtering + +### Overview + +When there are empty values like `nil` in a `map`/ `struct`, by default, `gdb` will consider them as normal input parameters, and hence, they will be updated to the data table. The `OmitNil` feature helps filter out these empty fields before writing data to the database. The difference between `OmitNil` and `OmitEmpty` is that `OmitNil` only filters fields with `nil` values, while other empty values like `""`, `0` will not be filtered. + +Related methods: + +```go +func (m *Model) OmitNil() *Model +func (m *Model) OmitNilWhere() *Model +func (m *Model) OmitNilData() *Model +``` + +The `OmitNil` method filters out empty fields from both `Where` and `Data`, while the `OmitNilWhere/OmitNilData` methods allow specific field filtering. + +### Using `do` Objects for Field Filtering + +If you use the `GoFrame` project directory and use the `gf gen dao` or `make dao` command, the corresponding table `dao/entity/do` files will be automatically generated based on the configured database. If `do` objects are used in database operations, unassigned fields will be automatically filtered. For example: + +Generated `do` object struct definition + +```go +// User is the golang structure of table user for DAO operations like Where/Data. +type User struct { + g.Meta `orm:"table:user, do:true"` + Id interface{} // User ID + Passport interface{} // User Passport + Password interface{} // User Password + Nickname interface{} // User Nickname + CreateAt *gtime.Time // Created Time + UpdateAt *gtime.Time // Updated Time +} +``` + +Data Insertion: + +```go +dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Passport: in.Passport, + Password: in.Password, + Nickname: in.Nickname, + }).Insert() + return err +}) +``` + +Data Query: + +```go +var user *entity.User +err = dao.User.Ctx(ctx).Where(do.User{ + Passport: in.Passport, + Password: in.Password, +}).Scan(&user) +``` + +## `Filter` Field Filtering (Built-in) + +~~`gdb` can automatically synchronize the **table structure** to the program cache (cache does not expire until the program restarts/redeploys) and can filter out non-compliant table structure items from the submitted parameters. This feature can be achieved using the `Filter` method. It is commonly used in scenarios involving input `map/struct/[]map/[]string` parameters for insert/delete operations.~~ + +~~Usage example, suppose the `user` table has 4 fields: `uid`, `nickname`, `passport`, `password`:~~ + +```go +r, err := g.Model("user").Filter().Data(g.Map{ + "id" : 1, + "uid" : 1, + "passport" : "john", + "password" : "123456", +}).Insert() +// INSERT INTO user(uid,passport,password) VALUES(1, "john", "123456") +``` + +~~Here, `id` is a non-existent field, and it will be filtered out during data insertion to prevent execution errors in the constructed SQL.~~ +:::tip +~~The database is not designed to automatically filter for the `Data` method. Instead, developers need to specify filtering manually by calling the `Filter` method. This aims to kindly remind developers of possible mistakes/wrongly passed field names. Automatically filtering may cause unpredictable business logic anomalies, for example, the `Filter` method can cause the automatic filtering of essential fields due to incorrect spelling of field names, leading to incomplete data in the database.~~ +::: +:::warning +From version `GoFrame v1.15.7`, based on overall community feedback, to enhance component usability, the `filter` feature is enabled by default and no longer needs to be explicitly called, making the `Filter` method deprecated. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" new file mode 100644 index 00000000000..35250ce8083 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" @@ -0,0 +1,34 @@ +--- +slug: '/docs/core/gdb-chaining-object-parameter' +title: 'ORM Model - Object Input' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Chaining Operations, Object Input, gdb, Struct Mapping, Database ORM, Mapping Relationships, Go Language, Type Conversion] +description: "This document introduces how to use the object input feature of chaining operations in the GoFrame framework, supporting various types of data parameters, providing gdb with high flexibility. It explains in detail the mapping relationships and tag priorities when using struct objects for parameter input to achieve effective database ORM conversion." +--- + +The `Data/Where/WherePri/And/Or` methods support any `string/map/slice/struct/*struct` data type parameters, which provides `gdb` with great flexibility. When using `struct`/ `*struct` objects as input parameters, they will be automatically parsed as `map` types. Only the **public attributes** of the `struct` can be converted, and the `orm`/ `gconv`/ `json` tags are supported to define the key names after conversion, that is, the mapping relationship with the table fields. + +For example: + +```go +type User struct { + Uid int `orm:"user_id"` + Name string `orm:"user_name"` + NickName string `orm:"nick_name"` +} +// Or +type User struct { + Uid int `gconv:"user_id"` + Name string `gconv:"user_name"` + NickName string `gconv:"nick_name"` +} +// Or +type User struct { + Uid int `json:"user_id"` + Name string `json:"user_name"` + NickName string `json:"nick_name"` +} +``` + +In this, the attributes of `struct` should be public (first letter capitalized), and the `orm` tag corresponds to the field name of the data table. The tags for table field mapping relationships can use `orm`, `gconv`, or the traditional `json` tag. However, when all three tags are present, the `orm` tag takes precedence. To avoid conflicts with `JSON` encoding tags when converting `struct` objects to `JSON` data format, it is recommended to use the `orm` tag to achieve the database `ORM` mapping relationship. For more detailed conversion rules, please refer to the [Type Conversion - Map](../../类型转换/类型转换-Map转换.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" new file mode 100644 index 00000000000..c594cecb626 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" @@ -0,0 +1,59 @@ +--- +slug: '/docs/core/gdb-chaining-locks' +title: 'ORM Model - Lock' +sidebar_position: 15 +hide_title: true +keywords: [Pessimistic Lock, Optimistic Lock, GoFrame, GoFrame Framework, Chaining Operations, SQL, Shared Lock, FOR UPDATE, LOCK IN SHARE MODE, Transaction] +description: "How to implement pessimistic and optimistic locks through chaining operations in the GoFrame framework. Pessimistic locks are used to lock data during each access to prevent conflicts, commonly in high concurrency scenarios; while optimistic locks use a versioning mechanism to check data updates, suitable for scenarios with more reads and fewer writes. This document provides a detailed analysis of applicable scenarios, implementation methods, and the advantages and disadvantages of locking mechanisms to help developers optimize database performance." +--- + +`Pessimistic Lock`, as the name suggests, is very pessimistic, assuming that others will modify the data each time it is accessed, so it locks the data each time. This results in a block if someone else wants to access the data until they get the lock. Many traditional relational databases use this locking mechanism, such as row locks, table locks, read locks, and write locks, all of which lock before performing operations. + +`Optimistic Lock`, as the name suggests, is very optimistic, assuming that others will not modify the data each time it is accessed, so it does not lock. However, it checks if others have updated the data during this period when updating, which can be implemented using mechanisms like versioning. Optimistic locks are suitable for applications with more reads, which can improve throughput. + +### Using Pessimistic Locks + +Related methods: + +```go +func (m *Model) LockUpdate() *Model +func (m *Model) LockShared() *Model +``` + +The `gdb` module's chaining operations provide two methods to help you implement "pessimistic locks" in `SQL` statements. You can use the `LockShared` method in queries to carry a "shared lock" when executing statements. A shared lock prevents the selected rows from being modified until the transaction is committed: + +```go +g.Model("users").Ctx(ctx).Where("votes>?", 100).LockShared().All(); +``` + +The above query is equivalent to the following SQL statement: + +```sql +SELECT * FROM `users` WHERE `votes` > 100 LOCK IN SHARE MODE +``` + +You can also use the `LockUpdate` method. This method is used to create a `FOR UPDATE` lock, preventing selected rows from being modified or deleted by other shared locks: + +```go +g.Model("users").Ctx(ctx).Where("votes>?", 100).LockUpdate().All(); +``` + +The above query is equivalent to the following SQL statement: + +```sql +SELECT * FROM `users` WHERE `votes` > 100 FOR UPDATE +``` + +Both `FOR UPDATE` and `LOCK IN SHARE MODE` are used to ensure that the selected records cannot be updated by other transactions (locked). The difference is that `LOCK IN SHARE MODE` will not block other transactions from reading the value of the locked row, while `FOR UPDATE` will block other locking reads of the locked row (non-locking reads can still read these records; both `LOCK IN SHARE MODE` and `FOR UPDATE` are locking reads). + +This might be a bit abstract, so let's take a counter example: read a value in one statement, then update it in another. Using `LOCK IN SHARE MODE` allows two transactions to read the same initial value, so after executing the two transactions, the final value of the counter is `+1`; however, if using `FOR UPDATE`, it will lock the second transaction's read of the record value until the first transaction is completed, making the counter's final result `+2`. + +### Using Optimistic Locks + +Optimistic locks are mostly implemented based on a data version (`Version`) recording mechanism. What is a data version? It adds a version identifier to the data, usually by adding a "version" field to the database table in the version-based database table solution. + +When reading data, the version number is also read, and when updating, the version number is incremented. At this time, the submitted data's version information is compared with the current version of the corresponding record in the database table. If the submitted data version number is greater than the current version number in the database table, the update is allowed; otherwise, it is considered outdated data. + +### Summary of Locking Mechanisms + +Each type of lock has its advantages and disadvantages. One cannot claim one is better than the other. Optimistic locks are suitable for scenarios with fewer writes, i.e., when conflicts rarely occur, thus eliminating the overhead of locks and increasing the system's overall throughput. However, if conflicts occur frequently, the upper-level application will keep retrying, which reduces performance, so using pessimistic locks is more appropriate in such cases. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" new file mode 100644 index 00000000000..46bacbd3aad --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" @@ -0,0 +1,37 @@ +--- +slug: '/docs/core/gdb-chaining-schema' +title: 'ORM Model - Schema' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Chaining Operations, Database Switching, DB Object, Model Object, Configuration Group, Schema Method, Cross-Domain Operations] +description: "When using the GoFrame framework for ORM chaining operations, the database can be switched. We can achieve various database switching schemes through different configuration groups, changing the database configuration of singleton objects at runtime, using the Schema method for chaining operations, and including the database name in table names. These methods provide developers with flexible database operation options." +--- + +We know that the database configuration supports configuring a default database, so the `DB` object and `Model` object are bound to a specific database when initialized. There are several ways to switch databases at runtime (assuming our databases include a `user` user database and an `order` database): + +1. Achieved through different configuration groups. This requires configuring different group settings in the configuration file, and then you can obtain a singleton object of a specific database in the program via `g.DB("group name")`. +2. Switch the database of a singleton object at runtime using the `DB.SetSchema` method. Note that since the database configuration of the singleton object is modified, the impact is global: + ```go + g.DB().SetSchema("user-schema") + g.DB().SetSchema("order-schema") + ``` + +3. Create a `Schema` database object using the chaining operation `Schema` method, and create a model object through this database object to perform subsequent chaining operations: + ```go + g.DB().Schema("user-schema").Model("user").All() + g.DB().Schema("order-schema").Model("order").All() + ``` + +4. You can also set the corresponding database for the current chaining operation using the chaining operation `Model.Schema` method. If not set, the default connected database of the `DB` or `TX` is used: + ```go + g.Model("user").Schema("user-schema").All() + g.Model("order").Schema("order-schema").All() + ``` + :::tip + Note the difference between the two usage methods: the former creates a `Model` object after the `Schema` object and then executes operations; the latter achieves database switching by modifying the database name of the current `Model` object operation. + ::: +5. Additionally, if the current database operation's configured user has permission, cross-domain operations can be achieved directly by including the database name in the table name, even for cross-domain association queries: + ```go + // SELECT * FROM `order`.`order` o LEFT JOIN `user`.`user` u ON (o.uid=u.id) WHERE u.id=1 LIMIT 1 + g.Model("order.order o").LeftJoin("user.user u", "o.uid=u.id").Where("u.id", 1).One() + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" new file mode 100644 index 00000000000..f417fa0e909 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/core/gdb-chaining-query-all-and-count' +title: 'Model Query - AllAndCount' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame Framework,ORM Query,AllAndCount,Paging Query,Data Query,Total Count Query,v2.5.0,Record List,Simplify Query Logic] +description: "This document introduces the AllAndCount method provided in the GoFrame framework from version v2.5.0, which is used to simultaneously retrieve data record lists and total counts in paging query scenarios, simplifying query logic. By ignoring Limit/Page operations during queries, the AllAndCount method provides a convenient way to retrieve and count data." +--- + +## Introduction +This method has been provided since version `v2.5.0` and is used for querying both the data record list and the total count, generally used in paging query scenarios to simplify paging query logic. + +Method definition: + +```go +// AllAndCount retrieves all records and the total count of records from the model. +// If useFieldForCount is true, it will use the fields specified in the model for counting; +// otherwise, it will use a constant value of 1 for counting. +// It returns the result as a slice of records, the total count of records, and an error if any. +// The where parameter is an optional list of conditions to use when retrieving records. +// +// Example: +// +// var model Model +// var result Result +// var count int +// where := []interface{}{"name = ?", "John"} +// result, count, err := model.AllAndCount(true) +// if err != nil { +// // Handle error. +// } +// fmt.Println(result, count) +func (m *Model) AllAndCount(useFieldForCount bool) (result Result, totalCount int, err error) +``` + +When querying the total count inside the method, the `Limit/Page` operations in the query will be ignored. + +## Usage Example + +```go +// SELECT `uid`,`name` FROM `user` WHERE `status`='deleted' LIMIT 0,10 +// SELECT COUNT(`uid`,`name`) FROM `user` WHERE `status`='deleted' +all, count, err := Model("user").Fields("uid", "name").Where("status", "deleted").Limit(0, 10).AllAndCount(true) + +// SELECT `uid`,`name` FROM `user` WHERE `status`='deleted' LIMIT 0,10 +// SELECT COUNT(1) FROM `user` WHERE `status`='deleted' +all, count, err := Model("user").Fields("uid", "name").Where("status", "deleted").Limit(0, 10).AllAndCount(false) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" new file mode 100644 index 00000000000..69df8b2722c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" @@ -0,0 +1,42 @@ +--- +slug: '/docs/core/gdb-chaining-query-exist' +title: 'Model Query - Exist' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Query, Exist Method, Data Retrieval, MySQL Table Structure, Model Query, Where Condition, Go Language, Query Efficiency] +description: "Using the Exist method in the GoFrame framework can effectively determine whether data meeting specific conditions exists without needing to retrieve the complete data result. Combined with MySQL table structures, it improves query efficiency using the SELECT 1 method, reducing unnecessary data transmission. This article includes method definition, MySQL table structure examples, and practical use cases to help developers better optimize model query processes." +--- + +The `Exist` method can retrieve whether data with the given `Where` conditions exists more efficiently instead of querying the complete data result and returning it. + +Method definition: +```go +func (m *Model) Exist(where ...interface{}) (bool, error) +``` + +## Example SQL +This is the `MySQL` table structure used in the following example code. + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +## Usage Example + +Querying complete data: +```go +// SELECT * FROM `user` WHERE (id > 1) AND `deleted_at`=0 +g.Model("user").Where("id > ?", 1).All() +``` + +Using the `Exist` method: +```go +// SELECT 1 FROM `user` WHERE (id > 1) AND `deleted_at`=0 LIMIT 1 +g.Model("user").Where("id > ?", 1).Exist() +``` + +As you can see, it uses `SELECT 1` at the underlying level to query the result, meaning if the result exists, it returns `1`; otherwise, it returns nothing. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" new file mode 100644 index 00000000000..8249b2c5604 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/gdb-chaining-query-join' +title: 'Model Query - Join' +sidebar_position: 5 +hide_title: true +keywords: [ORM Query, LeftJoin, RightJoin, InnerJoin, GoFrame, Associated Query, Table Alias, Field Operator, Join Query, Data Aggregation] +description: "How to use ORM for LeftJoin, RightJoin, and InnerJoin queries in the GoFrame framework, including different associated query methods and their application scenarios. The article emphasizes caution in using Join operations in scenarios with large data volume and high concurrency, recommending code-based data aggregation. It also provides examples of join queries using custom table aliases and field operators, along with specific usage methods combined with dao." +--- + +## `*Join` Series Methods + +1. `LeftJoin` Left associative query. +2. `RightJoin` Right associative query. +3. `InnerJoin` Inner associative query. +:::note +Actually, we do not recommend using `Join` for join queries, especially in scenarios with large data volumes and high concurrent request volumes, as it can easily cause performance issues and increase maintenance complexity. It is recommended to use it when necessary. +Additionally, you can refer to +[Model Association - ScanList](../ORM链式操作-模型关联/模型关联-动态关联-ScanList.md) +section where the database is responsible only for storing data and performing simple single-table operations, with data aggregation being implemented at the code level using the functionalities provided by `ORM`. +::: +Example usage: + +```go +// Query the first record that meets the condition +// SELECT u.*,ud.site FROM user u LEFT JOIN user_detail ud ON u.uid=ud.uid WHERE u.uid=1 LIMIT 1 +g.Model("user u").LeftJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.site").Where("u.uid", 1).One() + +// Query specific field values +// SELECT ud.site FROM user u RIGHT JOIN user_detail ud ON u.uid=ud.uid WHERE u.uid=1 LIMIT 1 +g.Model("user u").RightJoin("user_detail ud", "u.uid=ud.uid").Fields("ud.site").Where("u.uid", 1).Value() + +// Grouping and ordering +// SELECT u.*,ud.city FROM user u INNER JOIN user_detail ud ON u.uid=ud.uid GROUP BY city ORDER BY register_time asc +g.Model("user u").InnerJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.city").Group("city").Order("register_time asc").All() + +// Join query without using join +// SELECT u.*,ud.city FROM user u,user_detail ud WHERE u.uid=ud.uid +g.Model("user u,user_detail ud").Where("u.uid=ud.uid").Fields("u.*,ud.city").All() +``` + +## Custom Table Alias + +```go +// SELECT * FROM `user` AS u LEFT JOIN `user_detail` as ud ON(ud.id=u.id) WHERE u.id=1 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 1).One() +g.Model("user").As("u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 1).One() +``` + +## `*JoinOnFields` Series Methods + +The methods `LeftJoinOnFields/RightJoinOnFields/InnerJoinOnFields` allow specifying fields and operators for `join` queries. Example usage: + +```go +// Query the first record that meets the condition +// SELECT user.*,user_detail.address FROM user LEFT JOIN user_detail ON (user.id = user_detail.uid) WHERE user.id=1 LIMIT 1 +g.Model("user").LeftJoinOnFields("user_detail", "id", "=", "uid").Fields("user.*,user_detail.address").Where("id", 1).One() + +// Query multiple records +// SELECT user.*,user_detail.address FROM user RIGHT JOIN user_detail ON (user.id = user_detail.uid) +g.Model("user").RightJoinOnFields("user_detail", "id", "=", "uid").Fields("user.*,user_detail.address").All() +``` + +## Example Usage with `dao` + +```go +// SELECT resource_task_schedule.id,...,time_window.time_window +// FROM `resource_task_schedule` +// LEFT JOIN `time_window` ON (`resource_task_schedule`.`resource_id`=`time_window`.`resource_id`) +// WHERE (time_window.`status`="valid") AND (`time_window`.`start_time` <= 3600) +var ( + orm = dao.ResourceTaskSchedule.Ctx(ctx) + tsTable = dao.ResourceTaskSchedule.Table() + tsCls = dao.ResourceTaskSchedule.Columns() + twTable = dao.TimeWindow.Table() + twCls = dao.TimeWindow.Columns() + scheduleItems []scheduleItem +) +orm = orm.FieldsPrefix(tsTable, tsCls) +orm = orm.FieldsPrefix(twTable, twCls.TimeWindow) +orm = orm.LeftJoinOnField(twTable, twCls.ResourceId) +orm = orm.WherePrefix(twTable, twCls.Status, "valid") +orm = orm.WherePrefixLTE(twTable, twCls.StartTime, 3600) +err = orm.Scan(&scheduleItems) +``` + +```go +// SELECT DISTINCT resource_info.* FROM `resource_info` +// LEFT JOIN `resource_network` ON (`resource_info`.`resource_id`=`resource_network`.`resource_id`) +// WHERE (`resource_info`.`resource_id` like '%10.0.1.3%') +// or (`resource_info`.`resource_name` like '%10.0.1.3%') +// or (`resource_network`.`vip`like '%10.0.1.3%') +// ORDER BY `id` Desc LIMIT 0,2 +var ( + orm = dao.ResourceInfo.Ctx(ctx).OmitEmpty() + rTable = dao.ResourceInfo.Table() + rCls = dao.ResourceInfo.Columns() + nTable = dao.ResourceNetwork.Table() + nCls = dao.ResourceNetwork.Columns() +) +orm = orm.LeftJoinOnField(nTable, rCls.ResourceId) +orm = orm.WherePrefix(rTable, do.ResourceInfo{ + AppId: req.AppIds, + ResourceId: req.ResourceIds, + Region: req.Regions, + Zone: req.Zones, + ResourceName: req.ResourceNames, + Status: req.Statuses, + BusinessType: req.Products, + Engine: req.Engines, + Version: req.Versions, +}) +orm = orm.WherePrefix(nTable, do.ResourceNetwork{ + Vip: req.Vips, + VpcId: req.VpcIds, + SubnetId: req.SubnetIds, +}) +// Fuzzy like querying. +if req.Key != "" { + var ( + keyLike = "%" + req.Key + "%" + ) + whereFormat := fmt.Sprintf( + "(`%s`.`%s` like ?) or (`%s`.`%s` like ?) or (`%s`.`%s`like ?) ", + rTable, rCls.ResourceId, + rTable, rCls.ResourceName, + nTable, nCls.Vip, + ) + orm = orm.Where(whereFormat, keyLike, keyLike, keyLike) +} +// Resource items. +err = orm.Distinct().FieldsPrefix(rTable, "*").Order(req.Order, req.OrderDirection).Limit(req.Offset, req.Limit).Scan(&res.Items) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" new file mode 100644 index 00000000000..8d938e95d3e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gdb-chaining-query-scan-and-count' +title: 'Model Query - ScanAndCount' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM Query, ScanAndCount, Pagination Query, Limit, Page, Data Query, Total Count Query, Chaining Query] +description: "When using the GoFrame framework for ORM queries, simplify pagination query scenarios. With the ScanAndCount method, data queries and total count queries can be completed in a single operation, effectively reducing code redundancy and improving development efficiency. Suitable for situations where both data and its total count need to be obtained, such as pagination queries." +--- + +## Introduction + +In pagination query scenarios, we often need to first call the `Scan` method combined with the `Limit/Page` chaining operation method to query the list, and then remove the `Limit/Page` chaining operation method to query the total count. This process is quite cumbersome, so from version `v2.5.0`, the framework provides the `ScanAndCount` method to simplify pagination query scenarios. + +## Example Usage +:::tip +The sample code is derived from business project cases and is for reference and understanding only, it cannot run independently. +::: +Using traditional pagination query logic code: + +```go +// GetList retrieves the user list of the instance. +func (s sUserInfo) GetList(ctx context.Context, in model.UserInfoGetListInput) (items []entity.UserInfo, total int, err error) { + items = make([]entity.UserInfo, 0) + orm := dao.UserInfo.Ctx(ctx).Where(do.UserInfo{ + ResourceId: in.ResourceId, + Status: in.Statuses, + }) + err = orm.Order(in.OrderBy, in.OrderDirection).Limit(in.Offset, in.Limit).Scan(&items) + if err != nil { + return + } + total, err = orm.Count() + return +} +``` + +Using the `ScanAndCount` method for pagination queries: + +```go +// GetList retrieves the user list of the instance. +func (s sUserInfo) GetList(ctx context.Context, in model.UserInfoGetListInput) (items []entity.UserInfo, total int, err error) { + items = make([]entity.UserInfo, 0) + err = dao.UserInfo.Ctx(ctx).Where(do.UserInfo{ + ResourceId: in.ResourceId, + Status: in.Statuses, + }). + Order(in.OrderBy, in.OrderDirection). + Limit(in.Offset, in.Limit). + ScanAndCount(&items, &total, false) + return +} +``` + +## Notes + +- It is only used in scenarios where both data and total count need to be queried, generally in pagination scenarios. +- The third parameter of `ScanAndCount`, `useFieldForCount`, indicates whether to use `Fields` as the parameter for `Count` operation. Generally, it should be `false`, meaning that the `COUNT(1)` query is performed for the total count. Passing `true` means using the fields of the query as the parameter for the `COUNT` method. diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" new file mode 100644 index 00000000000..99fc7486c07 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gdb-chaining-query-scan' +title: 'Model Query - Scan' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, ORM query, Scan method, struct conversion, struct array, gdb, query result, struct object, Go framework] +description: "Techniques for using the Scan method for ORM queries in the GoFrame framework, mainly including how to convert query results to struct objects and struct arrays. Illustrated with example code demonstrating the usage of the Scan method, such as converting a single record to a struct object and multiple records to a struct array, helping users effectively handle database query results." +--- + +The `Scan` method supports converting query results into a struct or struct array. The `Scan` method will automatically identify the type of conversion to execute based on the given parameter type. + +## `struct` Object + +The `Scan` method supports converting query results into a `struct` object. The query result should be a specific single record, and the `pointer` parameter should be the pointer address of the `struct` object (`*struct` or `**struct`). For example: + +```go +type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time +} +user := User{} +g.Model("user").Where("id", 1).Scan(&user) +``` + +Or + +```go +var user = User{} +g.Model("user").Where("id", 1).Scan(&user) +``` + +The first two methods pre-initialize the object (allocate memory in advance), while the recommended method is: + +```go +var user *User +g.Model("user").Where("id", 1).Scan(&user) +``` + +This method initializes and allocates memory only when data is queried. Note the difference in usage, especially the difference in parameter types (the first two methods pass a `*User` type, while this method actually passes a `**User` type). + +## `struct` Array + +The `Scan` method supports converting multiple query results into a `[]struct/[]*struct` array. The query result should be a result set composed of multiple records, and the `pointer` should be the pointer address of the array. For example: + +```go +var users []User +g.Model("user").Scan(&users) +``` + +Or + +```go +var users []*User +g.Model("user").Scan(&users) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" new file mode 100644 index 00000000000..1fab9abc0e7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/core/gdb-chaining-query-union' +title: 'Model Query - Union/UnionAll' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame Framework,ORM,Union,UnionAll,Chaining Operations,Method Operations,Query Optimization,MySQL,SQL] +description: "Use the ORM component in the GoFrame framework to perform Union and UnionAll query operations. The Union operator can remove duplicate data, while the UnionAll operator retains all data. These two query methods can be easily achieved through chaining operations or method operations. The article also introduces how to perform combined queries in MySQL and provides detailed code examples." +--- + +`GoFrame ORM` component supports `Union/UnionAll` operations, where the `Union/UnionAll` operator is used to combine the results of two or more `SELECT` statements into a single result set. For more information on `Union/UnionAll` composite queries, refer to the MySQL official documentation [https://dev.mysql.com/doc/refman/8.0/en/union.html](https://dev.mysql.com/doc/refman/8.0/en/union.html). We can achieve `Union/UnionAll` operations through chaining operations or method operations. + +## Method Definition + +```go +// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement. +func (c *Core) Union(unions ...*Model) *Model + +// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement. +func (c *Core) UnionAll(unions ...*Model) *Model +``` + +## `Union` + +Using the `Union` operator, multiple `SELECT` statements will remove duplicate data. + +```go +// Get the default configured database object (configuration name is "default") +db := g.DB() + +db.Union( + db.Model("user").Where("id", 1), + db.Model("user").Where("id", 2), + db.Model("user").WhereIn("id", g.Slice{1, 2, 3}), +).OrderDesc("id").All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION +// (SELECT * FROM `user` WHERE `id`=2) +// UNION +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +It can also be implemented through `dao` chaining operations: + +```go +dao.User.Union( + dao.User.Where(dao.User.Columns.Id, 1), + dao.User.Where(dao.User.Columns.Id, 2), + dao.User.WhereIn(dao.User.Columns.Id, g.Slice{1, 2, 3}), +).OrderDesc(dao.User.Columns.Id).All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION +// (SELECT * FROM `user` WHERE `id`=2) +// UNION +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +## `UnionAll` + +Using the `UnionAll` operator, multiple `SELECT` statements will not remove duplicate data. + +```go +db.UnionAll( + db.Model("user").Where("id", 1), + db.Model("user").Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}), +).OrderDesc("id").All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION ALL +// (SELECT * FROM `user` WHERE `id`=2) +// UNION ALL +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +It can also be implemented through `dao` chaining operations: + +```go +dao.User.UnionAll( + dao.User.Where(dao.User.Columns.Id, 1), + dao.User.Where(dao.User.Columns.Id, 2), + dao.User.WhereIn(dao.User.Columns.Id, g.Slice{1, 2, 3}), +).OrderDesc(dao.User.Columns.Id).All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION ALL +// (SELECT * FROM `user` WHERE `id`=2) +// UNION ALL +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" new file mode 100644 index 00000000000..2047614be02 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" @@ -0,0 +1,248 @@ +--- +slug: '/docs/core/gdb-chaining-query-where' +title: 'Model Query - Where' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, ORM, query, Where conditions, condition queries, database, conditional methods, Go language, data processing, GoFrame framework] +description: "A detailed discussion of the various conditional query methods provided by the ORM component in the GoFrame framework, including ways to use methods like Where, WhereOr, Wheref, etc., and how they perform conditional grouping. Through examples, it demonstrates how to use these methods for complex database queries and explores the advantages of using primary key queries." +--- + +The `ORM` component provides some commonly used conditional query methods, and the conditional methods support multiple data type inputs. + +```go +func (m *Model) Where(where interface{}, args...interface{}) *Model +func (m *Model) Wheref(format string, args ...interface{}) *Model +func (m *Model) WherePri(where interface{}, args ...interface{}) *Model +func (m *Model) WhereBetween(column string, min, max interface{}) *Model +func (m *Model) WhereLike(column string, like interface{}) *Model +func (m *Model) WhereIn(column string, in interface{}) *Model +func (m *Model) WhereNull(columns ...string) *Model +func (m *Model) WhereLT(column string, value interface{}) *Model +func (m *Model) WhereLTE(column string, value interface{}) *Model +func (m *Model) WhereGT(column string, value interface{}) *Model +func (m *Model) WhereGTE(column string, value interface{}) *Model + +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereNotLike(column string, like interface{}) *Model +func (m *Model) WhereNotIn(column string, in interface{}) *Model +func (m *Model) WhereNotNull(columns ...string) *Model + +func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrLike(column string, like interface{}) *Model +func (m *Model) WhereOrIn(column string, in interface{}) *Model +func (m *Model) WhereOrNull(columns ...string) *Model +func (m *Model) WhereOrLT(column string, value interface{}) *Model +func (m *Model) WhereOrLTE(column string, value interface{}) *Model +func (m *Model) WhereOrGT(column string, value interface{}) *Model +func (m *Model) WhereOrGTE(column string, value interface{}) *Model + +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model +func (m *Model) WhereOrNotNull(columns ...string) *Model +``` + +Below, we provide a brief introduction to several commonly used methods; other conditional query methods are similar in usage. + +## `Where/WhereOr` Query Conditions + +### Introduction + +These two methods are used to pass query condition parameters, and the supported parameters can be any `string/map/slice/struct/*struct` type. + +It is recommended to use a string parameter method for `Where` condition parameters (using `?` as a placeholder for preprocessing) because `map`/`struct` types as query parameters cannot guarantee order, and in some cases (the database may help you automatically optimize query indexes), the order of the database index and the order of your query condition has a certain relationship. + +When using multiple `Where` methods to connect query conditions, the conditions are connected using `And`. In addition, when multiple query conditions exist, `gdb` will default to enclosing each condition in `()` brackets, allowing for friendly support for query condition grouping. + +Example usage: + +```go +// WHERE `uid`=1 +Where("uid=1") +Where("uid", 1) +Where("uid=?", 1) +Where(g.Map{"uid" : 1}) +// WHERE `uid` <= 1000 AND `age` >= 18 +Where(g.Map{ + "uid <=" : 1000, + "age >=" : 18, +}) + +// WHERE (`uid` <= 1000) AND (`age` >= 18) +Where("uid <=?", 1000).Where("age >=?", 18) + +// WHERE `level`=1 OR `money`>=1000000 +Where("level=? OR money >=?", 1, 1000000) + +// WHERE (`level`=1) OR (`money`>=1000000) +Where("level", 1).WhereOr("money >=", 1000000) + +// WHERE `uid` IN(1,2,3) +Where("uid IN(?)", g.Slice{1,2,3}) +``` + +Example using `struct` parameters, where the `orm` `tag` is used to specify the mapping relationship between `struct` attributes and table fields: + +```go +type Condition struct{ + Sex int `orm:"sex"` + Age int `orm:"age"` +} +Where(Condition{1, 18}) +// WHERE `sex`=1 AND `age`=18 +``` + +### Example Usage + +`Where + string`, using string and preprocessing as condition parameters. + +```go +// Query multiple records with Limit pagination +// SELECT * FROM user WHERE uid>1 LIMIT 0,10 +g.Model("user").Where("uid > ?", 1).Limit(0, 10).All() + +// Using the Fields method to specify query fields +// Default query is * when Fields method is not used to specify query fields +// SELECT uid,name FROM user WHERE uid>1 LIMIT 0,10 +g.Model("user").Fields("uid,name").Where("uid > ?", 1).Limit(0, 10).All() + +// Supporting multiple Where condition parameter types +// SELECT * FROM user WHERE uid=1 LIMIT 1 +g.Model("user").Where("uid=1").One() +g.Model("user").Where("uid", 1).One() +g.Model("user").Where("uid=?", 1).One() + +// SELECT * FROM user WHERE (uid=1) AND (name='john') LIMIT 1 +g.Model("user").Where("uid", 1).Where("name", "john").One() +g.Model("user").Where("uid=?", 1).Where("name=?", "john").One() + +// SELECT * FROM user WHERE (uid=1) OR (name='john') LIMIT 1 +g.Model("user").Where("uid=?", 1).WhereOr("name=?", "john").One() +``` + +`Where + slice`, preprocessing parameters can be directly provided through the `slice` parameter. + +```go +// SELECT * FROM user WHERE age>18 AND name like '%john%' +g.Model("user").Where("age>? AND name like ?", g.Slice{18, "%john%"}).All() + +// SELECT * FROM user WHERE status=1 +g.Model("user").Where("status=?", g.Slice{1}).All() +``` + +`Where + map`, using any `map` type to pass condition parameters. + +```go +// SELECT * FROM user WHERE uid=1 AND name='john' LIMIT 1 +g.Model("user").Where(g.Map{"uid" : 1, "name" : "john"}).One() + +// SELECT * FROM user WHERE uid=1 AND age>18 LIMIT 1 +g.Model("user").Where(g.Map{"uid" : 1, "age>" : 18}).One() +``` + +`Where + struct/*struct`, `struct` tags support `orm/json`, mapping properties to field names. + +```go +type User struct { + Id int `json:"uid"` + UserName string `orm:"name"` +} +// SELECT * FROM user WHERE uid =1 AND name='john' LIMIT 1 +g.Model("user").Where(User{ Id : 1, UserName : "john"}).One() + +// SELECT * FROM user WHERE uid =1 LIMIT 1 +g.Model("user").Where(&User{ Id : 1}).One() +``` + +The query conditions above are relatively simple. Let's look at a more complex query example. + +```go +condition := g.Map{ + "title like ?" : "%Jiuzhai%", + "online" : 1, + "hits between ? and ?" : g.Slice{1, 10}, + "exp > 0" : nil, + "category" : g.Slice{100, 200}, +} +// SELECT * FROM article WHERE title like '%Jiuzhai%' AND online=1 AND hits between 1 and 10 AND exp > 0 AND category IN(100,200) +g.Model("article").Where(condition).All() +``` + +## `Wheref` Formatted Condition String + +In some scenarios, entering a conditional statement with strings often requires the use of `fmt.Sprintf` to format the condition (note to use placeholders in the string instead of directly formatting the variable), so a convenient method combining `Where+fmt.Sprintf`, `Wheref`, is provided. Usage example: + +```go +// WHERE score > 100 and status in('succeeded','completed') +Wheref(`score > ? and status in (?)`, 100, g.Slice{"succeeded", "completed"}) +``` + +## `WherePri` Supports Primary Key Query Conditions + +The `WherePri` method functions the same way as `Where`, but it provides intelligent recognition of table primary keys and is commonly used for convenient data queries based on primary keys. Suppose the primary key of the `user` table is `uid`, let's see the difference between `Where` and `WherePri`: + +```go +// WHERE `uid`=1 +Where("uid", 1) +WherePri(1) + +// WHERE `uid` IN(1,2,3) +Where("uid", g.Slice{1,2,3}) +WherePri(g.Slice{1,2,3}) +``` + +As you can see, when using the `WherePri` method and the given parameter is a single basic type or a `slice` type, it will be recognized as the value of the primary key query condition. + +## `WhereBuilder` Complex Condition Combinations + +`WhereBuilder` is used to generate complex `Where` conditions. + +### Object Creation + +We can use the `Builder` method of `Model` to generate a `WhereBuilder` object. The method is defined as follows: + +```go +// Builder creates and returns a WhereBuilder. +func (m *Model) Builder() *WhereBuilder +``` + +### Usage Example + +```go +// SELECT * FROM `user` WHERE `id`=1 AND `address`="USA" AND (`status`="active" OR `status`="pending") +m := g.Model("user") +all, err := m.Where("id", 1).Where("address", "USA").Where( + m.Builder().Where("status", "active").WhereOr("status", "pending"), +).All() +``` + +## Note: `0=1` Condition Triggered by Empty Array Condition + +Let's look at an example: + +`SQL1`: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{"permitted", "inherited"}).Where("uid", 1).All() +// SELECT * FROM `auth` WHERE (`status` IN('permitted','inherited')) AND (`uid`=1) +``` + +`SQL2`: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{}).Where("uid", 1).All() +// SELECT * FROM `auth` WHERE (0=1) AND (`uid`=1) +``` + +As you can see, when the given array condition is an empty array, the resulting `SQL` has a `0=1` invalid condition. Why is that? + +When developers do not explicitly declare the intention to filter empty array conditions, `ORM` does not automatically filter empty array conditions to avoid program logic bypassing `SQL` restriction conditions, which could cause unpredictable business issues. If the developer determines that the `SQL` restriction condition can be filtered, they can explicitly call the `OmitEmpty/OmitEmptyWhere` methods to perform empty condition filtering, as shown below: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{}).Where("uid", 1).OmitEmpty().All() +// SELECT * FROM `auth` WHERE `uid`=1 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" new file mode 100644 index 00000000000..300d26eab50 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" @@ -0,0 +1,56 @@ +--- +slug: '/docs/core/gdb-chaining-query-group-order-having' +title: 'Model Query - Group/Order' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Group, Order, Having, Sorting, Group Query, Condition Filter, Database Query] +description: "Use GoFrame framework's ORM for query operations including grouping, sorting, and condition filtering. Implement data grouping with the Group method, sorting with the Order method, and condition filtering on query results with the Having method. Provides detailed code examples and method explanations to help users better master database operation skills." +--- + +## `Group/Order` Grouping and Sorting + +The `Group` method is used for query grouping, and the `Order` method is used for query sorting. Example usage: + +```go +// SELECT COUNT(*) total, age FROM `user` GROUP BY age +g.Model("user").Fields("COUNT(*) total,age").Group("age").All() + +// SELECT * FROM `student` ORDER BY class asc, course asc, score desc +g.Model("student").Order("class asc,course asc,score desc").All() +``` + +At the same time, `goframe`’s `ORM` offers some common sorting methods: + +```go +// Sort in ascending order according to a specified field +func (m *Model) OrderAsc(column string) *Model +// Sort in descending order according to a specified field +func (m *Model) OrderDesc(column string) *Model +// Random sorting +func (m *Model) OrderRandom() *Model +``` + +Example usage: + +```go +// SELECT `id`,`title` FROM `article` ORDER BY `created_at` ASC +g.Model("article").Fields("id,title").OrderAsc("created_at").All() + +// SELECT `id`,`title` FROM `article` ORDER BY `views` DESC +g.Model("article").Fields("id,title").OrderDesc("views").All() + +// SELECT `id`,`title` FROM `article` ORDER BY RAND() +g.Model("article").Fields("id,title").OrderRandom().All() +``` + +## `Having` Condition Filtering + +The `Having` method is used for condition filtering of query results. Example usage: + +```go +// SELECT COUNT(*) total, age FROM `user` GROUP BY age HAVING total>100 +g.Model("user").Fields("COUNT(*) total,age").Group("age").Having("total>100").All() + +// SELECT * FROM `student` ORDER BY class HAVING score>60 +g.Model("student").Order("class").Having("score>?", 60).All() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..2a7f475f5be --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gdb-chaining-query-sub-query' +title: 'Model Query - Subquery' +sidebar_position: 8 +hide_title: true +keywords: [ORM, Subquery, Where Subquery, Having Subquery, From Subquery, GoFrame, GoFrame Framework, Database Query, Data Model, Subquery Statement] +description: "Three subquery features supported by the ORM component in the GoFrame framework: Where subquery, Having subquery, and From subquery. Examples demonstrate how to use subqueries in Where, Having conditions, and when using the Model method to create models to enhance database query efficiency." +--- + +The `ORM` component currently supports three common subquery syntaxes: `Where` subquery, `Having` subquery, and `From` subquery. + +## `Where` Subquery + +We can use subquery statements in the `Where` condition, for example: + +```go +g.Model("orders").Where("amount > ?", g.Model("orders").Fields("AVG(amount)")).Scan(&orders) +// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders") +``` + +## `Having` Subquery + +We can use subquery statements in the `Having` condition, for example: + +```go +subQuery := g.Model("users").Fields("AVG(age)").WhereLike("name", "name%") +g.Model("users").Fields("AVG(age) as avgage").Group("name").Having("AVG(age) > ?", subQuery).Scan(&results) +// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +``` + +## `From` Subquery + +We can use subquery statements when creating models using the `Model` method, for example: + +```go +g.Model("? as u", g.Model("users").Fields("name", "age")).Where("age", 18).Scan(&users) +// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 + +subQuery1 := g.Model("users").Fields("name") +subQuery2 := g.Model("pets").Fields("name") +g.Model("? as u, ? as p", subQuery1, subQuery2).Scan(&users) +// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..d7373d3a972 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" @@ -0,0 +1,280 @@ +--- +slug: '/docs/core/gdb-chaining-query-example' +title: 'Model Query - Operators' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, ORM Query, Database Operations, Chaining Queries, Common Conditions, Statistical Methods, Field Uniqueness, Query Examples, GoFrame Framework, Parameter Filtering] +description: "Common operation examples for ORM queries using the GoFrame framework, including IN, LIKE, MIN/MAX/AVG/SUM operations. It also introduces the use of chaining query methods like WhereIn, WhereNotIn, WhereBetween, etc., and helps understand different query strategies and parameter filtering through examples." +--- + +## `in` Query + +Use string or `slice` parameter types. When using the `slice` parameter type, only one `?` placeholder is needed. + +```go +// SELECT * FROM user WHERE uid IN(100,10000,90000) +g.Model("user").Where("uid IN(?,?,?)", 100, 10000, 90000).All() +g.Model("user").Where("uid", g.Slice{100, 10000, 90000}).All() + +// SELECT * FROM user WHERE gender=1 AND uid IN(100,10000,90000) +g.Model("user").Where("gender=? AND uid IN(?)", 1, g.Slice{100, 10000, 90000}).All() + +// SELECT COUNT(*) FROM user WHERE age in(18,50) +g.Model("user").Where("age IN(?,?)", 18, 50).Count() +g.Model("user").Where("age", g.Slice{18, 50}).Count() +``` + +Use any `map` parameter type. + +```go +// SELECT * FROM user WHERE gender=1 AND uid IN(100,10000,90000) +g.Model("user").Where(g.Map{ + "gender" : 1, + "uid" : g.Slice{100,10000,90000}, +}).All() +``` + +Use `struct` parameter type, note that the order of query conditions depends on the order of struct attribute definitions. + +```go +type User struct { + Id []int `orm:"uid"` + Gender int `orm:"gender"` +} +// SELECT * FROM `user` WHERE uid IN(100,10000,90000) AND gender=1 +g.Model("user").Where(User{ + Gender: 1, + Id: []int{100, 10000, 90000}, +}).All() + +``` + +For usability, if the passed `slice` parameter is empty or `nil`, the query will not throw an error but convert to a `false` condition statement. + +```go +// SELECT * FROM `user` WHERE 0=1 +g.Model("user").Where("uid", g.Slice{}).All() +// SELECT * FROM `user` WHERE `uid` IS NULL +g.Model("user").Where("uid", nil).All() +``` + +The `ORM` also provides common condition methods `WhereIn/WhereNotIn/WhereOrIn/WhereOrNotIn` for common `In` query condition filtering. Method definitions are as follows: + +```go +func (m *Model) WhereIn(column string, in interface{}) *Model +func (m *Model) WhereNotIn(column string, in interface{}) *Model +func (m *Model) WhereOrIn(column string, in interface{}) *Model +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model +``` + +Examples: + +```go +// SELECT * FROM `user` WHERE (`gender`=1) AND (`type` IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) AND (`type` NOT IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereNotIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`type` IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereOrIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`type` NOT IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereOrNotIn("type", g.Slice{1,2,3}).All() +``` + +## `like` Query + +```go +// SELECT * FROM `user` WHERE name like '%john%' +g.Model("user").Where("name like ?", "%john%").All() +// SELECT * FROM `user` WHERE birthday like '1990-%' +g.Model("user").Where("birthday like ?", "1990-%").All() + +``` + +Starting from `goframe v1.16`, `goframe`'s `ORM` also provides common condition methods `WhereLike/WhereNotLike/WhereOrLike/WhereOrNotLike` for common `Like` query condition filtering. Method definitions are as follows: + +```go +func (m *Model) WhereLike(column string, like interface{}) *Model +func (m *Model) WhereNotLike(column string, like interface{}) *Model +func (m *Model) WhereOrLike(column string, like interface{}) *Model +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model +``` + +Examples: + +```go +// SELECT * FROM `user` WHERE (`gender`=1) AND (`name` LIKE 'john%') +g.Model("user").Where("gender", 1).WhereLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) AND (`name` NOT LIKE 'john%') +g.Model("user").Where("gender", 1).WhereNotLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`name` LIKE 'john%') +g.Model("user").Where("gender", 1).WhereOrLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`name` NOT LIKE 'john%') +g.Model("user").Where("gender", 1).WhereOrNotLike("name", "john%").All() +``` + +## `min/max/avg/sum` + +We directly apply the statistical method on the `Fields` method, for example: + +```go +// SELECT MIN(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("MIN(score)").Where("uid", 1).Value() + +// SELECT MAX(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("MAX(score)").Where("uid", 1).Value() + +// SELECT AVG(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("AVG(score)").Where("uid", 1).Value() + +// SELECT SUM(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("SUM(score)").Where("uid", 1).Value() +``` + +Starting from `goframe v1.16`, `goframe`'s `ORM` also provides common statistical methods `Min/Max/Avg/Sum` for common field statistical queries. Method definitions are as follows: + +```go +func (m *Model) Min(column string) (float64, error) +func (m *Model) Max(column string) (float64, error) +func (m *Model) Avg(column string) (float64, error) +func (m *Model) Sum(column string) (float64, error) +``` + +The above examples using shortcut statistical methods: + +```go +// SELECT MIN(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Min("score") + +// SELECT MAX(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Max("score") + +// SELECT AVG(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Avg("score") + +// SELECT SUM(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Sum("score") +``` + +## `count` Query + +```go +// SELECT COUNT(1) FROM `user` WHERE `birthday`='1990-10-01' +g.Model("user").Where("birthday", "1990-10-01").Count() +// SELECT COUNT(uid) FROM `user` WHERE `birthday`='1990-10-01' +g.Model("user").Fields("uid").Where("birthday", "1990-10-01").Count() + +``` + +Starting from `goframe v1.16`, `goframe`'s `ORM` also provides a common method `CountColumn` for `Count` by field. Method definition is as follows: + +```go +func (m *Model) CountColumn(column string) (int, error) +``` + +Example: + +```go +g.Model("user").Where("birthday", "1990-10-01").CountColumn("uid") +``` + +## `distinct` Query + +```go +// SELECT DISTINCT uid,name FROM `user` +g.Model("user").Fields("DISTINCT uid,name").All() +// SELECT COUNT(DISTINCT uid,name) FROM `user` +g.Model("user").Fields("DISTINCT uid,name").Count() + +``` + +Starting from `goframe v1.16`, `goframe`'s `ORM` also provides a method `Distinct` for field uniqueness filtering. Method definition is as follows: + +```go +func (m *Model) Distinct() *Model +``` + +Example: + +```go +// SELECT COUNT(DISTINCT `name`) FROM `user` +g.Model("user").Distinct().CountColumn("name") + +// SELECT COUNT(DISTINCT uid,name) FROM `user` +g.Model("user").Distinct().CountColumn("uid,name") + +// SELECT DISTINCT group,age FROM `user` +g.Model("user").Fields("group, age").Distinct().All() +``` + +## `between` Query + +```go +// SELECT * FROM `user` WHERE age between 18 and 20 +g.Model("user").Where("age between ? and ?", 18, 20).All() + +``` + +Starting from `goframe v1.16`, `goframe`'s `ORM` also provides common condition methods `WhereBetween/WhereNotBetween/WhereOrBetween/WhereOrNotBetween` for common `Between` query condition filtering. Method definitions are as follows: + +```go +func (m *Model) WhereBetween(column string, min, max interface{}) *Model +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model +``` + +Examples: + +```go +// SELECT * FROM `user` WHERE (`gender`=0) AND (`age` BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) AND (`age` NOT BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereNotBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) OR (`age` BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereOrBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) OR (`age` NOT BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereOrNotBetween("age", 16, 20).All() +``` + +## `null` Query + +The `ORM` provides common condition methods `WhereNull/WhereNotNull/WhereOrNull/WhereOrNotNull` for common `Null` query condition filtering. Method definitions are as follows: + +```go +func (m *Model) WhereNull(columns ...string) *Model +func (m *Model) WhereNotNull(columns ...string) *Model +func (m *Model) WhereOrNull(columns ...string) *Model +func (m *Model) WhereOrNotNull(columns ...string) *Model +``` + +Examples: + +```go +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NOT NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNotNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') OR (`inviter` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereOrNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') OR (`inviter` IS NOT NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereOrNotNull("inviter").All() +``` + +Additionally, these methods support multiple field inputs, for example: + +```go +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NULL) AND (`creator` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNull("inviter", "creator").All() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..cedb2b9a9f1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gdb-chaining-query-all-one-array-value-count' +title: 'Model Query - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Data Query, All Method, One Method, Array Method, Value Method, Count Method, CountColumn Method] +description: "Five common data query methods in the GoFrame framework: All, One, Array, Value, and Count. These methods allow you to easily retrieve multiple or single records from the database and support direct input of conditional parameters. Through example code, you'll learn how to efficiently perform database operations in GoFrame." +--- + +## Introduction +Commonly used data query methods: + +```go +func (m *Model) All(where ...interface{} (Result, error) +func (m *Model) One(where ...interface{}) (Record, error) +func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) +func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) +func (m *Model) Count(where ...interface{}) (int, error) +func (m *Model) CountColumn(column string) (int, error) +``` + +Brief explanation: + +1. `All` is used to query and return a list/array of multiple records. +2. `One` is used to query and return a single record. +3. `Array` is used to query data of specified field columns and return an array. +4. `Value` is used to query and return one field value, often used in conjunction with the `Fields` method. +5. `Count` is used to query and return the number of records. + +Additionally, it can be seen that these four methods' definitions also support direct input of conditional parameters, with parameter types consistent with the `Where` method. However, it is important to note that the `Array` and `Value` methods require at least a field parameter to be input. + +## Usage Examples + +```go +// SELECT * FROM `user` WHERE `score`>60 +Model("user").Where("score>?", 60).All() + +// SELECT * FROM `user` WHERE `score`>60 LIMIT 1 +Model("user").Where("score>?", 60).One() + +// SELECT `name` FROM `user` WHERE `score`>60 +Model("user").Fields("name").Where("score>?", 60).Array() + +// SELECT `name` FROM `user` WHERE `uid`=1 LIMIT 1 +Model("user").Fields("name").Where("uid", 1).Value() + +// SELECT COUNT(1) FROM `user` WHERE `status` IN(1,2,3) +Model("user").Where("status", g.Slice{1,2,3}).Count() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" new file mode 100644 index 00000000000..4d2f760b8d4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-chaining-query' +title: 'ORM Model - Query' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Chained Operations, Data Query, gdb, Database, Query Methods, Relational Mapping, Developer Guide] +description: "The ORM chained data query method for database operations using the GoFrame framework. Through chained operations, developers can more conveniently construct query statements, improving query efficiency and code readability. These features provide developers with richer querying capabilities." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" new file mode 100644 index 00000000000..2f1803b70e2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time' +title: 'ORM Model - Time Fields' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Chaining Operations, Time Maintenance, gdb, Auto-Fill, Soft Delete, Presentation Layer, Data Operation] +description: "The time maintenance feature when using ORM chaining operations in the gdb module of the GoFrame framework. By automatically filling in creation, update, and deletion times, development efficiency is significantly improved. The article elaborates on how to enable these features and implement them during database operations such as insertions, updates, and deletions. Additionally, it provides solutions for scenarios like soft deletion and ignoring time maintenance." +--- + +## Introduction +:::warning +Note that this feature is only effective for chaining operations. +::: +The `gdb` module supports the automatic filling of creation, update, and deletion times for data records, improving development and maintenance efficiency. To facilitate unified maintenance of time field names and types, if using this feature, we have the following conventions: + +- The field type can be a time type, numeric integer, or boolean, such as: `date`, `datetime`, `timestamp`, `int`, `uint`, `big int`, `bool`, etc. +- Field names can be customized, with default naming conventions being: + - `created_at` is updated during record creation and is only written once. + - `updated_at` is updated during record modification and is updated each time the record changes. + - `deleted_at` is for the soft delete feature of the record and is only written once when the record is deleted. +Field names are case-insensitive and ignore special characters, for example, `CreatedAt`, `UpdatedAt`, `DeletedAt` are also supported. + +## Feature Configuration + +Time field names can be customized in the configuration file, and the feature can be completely disabled on the database instance using the `timeMaintainDisabled` configuration. + +Corresponding configuration items in the configuration file: + +```yaml +database: + default: # Group name, customizable, default is "default" + createdAt: "created_at" # (Optional) Auto-create time field name + updatedAt: "updated_time" # (Optional) Auto-update time field name + deletedAt: "is_deleted" # (Optional) Soft delete time field name + timeMaintainDisabled: false # (Optional) Whether to completely disable the time update feature. If true, CreatedAt/UpdatedAt/DeletedAt will be ineffective +``` + +:::tip +Especially for historical projects, where different time field names already exist, the time field names can be flexibly configured using configuration items. +::: + +For a complete database configuration, please refer to the [ORM - Configuration](../../ORM使用配置/ORM使用配置.md) section. + +## Feature Activation + +When a data table contains any or multiple fields such as `created_at`, `updated_at`, `deleted_at`, or contains corresponding configuration fields in the configuration file, this feature is automatically enabled. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" new file mode 100644 index 00000000000..58682f0f345 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" @@ -0,0 +1,50 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-option' +title: 'Time Fields - SoftTimeOption' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, Time Maintenance, SoftTimeOption, Database Operation, Time Granularity, Millisecond Timestamp, MySQL Table Structure, ORM Operation, Time Field, Big Integer Storage] +description: "Introduction on how to use SoftTimeOption in the GoFrame framework to control time writing granularity, converting from second-level to millisecond-level timestamps, and providing relevant MySQL table structures and example code to help developers flexibly configure time fields, supporting multiple time granularity options to meet different project needs, and inserting data through ORM methods" +--- + +In the previous [Time Fields - Integer Fields](./时间维护-整型字段.md) example, the time fields are written with second-level timestamps. But what if we want to control the granularity of time writing and write millisecond-level timestamps? We can use `SoftTimeOption` to control the granularity of the written time values. + +## Example SQL +Here's the `MySQL` table structure used in the following example code. Since we need to write values with a granularity finer than seconds, the field type uses `big int` for storage. + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` bigint unsigned DEFAULT NULL, + `updated_at` bigint unsigned DEFAULT NULL, + `deleted_at` bigint unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +If you try to test and view the `SQL` statements executed by `ORM` operations, it is recommended to enable `debug` mode (Documents: [ORM Senior - Debug Mode](../../ORM高级特性/ORM高级特性-调试模式.md)), and the `SQL` statements will automatically be printed in the log output. +::: + +## `created_at` Write Time + +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731484186556,1731484186556,0) +g.Model("user").SoftTime(gdb.SoftTimeOption{ + SoftTimeType: gdb.SoftTimeTypeTimestampMilli, +}).Data(g.Map{"name": "john"}).Insert() +``` + +The `SoftTimeType` controls the time granularity, with the following granularity options: +```go +const ( + SoftTimeTypeAuto SoftTimeType = 0 // (Default) Auto detect the field type by table field type. + SoftTimeTypeTime SoftTimeType = 1 // Using datetime as the field value. + SoftTimeTypeTimestamp SoftTimeType = 2 // In unix seconds. + SoftTimeTypeTimestampMilli SoftTimeType = 3 // In unix milliseconds. + SoftTimeTypeTimestampMicro SoftTimeType = 4 // In unix microseconds. + SoftTimeTypeTimestampNano SoftTimeType = 5 // In unix nanoseconds. +) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..8c03db0d95e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,111 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-example' +title: 'Time Fields - Intro' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Time Maintenance, Soft Delete, created_at, updated_at, deleted_at, Database Operations, Join Query, Unscoped, Time Fields] +description: "This article introduces the basic methods for managing database time fields using the GoFrame framework, including the mechanisms for writing and updating fields like created_at, updated_at, and deleted_at, and the impact of soft delete features on query and update operations. It also demonstrates methods for join queries and ignoring time features with Unscoped. Through these examples, you can effectively manage soft deletion and timestamps, ensuring the accuracy of database records." +--- + +In the following examples, it is assumed that the sample data tables include the fields `created_at`, `updated_at`, and `deleted_at`, and that the field type is `datetime`. + +## Example SQL +This is the `MySQL` table structure used in the subsequent example code. +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` datetime DEFAULT NULL, + `updated_at` datetime DEFAULT NULL, + `deleted_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `user_detail` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `address` varchar(45) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +- If you choose to use the time field type, you need to set the field to allow `NULL`, so that soft delete can take effect. +- If you try to test and view the `SQL` statements executed by `ORM` operations, it is recommended to enable `debug` mode (related document: [ORM Senior - Debug Mode](../../ORM高级特性/ORM高级特性-调试模式.md)), and `SQL` statements will be automatically printed to the log output. +::: + +## `created_at` Writing Time + +This time is automatically written when executing the `Insert/InsertIgnore/BatchInsert/BatchInsertIgnore` methods, and subsequent update/delete operations will not change the content of the `created_at` field. +:::warning +It should be noted that the `Replace` method will also update this field, as this operation is equivalent to deleting the existing old data and writing a new record. +::: +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`) VALUES('john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10001,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`) +g.Model("user").Data(g.Map{"id": 10001, "name": "john"}).Save() +``` + +## `deleted_at` Data Soft Delete + +When soft delete exists (i.e., when the `deleted_at` field exists), all query statements will automatically add the `deleted_at` condition. + +```go +// UPDATE `user` SET `deleted_at`='2020-06-06 21:00:00' WHERE id=10 AND `deleted_at` IS NULL +g.Model("user").Where("id", 10).Delete() +``` + +Some changes occur during the query, for example: + +```go +// SELECT * FROM `user` WHERE id>1 AND `deleted_at` IS NULL +g.Model("user").Where("id>?", 1).All() +``` + +As you can see, when the `deleted_at` field exists in the data table, all query operations involving that table will automatically add the condition `deleted_at IS NULL`. + +## `updated_at` Updating Time + +This time is automatically written when executing the `Save/Update` methods. +:::warning +It should be noted that the `Replace` method will also update this field, as this operation is equivalent to deleting the existing old data and writing a new record. +::: +```go +// UPDATE `user` SET `name`='john guo',`updated_at`='2020-06-06 21:00:00' WHERE name='john' AND `deleted_at` IS NULL +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() + +// UPDATE `user` SET `status`=1,`updated_at`='2020-06-06 21:00:00' WHERE `deleted_at` IS NULL ORDER BY `id` ASC LIMIT 10 +g.Model("user").Data("status", 1).OrderAsc("id").Limit(10).Update() + +// INSERT INTO `user`(`id`,`name`,`update_at`) VALUES(1,'john guo','2020-12-29 20:16:14') ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`update_at`=VALUES(`update_at`) +g.Model("user").Data(g.Map{"id": 1, "name": "john guo"}).Save() +``` + +## Scenarios of Join Query + +If several tables involved in the join query have enabled the soft delete feature, the following situation will occur, that is, the conditional statement will include the soft delete time checks of all related tables. + +```go +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE (`u`.`id`=10) AND `u`.`deleted_at` IS NULL LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).One() +``` + +## `Unscoped` Ignoring Time Features + +`Unscoped` is used to ignore automatic time update features in chained operations. For example, in the above example, after adding the `Unscoped` method: + +```go +// SELECT * FROM `user` WHERE id>1 +g.Model("user").Unscoped().Where("id>?", 1).All() + +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE u.id=10 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).Unscoped().One() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" new file mode 100644 index 00000000000..513bde1b860 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-bool-fields' +title: 'Time Fields - Boolean Fields' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Time Fields,Boolean Fields,ORM Component,Auto Recognition,MySQL Table Structure,Soft Delete,deleted_at,Debug Mode] +description: "Introduces support for Boolean fields in time fields of the GoFrame framework, demonstrating through examples how to use a Boolean type 'deleted_at' field for data soft deletion. Provides MySQL table structure definition and examples of creating records and performing soft delete operations using the ORM component in GoFrame." +--- + +Starting from version `v2.8`, if the time fields `created_at`, `updated_at`, and `deleted_at` are Boolean fields, the ORM component will automatically recognize and support them, writing Boolean type values (represented by `0` and `1`). Typically, the Boolean field is the `deleted_at` field, and we only demonstrate the case where the `deleted_at` field is of type `bool`. + +## Example SQL +This is the `MySQL` table structure used in the example code that follows. + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` int(10) unsigned DEFAULT NULL, + `updated_at` int(10) unsigned DEFAULT NULL, + `deleted_at` bit(1) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +We recommend using `bit(1)` to represent the `bool` type for the field instead of `tinyint(1)` or `int(1)` because the range of `tinyint(1)/int(1)` is `-127~127`, which is often used as a status field type. The range of `bit(1)` is `0/1`, which can effectively represent the two values `false/true` of the `bool` type. + +:::tip +If you try to test and view the `ORM` operation execution SQL statements, it is recommended to enable the `debug` mode (related documentation: [ORM Senior - Debug Mode](../../ORM高级特性/ORM高级特性-调试模式.md)), and the SQL statements will be automatically printed to the log output. +::: + +## `created_at` Write Time + +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731481488,1731481488,0) +g.Model("user").Data(g.Map{"name": "john"}).Insert() +``` + +## `deleted_at` Data Soft Deletion + +```go +// UPDATE `user` SET `deleted_at`=1 WHERE (`id`=10) AND `deleted_at`=0 +g.Model("user").Where("id", 10).Delete() +``` + +Some changes occur during the query, for example: + +```go +// SELECT * FROM `user` WHERE (id>1) AND `deleted_at`=0 +g.Model("user").Where("id>?", 1).All() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" new file mode 100644 index 00000000000..eafe35db944 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" @@ -0,0 +1,106 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-numeric-fields' +title: 'Time Fields - Integer Fields' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Time Maintenance, Integer Fields, created_at, updated_at, deleted_at, Soft Delete, ORM Component, Insert, Update] +description: "If time fields such as created_at, updated_at, and deleted_at are integer fields, GoFrame's ORM component will automatically recognize and write second-level timestamps. During insertion, created_at is automatically updated, but updates and deletions do not change created_at. The Replace method will update all time fields. In the case of soft deletion, all queries automatically include the condition deleted_at=0." +--- + +Since version `v2.8`, if the time fields `created_at`, `updated_at`, and `deleted_at` are integer fields, the ORM component will automatically recognize and support them, writing **second-level timestamp** values. + +## Example SQL + +This is the `MySQL` table structure used in the subsequent example code. + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` int(10) unsigned DEFAULT NULL, + `updated_at` int(10) unsigned DEFAULT NULL, + `deleted_at` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `user_detail` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `address` varchar(45) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +If you try to test viewing the `SQL` statements executed by `ORM` operations, it's recommended to enable `debug` mode (related documentation: [ORM Senior - Debug Mode](../../ORM高级特性/ORM高级特性-调试模式.md)), the `SQL` statements will be automatically printed to log output. +::: + +## `created_at` Writing Time + +When executing the `Insert/InsertIgnore/BatchInsert/BatchInsertIgnore` methods, this time is automatically written. Subsequent update/delete operations will not change the content of the `created_at` field. + +:::warning +It should be noted that the `Replace` method will also update this field because this operation is equivalent to deleting the existing old data and writing a new data record. +::: +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731481488,1731481488,0) +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10000,'john',1731481518,1731481518,0) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10000,'john',1731481747,1731481747,0) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10001,'john',1731481766,1731481766,0) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`),`deleted_at`=VALUES(`deleted_at`) +g.Model("user").Data(g.Map{"id": 10001, "name": "john"}).Save() +``` + +## `deleted_at` Soft Delete + +When soft deletion is present (i.e., the `deleted_at` field exists), all query statements will automatically add conditions for `deleted_at`. + +```go +// UPDATE `user` SET `deleted_at`=1731481948 WHERE (`id`=10) AND `deleted_at`=0 +g.Model("user").Where("id", 10).Delete() +``` + +Changes occur when querying: + +```go +// SELECT * FROM `user` WHERE (id>1) AND `deleted_at`=0 +g.Model("user").Where("id>?", 1).All() +``` + +You can see that when the `deleted_at` field exists in the data table, all query operations involving the table automatically add the condition `deleted_at=0`. + +## `updated_at` Update Time + +When executing the `Save/Update` methods, this time is automatically written. It should be noted that the `Replace` method will also update this field because it is equivalent to deleting the existing old data and writing a new data record. + +:::info +If the `deleted_at` soft delete field also exists, the update operation statement will also contain the `deleted_at` condition. +::: +```go +// UPDATE `user` SET `name`='john guo',`updated_at`=1731481821 WHERE (`name`='john') AND `deleted_at`=0 +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() + +// UPDATE `user` SET `status`=1,`updated_at`=1731481895 WHERE `deleted_at`=0 ORDER BY `id` ASC LIMIT 10 +g.Model("user").Data("status", 1).OrderAsc("id").Limit(10).Update() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(1,'john guo',1731481915,1731481915,0) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`),`deleted_at`=VALUES(`deleted_at`) +g.Model("user").Data(g.Map{"id": 1, "name": "john guo"}).Save() +``` + +## Scenarios of Joint Table Queries + +If several tables involved in an associated query have enabled the soft delete feature, the following situation will occur, where all relevant tables will have soft deletion time judgment added to the condition statements. + +```go +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE (`u`.`id`=10) AND `u`.`deleted_at`=0 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).One() +``` + +## Control the Granularity of Time Writing + +The time field value writing in this chapter is by default in seconds-level timestamp. But if we want to control the granularity of time writing such as writing a millisecond-level timestamp, how do we do it? We can use [Time Fields - SoftTimeOption](./时间维护-SoftTimeOption.md). \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" new file mode 100644 index 00000000000..89dffaef9de --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/core/gdb-chaining-update-delete' +title: 'ORM Model - Update/Delete' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, ORM chaining operations, data update, data delete, Counter feature, increment operation, decrement operation, RawSQL, soft delete] +description: "Performing ORM chaining operations for data update and delete in the GoFrame framework. Emphasizes the importance of using the Update and Delete methods with Where conditions. Additionally, explores the feature of using Counter parameters to increase or decrease field values, and using the Increment and Decrement methods for field operations. It also explains techniques for embedding native SQL statements and implementing soft delete to ensure flexibility and security in data processing." +--- +:::warning +For security assurance and to prevent mistakes, the `Update` and `Delete` methods must have `Where` conditions to be executed; otherwise, an error will return with a message like: `there should be WHERE condition statement for XXX operation`. `goframe` is an enterprise-grade framework with rigorous module design and detailed handling of engineering practices. +::: +## `Update` Method + +`Update` is used for data updates and often needs to be used in conjunction with `Data` and `Where` methods. The `Data` method specifies the data to be updated, while the `Where` method specifies the condition range for the update. The `Update` method also supports directly passing data and condition parameters. + +Example usage: + +```go +// UPDATE `user` SET `name`='john guo' WHERE name='john' +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() +g.Model("user").Data("name='john guo'").Where("name", "john").Update() + +// UPDATE `user` SET `status`=1 WHERE `status`=0 ORDER BY `login_time` asc LIMIT 10 +g.Model("user").Data("status", 1).Order("login_time asc").Where("status", 0).Limit(10).Update() + +// UPDATE `user` SET `status`=1 WHERE 1 +g.Model("user").Data("status=1").Where(1).Update() +g.Model("user").Data("status", 1).Where(1).Update() +g.Model("user").Data(g.Map{"status" : 1}).Where(1).Update() +``` + +You can also directly pass `data` and `where` parameters to the `Update` method: + +```go +// UPDATE `user` SET `name`='john guo' WHERE name='john' +g.Model("user").Update(g.Map{"name" : "john guo"}, "name", "john") +g.Model("user").Update("name='john guo'", "name", "john") + +// UPDATE `user` SET `status`=1 WHERE 1 +g.Model("user").Update("status=1", 1) +g.Model("user").Update(g.Map{"status" : 1}, 1) +``` + +## `Counter` Update Feature + +You can use the `Counter` type parameter to numerically operate on specific fields, such as increasing and decreasing. + +`Counter` data structure definition: + +```go +// Counter is the type for update count. +type Counter struct { + Field string + Value float64 +} +``` + +Example using `Counter` for field increment: + +```go +updateData := g.Map{ + "views": &gdb.Counter{ + Field: "views", + Value: 1, + }, +} +// UPDATE `article` SET `views`=`views`+1 WHERE `id`=1 +result, err := db.Update("article", updateData, "id", 1) +``` + +`Counter` can also be used for incrementing by a non-own field, for example: + +```go +updateData := g.Map{ + "views": &gdb.Counter{ + Field: "clicks", + Value: 1, + }, +} +// UPDATE `article` SET `views`=`clicks`+1 WHERE `id`=1 +result, err := db.Update("article", updateData, "id", 1) +``` + +## `Increment/Decrement` + +We can use the `Increment` and `Decrement` methods to perform commonly used operations for incrementing/decrementing specified fields. The definitions for the two methods are as follows: + +```go +// Increment increments a column's value by a given amount. +func (m *Model) Increment(column string, amount float64) (sql.Result, error) + +// Decrement decrements a column's value by a given amount. +func (m *Model) Decrement(column string, amount float64) (sql.Result, error) +``` + +Example usage: + +```go +// UPDATE `article` SET `views`=`views`+10000 WHERE `id`=1 +g.Model("article").Where("id", 1).Increment("views", 10000) +// UPDATE `article` SET `views`=`views`-10000 WHERE `id`=1 +g.Model("article").Where("id", 1).Decrement("views", 10000) +``` + +## `RawSQL` Statement Embedding + +`gdb.Raw` is a string type, and the parameter of this type will be directly embedded as an `SQL` fragment into the SQL statement submitted to the underlying layer. It will not be automatically converted to a string parameter type, nor treated as a pre-processing parameter. For more detailed introduction, please refer to the chapter: [ORM Senior - RawSQL](../ORM高级特性/ORM高级特性-RawSQL.md). For example: + +```go +// UPDATE `user` SET login_count='login_count+1',update_time='now()' WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": "login_count+1", + "update_time": "now()", +}).Where("id", 1).Update() +// Execution error: Error Code: 1136. Column count doesn't match value count at row 1 +``` + +Revised with `gdb.Raw`: + +```go +// UPDATE `user` SET login_count=login_count+1,update_time=now() WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": gdb.Raw("login_count+1"), + "update_time": gdb.Raw("now()"), +}).Where("id", 1).Update() +``` + +## `Delete` Method + +The `Delete` method is used for data deletion. + +Example usage: + +```go +// DELETE FROM `user` WHERE uid=10 +g.Model("user").Where("uid", 10).Delete() +// DELETE FROM `user` ORDER BY `login_time` asc LIMIT 10 +g.Model("user").Order("login_time asc").Limit(10).Delete() +``` + +You can also directly pass `where` parameters to the `Delete` method: + +```go +// DELETE FROM `user` WHERE `uid`=10 +g.Model("user").Delete("uid", 10) +// DELETE FROM `user` WHERE `score`<60 +g.Model("user").Delete("score < ", 60) +``` + +## Soft Delete Feature + +For soft delete feature details, please refer to the chapter: [ORM Model - Time Fields](ORM链式操作-时间维护/ORM链式操作-时间维护.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..c16e057fc87 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" @@ -0,0 +1,165 @@ +--- +slug: '/docs/core/gdb-chaining-query-cache' +title: 'ORM Model - Query Cache' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, ORM, query cache, chained operations, cache management, Redis, database, cache clearing, cache adaptation, table structure] +description: "Performing query cache operations using the ORM in the GoFrame framework. It supports caching to optimize query results, suitable for scenarios with more reads and fewer writes. The article provides a detailed introduction to cache management and adaptation, especially how to implement distributed caching using Redis. It also provides sample code showing table structures and their caching effects, demonstrating query cache implementation and cache clearing functions." +--- + +## Query Cache + +`gdb` supports caching query results, which is commonly used in scenarios involving more reads and fewer writes. It also allows for manual cache clearing. Note that query caching only supports chained operations and is not available within a transaction. + +Related methods: + +```go +type CacheOption struct { + // Duration is the TTL for the cache. + // If the parameter `Duration` < 0, which means it clears the cache with given `Name`. + // If the parameter `Duration` = 0, which means it never expires. + // If the parameter `Duration` > 0, which means it expires after `Duration`. + Duration time.Duration + + // Name is an optional unique name for the cache. + // The Name is used to bind a name to the cache, which means you can later control the cache + // like changing the `duration` or clearing the cache with specified Name. + Name string + + // Force caches the query result whatever the result is nil or not. + // It is used to avoid Cache Penetration. + Force bool +} + +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// Note that, the cache feature is disabled if the model is performing select statement +// on a transaction. +func (m *Model) Cache(option CacheOption) *Model +``` + +## Cache Management + +### Cache Object + +By default, the `ORM` object provides a cache management object, and this cache object type is `*gcache.Cache`. This means it also supports all the features of `*gcache.Cache`. You can obtain the cache object through the `GetCache() *gcache.Cache` method and use the returned object to perform various custom cache operations, such as `g.DB().GetCache().Keys()`. + +### Cache Adaptation (Redis Caching) + +By default, the `*gcache.Cache` cache object of `ORM` provides single-process memory caching, which is highly efficient but can only be used in a single process. If the service is deployed on multiple nodes, caches between nodes may be inconsistent, so in most scenarios, we implement database query caching through a Redis server. The `*gcache.Cache` object adopts an adapter design pattern, allowing easy switching from single-process memory caching to distributed Redis caching. Example use: + +```go +redisCache := gcache.NewAdapterRedis(g.Redis()) +g.DB().GetCache().SetAdapter(redisCache) +``` + +For more information, refer to: [Caching - Redis](../../缓存管理/缓存管理-Redis缓存.md) + +### Management Methods + +To simplify database query cache management, starting from version `v2.2.0`, two cache management methods are provided: + +```go +// ClearCache removes cached sql result of certain table. +func (c *Core) ClearCache(ctx context.Context, table string) (err error) + +// ClearCacheAll removes all cached sql result from cache +func (c *Core) ClearCacheAll(ctx context.Context) (err error) +``` + +The methods are explained in the comments. These methods are mounted on the `Core` object, and the underlying `Core` object is already exposed through the `DB` interface, allowing us to obtain the `Core` object like this: + +```go +g.DB().GetCore() +``` + +## Usage Example + +### Table Structure + +```sql +CREATE TABLE `user` ( + `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(30) NOT NULL DEFAULT '' COMMENT 'Nickname', + `site` varchar(255) NOT NULL DEFAULT '' COMMENT 'Homepage', + PRIMARY KEY (`uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +### Sample Code + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + db = g.DB() + ctx = gctx.New() + ) + + // Enable debug mode to record all executed SQL + db.SetDebug(true) + + // Insert test data + _, err := g.Model("user").Ctx(ctx).Data(g.Map{ + "name": "john", + "site": "https://goframe.org", + }).Insert() + + // Perform the query twice and cache the result for 1 hour with an optional cache name + for i := 0; i < 2; i++ { + r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "vip-user", + Force: false, + }).Where("uid", 1).One() + g.Log().Debug(ctx, r.Map()) + } + + // Perform an update operation and clear the query cache with a specified name + _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: "vip-user", + Force: false, + }).Data(gdb.Map{"name": "smith"}).Where("uid", 1).Update() + if err != nil { + g.Log().Fatal(ctx, err) + } + + // Execute the query again enabling the query cache feature + r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "vip-user", + Force: false, + }).Where("uid", 1).One() + g.Log().Debug(ctx, r.Map()) +} +``` + +The output after execution is (the test table data structure is for reference only): + +```html +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"john","site":"https://goframe.org","uid":1} +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"john","site":"https://goframe.org","uid":1} +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} [ 0 ms] [default] [rows:1 ] UPDATE `user` SET `name`='smith' WHERE `uid`=1 +2022-02-08 17:36:19.818 [DEBU] {c0424c75f1c5d116d0df0f7197379412} [ 1 ms] [default] [rows:1 ] SELECT * FROM `user` WHERE `uid`=1 LIMIT 1 +2022-02-08 17:36:19.818 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"smith","site":"https://goframe.org","uid":1} +``` + +It can be observed that: + +1. To demonstrate cache effects conveniently, data `debug` feature is enabled, so any SQL operation will be output to the terminal. +2. The `One` method is executed twice for data querying. The first time an SQL query is executed, the second time it directly uses the cache without submitting the SQL to the database, hence only one query SQL is printed, and the results of both queries are consistent. +3. Note that a custom name `vip-user` is set for caching the query, facilitating subsequent cache clearing upon update. If cache clearing is not needed, a cache name does not need to be set. +4. The cache with a specified name is cleared when the `Update` operation is executed. +5. The query is executed again to re-cache the new data. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" new file mode 100644 index 00000000000..75f59e04fbb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-chaining-relation' +title: 'ORM Model - Association' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame framework, ORM, chaining operations, model association, gdb, database, data handling, development framework, documentation] +description: "Use ORM chaining operations in the GoFrame framework to implement model associations. Through detailed examples and explanations, help developers understand and apply the database processing capabilities in GoFrame, thereby improving development efficiency and facilitating the easy management of complex data relationships." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" new file mode 100644 index 00000000000..f48cf93cad7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" @@ -0,0 +1,239 @@ +--- +slug: '/docs/core/gdb-chaining-relation-scan-list' +title: 'Model Association - ScanList' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Dynamic Association, ScanList, Data Insertion, Data Query, Data Model, Transaction Handling, Binding Data, Association, Data Structure] +description: "How the GoFrame framework handles model association design by simplifying tag content and association attributes, reducing cognitive load. It provides a detailed explanation of how to use the ScanList method for dynamic association of data structures, demonstrating 1:1 and 1:N relationships between users, user details, and user credits, and provides sample Go code for data queries and insertion operations, including implementation methods for transaction handling and data binding." +--- + +The `ORM` of `gf` does not adopt common model association designs such as `BelongsTo`, `HasOne`, `HasMany`, `ManyToMany` found in other `ORMs`. Such association maintenance is quite cumbersome, involving foreign key constraints, additional tag annotations, etc., imposing a cognitive burden on developers. Therefore, the `gf` framework doesn't favor injecting complex tag content, association attributes, or methods into model structs. It consistently strives to simplify the design, aiming to make model association queries as easy to understand and useful as possible. + +:::warning +The following implementation of model association provided by `gf ORM` is available from `GoFrame v1.13.6` and is currently an experimental feature. +::: + +Let's introduce the model association provided by `gf ORM` with an example. + +### Data Structure + +To simplify the example, the tables we design here are as simple as possible, with each table containing only 3-4 fields to conveniently explain the associations. + +``` +# User Table +CREATE TABLE `user` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# User Details +CREATE TABLE `user_detail` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# User Credits +CREATE TABLE `user_scores` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + course varchar(45) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +``` + +### Data Model + +Based on the table definition, we can infer: + +1. The user table and user details have a `1:1` relationship. +2. The user table and user credits have a `1:N` relationship. +3. We don't demonstrate the `N:N` relationship here, as it is similar to `1:N` in terms of needing only one more association or query, with a similar final processing method. + +The model in Golang can be defined as follows: + +```go +// User Table +type EntityUser struct { + Uid int `orm:"uid"` + Name string `orm:"name"` +} +// User Details +type EntityUserDetail struct { + Uid int `orm:"uid"` + Address string `orm:"address"` +} +// User Credits +type EntityUserScores struct { + Id int `orm:"id"` + Uid int `orm:"uid"` + Score int `orm:"score"` + Course string `orm:"course"` +} +// Composite Model, User Information +type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores +} +``` + +Here, `EntityUser`, `EntityUserDetail`, and `EntityUserScores` correspond to the data models for user table, user details, and user credits, respectively. `Entity` is a composite model representing all detailed information of a user. + +### Data Insertion + +Data insertion involves simple database transactions. + +```go + err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + r, err := tx.Model("user").Save(EntityUser{ + Name: "john", + }) + if err != nil { + return err + } + uid, err := r.LastInsertId() + if err != nil { + return err + } + _, err = tx.Model("user_detail").Save(EntityUserDetail{ + Uid: int(uid), + Address: "Beijing DongZhiMen #66", + }) + if err != nil { + return err + } + _, err = tx.Model("user_scores").Save(g.Slice{ + EntityUserScores{Uid: int(uid), Score: 100, Course: "math"}, + EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"}, + }) + return err + }) +``` + +### Data Query + +#### Single Data Record + +Querying a single model data is straightforward using the `Scan` method, which automatically identifies whether to bind query results to a single object attribute or an array object attribute. For example: + +```go +// Define User List +var user Entity +// Query User Basic Data +// SELECT * FROM `user` WHERE `name`='john' +err := g.Model("user").Scan(&user.User, "name", "john") +if err != nil { + return err +} +// Query User Detail Data +// SELECT * FROM `user_detail` WHERE `uid`=1 +err := g.Model("user_detail").Scan(&user.UserDetail, "uid", user.User.Uid) +// Query User Credits Data +// SELECT * FROM `user_scores` WHERE `uid`=1 +err := g.Model("user_scores").Scan(&user.UserScores, "uid", user.User.Uid) +``` + +This method has been introduced in previous sections, so I won't repeat it here. + +#### Multiple Data Records + +To query multiple data records and bind the data to the data model array, you need to use the `ScanList` method. This method requires you to specify the relationship between the result fields and the model attributes, then iterates over the array and automatically binds the data. For example: + +```go +// Define User List +var users []Entity +// Query User Basic Data +// SELECT * FROM `user` +err := g.Model("user").ScanList(&users, "User") +// Query User Detail Data +// SELECT * FROM `user_detail` WHERE `uid` IN(1,2) +err := g.Model("user_detail"). + Where("uid", gdb.ListItemValuesUnique(users, "User", "Uid")). + ScanList(&users, "UserDetail", "User", "uid:Uid") +// Query User Credits Data +// SELECT * FROM `user_scores` WHERE `uid` IN(1,2) +err := g.Model("user_scores"). + Where("uid", gdb.ListItemValuesUnique(users, "User", "Uid")). + ScanList(&users, "UserScores", "User", "uid:Uid") +``` + +This involves two important methods: + +**1\. `ScanList`** + +Method definition: + +```go +// ScanList converts to struct slice which contains other complex struct attributes. +// Note that the parameter should be type of *[]struct/*[]*struct. +// Usage example: +// +// type Entity struct { +// User *EntityUser +// UserDetail *EntityUserDetail +// UserScores []*EntityUserScores +// } +// var users []*Entity +// or +// var users []Entity +// +// ScanList(&users, "User") +// ScanList(&users, "UserDetail", "User", "uid:Uid") +// ScanList(&users, "UserScores", "User", "uid:Uid") +// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct +// that current result will be bound to. +// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational +// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given +// parameter. +// See the example or unit testing cases for clear understanding for this function. +func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) +``` + +This method is used to bind the queried array data to the specified list, for example: + +- `ScanList(&users, "User")` + +Indicates binding the queried user information array data to the `User` attribute of each item in the `users` list. + +- `ScanList(&users, "UserDetail", "User", "uid:Uid")` + +Indicates binding the queried user detail array data to the `UserDetail` attribute of each item in the `users` list, and associating with another `User` object attribute through `uid:Uid` field:attribute relation. Internally, this will automatically handle data binding based on this association. Here `uid:Uid` specifies the `uid` field in the query result and `Uid` denotes the `Uid` attribute in the target associated object. + +- `ScanList(&users, "UserScores", "User", "uid:Uid")` + +Indicates binding the queried user detail array data to the `UserScores` attribute of each item in the `users` list, and associating with another `User` object attribute through `uid:Uid` field:attribute relation. Internally, this method can automatically recognize that `User` to `UserScores` is essentially a `1:N` relation due to `UserScores` being an array type `[]*EntityUserScores`, and thus completes data binding. + +Do note, if the corresponding association attribute data does not exist within the associated data, the attribute will not be initialized and will remain `nil`. + +**2\. `ListItemValues/ListItemValuesUnique`** + +Method definition: + +```go +// ListItemValues retrieves and returns the elements of all item struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// +// The parameter supports types like: +// []map[string]interface{} +// []map[string]sub-map +// []struct +// []struct:sub-struct +// Note that the sub-map/sub-struct makes sense only if the optional parameter is given. +func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) + +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// See gutil.ListItemValuesUnique. +func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} +``` + +The difference between `ListItemValuesUnique` and `ListItemValues` is that the former filters out duplicate return values, ensuring the returned list does not contain duplicates. These functions are used to obtain specified attribute/key values from elements in a given list containing `struct`/`map` data items, constructing them into a `[]interface{}` array to return. Example: + +- `gdb.ListItemValuesUnique(users, "Uid")` is used to obtain every `Uid` attribute from the `users` array, constructing it into a `[]interface{}` array to return. This facilitates constructing a `SELECT...IN...` query based on `uid`. +- `gdb.ListItemValuesUnique(users, "User", "Uid")` is used to obtain every `Uid` attribute from the `User` property item in the `users` array, constructing it into a `[]interface{}` array to return. This facilitates constructing a `SELECT...IN...` query based on `uid`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..c2fbea7e6a9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" @@ -0,0 +1,737 @@ +--- +slug: '/docs/core/gdb-chaining-relation-with' +title: 'Model Association - With' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, With Feature, ORM, Model Association, Data Query, Transaction Operation, Data Structure, Go Language, Database, SQL] +description: "The With feature in the GoFrame framework demonstrates how to achieve model association and data queries through examples. It introduces data structure definition, transaction operations, data writing and querying, helping developers better understand and use the GoFrame framework for efficient development." +--- + +## 1. Design Background + +Everyone knows that usability and maintainability have always been the focus of `goframe`, and it's also a significant difference between `goframe` and other frameworks and components. `goframe` does not adopt other common `ORM` model association designs like `BelongsTo`, `HasOne`, `HasMany`, `ManyToMany`, which are cumbersome to maintain due to foreign key constraints, additional tag annotations, etc., imposing a certain cognitive load on developers. Therefore, the framework is inclined not to inject overly complex tag content, associated attributes, or methods into model structures and consistently tries to simplify the design with the goal of making model association queries as understandable and easy to use as possible. Before learning more about the `With` feature, it is recommended to first understand [Model Association - ScanList](模型关联-动态关联-ScanList.md). + +Through a series of project practices, we found that although `ScanList` maintains model associations from a runtime business logic perspective, this association maintenance is not as straightforward as expected. Therefore, we continue to improve and introduce the `With` model association feature, which can easily maintain the association relationships through models. Of course, this feature is still dedicated to enhancing the usability and maintainability of the overall framework, and it can be seen as a combination and improvement of `ScanList` and model association maintenance. + +:::warning +The `With` feature is currently experimental. +::: + +## 2. An Example + +Let's start with a simple example to help better understand the `With` feature, which is an improved version of the same example from the previous `ScanList` section. + +### 1. Data Structure + +```sql +# User Table +CREATE TABLE `user` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# User Detail +CREATE TABLE `user_detail` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# User Scores +CREATE TABLE `user_scores` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +### 2. Data Structure + +Based on the table definitions, we can tell: + +1. The user table and user details have a `1:1` relationship. +2. The user table and user scores have a `1:N` relationship. +3. We did not demonstrate a `N:N` relationship here because, compared to a `1:N` query, it's just an additional association or one more query, and the final processing method is similar to `1:N`. + +The `Golang` model can be defined as follows: + +```go +// User Detail +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +// User Scores +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} +// User Information +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 3. Data Insertion + +To simplify the example, we create `5` user records, using transactional operations: + +- User information, `id` ranges from `1-5`, `name` ranges from `name_1` to `name_5`. +- Simultaneously create `5` user detail records, where `address` data ranges from `address_1` to `address_5`. +- Each user has `5` score entries, scoring `1-5`. + +```go +g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := g.Model(user).Data(user).OmitEmpty().InsertAndGetId() + if err != nil { + return err + } + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = g.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + if err != nil { + return err + } + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScores{ + Uid: int(lastInsertId), + Score: j, + } + _, err = g.Model(userScore).Data(userScore).OmitEmpty().Insert() + if err != nil { + return err + } + } + } + return nil +}) +``` + +After execution, the database data is as follows: + +```text +mysql> show tables; ++----------------+ +| Tables_in_test | ++----------------+ +| user | +| user_detail | +| user_score | ++----------------+ +3 rows in set (0.01 sec) + +mysql> select * from `user`; ++----+--------+ +| id | name | ++----+--------+ +| 1 | name_1 | +| 2 | name_2 | +| 3 | name_3 | +| 4 | name_4 | +| 5 | name_5 | ++----+--------+ +5 rows in set (0.01 sec) + +mysql> select * from `user_detail`; ++-----+-----------+ +| uid | address | ++-----+-----------+ +| 1 | address_1 | +| 2 | address_2 | +| 3 | address_3 | +| 4 | address_4 | +| 5 | address_5 | ++-----+-----------+ +5 rows in set (0.00 sec) + +mysql> select * from `user_score`; ++----+-----+-------+ +| id | uid | score | ++----+-----+-------+ +| 1 | 1 | 1 | +| 2 | 1 | 2 | +| 3 | 1 | 3 | +| 4 | 1 | 4 | +| 5 | 1 | 5 | +| 6 | 2 | 1 | +| 7 | 2 | 2 | +| 8 | 2 | 3 | +| 9 | 2 | 4 | +| 10 | 2 | 5 | +| 11 | 3 | 1 | +| 12 | 3 | 2 | +| 13 | 3 | 3 | +| 14 | 3 | 4 | +| 15 | 3 | 5 | +| 16 | 4 | 1 | +| 17 | 4 | 2 | +| 18 | 4 | 3 | +| 19 | 4 | 4 | +| 20 | 4 | 5 | +| 21 | 5 | 1 | +| 22 | 5 | 2 | +| 23 | 5 | 3 | +| 24 | 5 | 4 | +| 25 | 5 | 5 | ++----+-----+-------+ +25 rows in set (0.00 sec) +``` + +### 4. Data Query + +With the new `With` feature, data querying is quite straightforward. For example, to query one record: + +```go +// Redefine it to avoid scrolling +// User Detail +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +// User Scores +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} +// User Information +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} + +var user *User +// WithAll will query fields with with tags, in this example, it will query tables corresponding to the UserDetail and UserScores structures +g.Model(tableUser).WithAll().Where("id", 3).Scan(&user) +``` + +The above statement will query information for a user with ID `3`, including user information, user details, and user score information. The above statement will automatically execute the following `SQL` statements in the database: + +```text +2021-05-02 22:29:52.634 [DEBU] [ 2 ms] [default] SHOW FULL COLUMNS FROM `user` +2021-05-02 22:29:52.635 [DEBU] [ 1 ms] [default] SELECT * FROM `user` WHERE `id`=3 LIMIT 1 +2021-05-02 22:29:52.636 [DEBU] [ 1 ms] [default] SHOW FULL COLUMNS FROM `user_detail` +2021-05-02 22:29:52.637 [DEBU] [ 1 ms] [default] SELECT `uid`,`address` FROM `user_detail` WHERE `uid`=3 LIMIT 1 +2021-05-02 22:29:52.643 [DEBU] [ 6 ms] [default] SHOW FULL COLUMNS FROM `user_score` +2021-05-02 22:29:52.644 [DEBU] [ 0 ms] [default] SELECT `id`,`uid`,`score` FROM `user_score` WHERE `uid`=3 +``` + +After execution, the information printed by `g.Dump(user)` is as follows: + +```js +{ + Id: 3, + Name: "name_3", + UserDetail: { + Uid: 3, + Address: "address_3", + }, + UserScores: [ + { + Id: 11, + Uid: 3, + Score: 1, + }, + { + Id: 12, + Uid: 3, + Score: 2, + }, + { + Id: 13, + Uid: 3, + Score: 3, + }, + { + Id: 14, + Uid: 3, + Score: 4, + }, + { + Id: 15, + Uid: 3, + Score: 5, + }, + ], +} +``` + +### 5. List Query + +Let's see an example of querying a list through the `With` feature: + +```go +var users []*User +// With(UserDetail{}) only queries the table corresponding to UserDetail in the User structure +g.Model(users).With(UserDetail{}).Where("id>?", 3).Scan(&users) +``` + +After execution, the data printed by `g.Dump(users)` is as follows: + +```js +[ + { + Id: 4, + Name: "name_4", + UserDetail: { + Uid: 4, + Address: "address_4", + }, + UserScores: [], + }, + { + Id: 5, + Name: "name_5", + UserDetail: { + Uid: 5, + Address: "address_5", + }, + UserScores: [], + }, +] +``` + +### 6. Conditions and Sorting + +When associating with the `With` feature, additional association conditions can be specified, as well as sorting rules for multiple data results. For example: + +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"` + UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"` +} +``` + +Use the `where` sub-tag and `order` sub-tag in the `orm` tag to specify additional association conditions and sorting rules. + +### 7. `unscoped` Tag + +The `with` struct tag supports the `unscoped` feature, for example: + +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, unscoped:true"` + UserScores []*UserScore `orm:"with:uid=id, unscoped:true"` +} +``` + +## 3. Detailed Explanation + +You might be curious about some of the usages above, such as the `gmeta` package, the `WithAll` method, the `with` statement in the `orm` tag, and the `Model` method's struct parameter recognizing table names, etc. That's right, let's talk about them in detail. + +### 1. `gmeta` Package + +In the above data structures, you can see a `g.Meta` struct is embedded in an `embed` way, like: + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +``` + +Within the `GoFrame` framework, there are many such small component packages for implementing specific functions. The `gmeta` package is mainly used to embed into user-defined structures, and using tags to mark the struct (like `g.Meta`) in the `gmeta` package with custom tag content (such as `` `orm:"table:user_detail"` ``), and can dynamically obtain these custom tag contents with specific methods at runtime. For more details, refer to the chapter: [Metadata](../../../../组件列表/实用工具/元数据-gmeta.md). + +Therefore, embedding `g.Meta` here is to label the data table name associated with the struct. + +### 2. Model Association Specification + +In the following structure: + +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` +} +``` + +We bind the `orm` tag to the specified struct property, and specify the association relationship between the current struct (table) and the target struct (table) through the `with` statement in the `orm` tag. The syntax of the `with` statement is as follows: + +```text +with:target_table_association_field=current_structure_association_field +``` + +The field names are **case-insensitive and ignore special characters**. For example, the following forms of associations can all be automatically recognized: + +```text +with:UID=ID +with:Uid=Id +with:U_ID=id +``` + +If the association fields of both tables have the same name, you can just write one, such as: + +```text +with:uid +``` + +In this example, the table corresponding to the `UserDetail` property is `user_detail`, and the table corresponding to the `UserScores` property is `user_score`. Both are associated with the `user` table of the current `User` struct using `uid`, and the associated field of the target `user` table is `id`. + +### 3. `With/WithAll` + +#### 1) Introduction + +By default, even if the properties in our struct have `orm` tags with `with` statements, the `ORM` component will not enable the `With` feature for association queries by default. It needs to be enabled by the `With/WithAll` method. + +- `With`: Specify the association query tables by specifying the property objects. +- `WithAll`: Enable association queries for all property structures with `with` statements in the operating object. + +In our example, the `WithAll` method is used, so all property model association queries in the `User` table are automatically enabled. As long as the property struct is associated with a table and the `orm` tag contains a `with` statement, it will automatically query data and bind data according to the model structure association relationship. If we only enable association queries for some properties rather than all property models, we can use the `With` method to specify. And the `With` method can specify multiple associated model automatic queries. The `WithAll` in this example is equivalent to: + +```go +var user *User +g.Model(tableUser).With(UserDetail{}, UserScore{}).Where("id", 3).Scan(&user) +``` + +Or like this: + +```go +var user *User +g.Model(tableUser).With(User{}.UserDetail, User{}.UserScore).Where("id", 3).Scan(&user) +``` + +#### 2) Only Associate User Detail Model + +If we only need to query user details and not user scores, we can use the `With` method to enable association queries for the specified object corresponding tables, such as: + +```go +var user *User +g.Model(tableUser).With(UserDetail{}).Where("id", 3).Scan(&user) +``` + +Or like this: + +```go +var user *User +g.Model(tableUser).With(User{}.UserDetail).Where("id", 3).Scan(&user) +``` + +After execution, the data printed by `g.Dump(user)` is: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": { + "uid": 3, + "address": "address_3" + }, + "UserScores": [] +} +``` + +#### 3) Only Associate User Score Model + +We can also associate and query only user score information, such as: + +```go +var user *User +g.Model(tableUser).With(UserScore{}).Where("id", 3).Scan(&user) +``` + +Or like this: + +```go +var user *User +g.Model(tableUser).With(User{}.UserScore).Where("id", 3).Scan(&user) +``` + +After execution, the data printed by `g.Dump(user)` is: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": null, + "UserScores": [ + { + "id": 11, + "uid": 3, + "score": 1 + }, + { + "id": 12, + "uid": 3, + "score": 2 + }, + { + "id": 13, + "uid": 3, + "score": 3 + }, + { + "id": 14, + "uid": 3, + "score": 4 + }, + { + "id": 15, + "uid": 3, + "score": 5 + } + ] +} +``` + +#### 4) Do Not Associate Any Model Query + +If we do not need any association query, it's simpler, for example: + +```go +var user *User +g.Model(tableUser).Where("id", 3).Scan(&user) +``` + +After execution, the data printed by `g.Dump(user)` is: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": null, + "UserScores": [] +} +``` + +## 4. Usage Restrictions + +### 1. Field Query and Filtering + +As seen in our example above, we have not specified the fields to query, but in the `SQL` logs printed, the query statement is not a simple `SELECT *` but executed concrete field queries. Under the `With` feature, automatic field query mapping according to the associated model object properties will happen, and it will automatically filter out fields that cannot be mapped. + +Therefore, under the `With` feature, we cannot query only some corresponding properties’ fields. To query and assign only specific fields, it is recommended to trim the `model` data structure according to business scenarios and create data structures that meet specific business scenarios, rather than using one data structure to fit multiple different scenarios. + +Let's use an example for better illustration. Suppose we have an entity object data structure `Content`, a common content model in a `CMS` system as follows, which corresponds to the fields of the data table: + +```go +type Content struct { + Id uint `orm:"id,primary" json:"id"` // Auto-increment ID + Key string `orm:"key" json:"key"` // Unique key name, generally not commonly used + Type string `orm:"type" json:"type"` // Content model: topic, ask, article, etc., defined by the program + CategoryId uint `orm:"category_id" json:"category_id"` // Category ID + UserId uint `orm:"user_id" json:"user_id"` // User ID + Title string `orm:"title" json:"title"` // Title + Content string `orm:"content" json:"content"` // Content + Sort uint `orm:"sort" json:"sort"` // Sort order, lower value means higher priority, default is the timestamp when added, can be used to pin + Brief string `orm:"brief" json:"brief"` // Summary + Thumb string `orm:"thumb" json:"thumb"` // Thumbnail + Tags string `orm:"tags" json:"tags"` // Tag names list, stored in JSON + Referer string `orm:"referer" json:"referer"` // Content Source, e.g., GitHub/Gitee + Status uint `orm:"status" json:"status"` // Status 0: Normal, 1: Disabled + ReplyCount uint `orm:"reply_count" json:"reply_count"` // Reply count + ViewCount uint `orm:"view_count" json:"view_count"` // View count + ZanCount uint `orm:"zan_count" json:"zan_count"` // Likes + CaiCount uint `orm:"cai_count" json:"cai_count"` // Dislikes + CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` // Creation time + UpdatedAt *gtime.Time `orm:"updated_at" json:"updated_at"` // Update time +} +``` + +The content list page does not need to display such detailed content, especially the `Content` field, which is very large. We only need to query a few fields for the list page. Therefore, we can define a separate data structure for list returns (field trimming) instead of directly using the data table entity object data structure. For example: + +```go +type ContentListItem struct { + Id uint `json:"id"` // Auto-increment ID + CategoryId uint `json:"category_id"` // Category ID + UserId uint `json:"user_id"` // User ID + Title string `json:"title"` // Title + CreatedAt *gtime.Time `json:"created_at"` // Creation time + UpdatedAt *gtime.Time `json:"updated_at"` // Update time +} +``` + +### 2. Must Exist Association Field Property + +The `With` feature is achieved by recognizing data structure associations and automatically executing multiple SQL queries, so associated fields must exist as object properties for automatic retrieval of association field values. Simply put, the fields in the `with` tag must be present in the attributes of the associated object. + +## 5. Recursive Association + +If the associated model properties also have `with` tags, recursive association querying will occur. The `With` feature supports unlimited levels of recursive association. The following example is for reference only: + +```go +// User Detail +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +// User Scores - Required Courses +type UserScoresRequired struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +// User Scores - Elective Courses +type UserScoresOptional struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +// User Scores +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Required []UserScoresRequired `orm:"with:id, where:type=1"` + Optional []UserScoresOptional `orm:"with:id, where:type=2"` +} + +// User Information +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +## 6. Model Examples + +Based on the current data tables, more model writing examples are provided for reference. + +### 1. Nested Associated Models + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + g.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +Nested models also support nesting to allow automatic data assignment for embedded structures, such as: + +```go +type UserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserDetailEmbedded struct { + UserDetail +} + +type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + *UserDetailEmbedded `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 2. Basic Model Nesting + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type UserEmbedded struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type User struct { + g.Meta `orm:"table:user"` + UserEmbedded + *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 3. Models Without `meta` Information + +The `meta` structure in the model is crucial for specifying the table name. When there is no `meta` information, the table name for query will automatically use the `CaseSnake` name of the struct. For example, `UserDetail` will automatically use the `user_detail` table name, and `UserScores` will automatically use the `user_scores` table name. + +```go +type UserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +## 7. Future Improvements + +- Currently, the `With` feature is only implemented for query operations and does not support write or update operations. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" new file mode 100644 index 00000000000..02c43dde3b6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" @@ -0,0 +1,140 @@ +--- +slug: '/docs/core/gdb-chaining-model' +title: 'ORM Model - Model' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Chaining Operations, Model Creation, Model Object, g.Model, Raw Method, Chaining Safety, Clone Method, Safe Method] +description: "Using the ORM tool in the GoFrame framework for chaining operations, focusing on model creation, the use of Model objects, and ways to ensure chaining safety. By using the Model and Raw methods, model objects can be created on default and switched database configurations. It also explores the implementation of chaining safety, including default non-chaining safety, Clone method cloning, Safe method setting for chaining safety, and more operational techniques." +--- + +## Model Creation + +### `Model` + +The `Model` method is used to create a `Model` object based on a data table. Commonly, you can also use the `Model` method in the `g` object management module to create a `Model` object on the default database configuration. + +Usage example: + +```go +g.Model("user") +// Or +g.DB().Model("user") +``` + +Additionally, in certain scenarios, you can switch the database object of the current model through the `DB` method, for example: + +```go +m := g.Model("user") +m = m.DB(g.DB("order")) +``` + +This is equivalent to the following operation: + +```go +m := g.DB("user").Model("user") +``` + +### `Raw` + +The `Raw` method is used to create a `Model` object based on a raw `SQL` statement. You can also use the `ModelRaw` method in the `g` object management module to create a `Model` object on the default database configuration given an `SQL` statement. + +```go +s := "SELECT * FROM `user`" +m, _ := g.ModelRaw(s).WhereLT("age", 18).Limit(10).OrderAsc("id").All() +// SELECT * FROM `user` WHERE `age`<18 ORDER BY `id` ASC LIMIT 10 +``` + +```go +s := "SELECT * FROM `user` WHERE `status` IN(?)" +m, _ := g.ModelRaw(s, g.Slice{1,2,3}).WhereLT("age", 18).Limit(10).OrderAsc("id").All() +// SELECT * FROM `user` WHERE `status` IN(1,2,3) AND `age`<18 ORDER BY `id` ASC LIMIT 10 +``` + +## Chaining Safety + +`Chaining Safety` is simply the distinction between two ways of model operations: one modifies the current `model` object (unsafe, default), and one does not (safe) but requires using assignment operations for model property modification/condition stacking, that's all. + +### Default Situation + +By default, `gdb` is `non-chaining safe`, meaning each method of the chaining operation will modify the `Model` properties of the current operation. Hence, the `Model` object **cannot be reused**. For instance, when there are separate query conditions, we can use the `Model` object as follows: + +```go +user := g.Model("user") +user.Where("status", g.Slice{1,2,3}) +if vip { + // Automatically stack query conditions, modifying the current model + user.Where("money>=?", 1000000) +} else { + // Automatically stack query conditions, modifying the current model + user.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := user.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := user.Count() +``` + +As observed, if chaining operations are executed separately, each operation in the chain will modify the existing `Model` object, and the query conditions will automatically stack, so the `user` object cannot be reused; otherwise, conditions will keep stacking. Also, in this usage mode, whenever we need to operate on the `user` table, we must use syntax like `g.DB().Table("user")` to create a new `user` model object, which can be quite cumbersome. + +By default, considering performance and GC optimization, model objects are `non-chaining safe` to prevent creating too many temporary model objects. ![(Smile)](/markdown/1f7ee2ac67fc5de100b8fc690d7438ea.svg) +:::tip +However, it should be noted that if you are using `dao` generated by the cli tool `gen dao`, like `user := dao.User.Ctx(ctx)`, the obtained `user` `Model` object is chaining safe by default (automatically called `.Safe()`). +::: +### `Clone` Method + +Additionally, you can manually invoke the `Clone` method to clone the current model, creating a new model to achieve chaining safety. Since it is a new model object, there is no concern about modifying the existing model object, for example: + +```go +// Define a user model singleton +user := g.Model("user") +``` + +```go +// Clone a new user model +m := user.Clone() +m.Where("status", g.Slice{1,2,3}) +if vip { + m.Where("money>=?", 1000000) +} else { + m.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := m.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := m.Count() +``` + +### `Safe` Method + +Of course, you can set the current model to be `chaining safe` through the `Safe` method, and each subsequent chaining operation will return a new `Model` object that can be reused. However, it is important to note that modifications to model properties or stacking of operation conditions need to be achieved through variable assignments (`m = m.xxx`) to overwrite the original model object, for example: + +```go +// Define a user model singleton +user := g.Model("user").Safe() +``` + +```go +m := user.Where("status", g.Slice{1,2,3}) +if vip { + // Query conditions stacked through assignment + m = m.Where("money>=?", 1000000) +} else { + // Query conditions stacked through assignment + m = m.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := m.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := m.Count() +``` + +As seen, the user model singleton object `user` in the example can be reused without worrying about being "polluted". Under this chaining safety approach, we can create a user singleton object `user` and reuse it in various subsequent queries. However, when there are multiple query conditions, condition stacking needs to be achieved through model assignment operations (`m = m.xxx`). +:::tip +After using the `Safe` method, each chaining operation will create a new temporary model object (using `Clone` internally to achieve model cloning), thereby achieving chaining safety. This usage is quite common in model operations. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..335ed7c67c2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/core/gdb-chaining' +title: 'ORM - Model 🔥' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame Framework, ORM Chaining Operation, gdb, Database Operation, Model Method, Transaction Processing, API Documentation, Database Object, GoFrame, Chaining Invocation] +description: "The chaining operation of gdb in the GoFrame framework, a flexible and officially recommended way to operate the database. Through the db.Model or tx.Model methods, a chained operation object *Model can be returned based on the data table, supporting various database operations such as Replace, Save, InsertGetId, etc., with a detailed explanation of the support status for each database." +--- + +## Introduction + +`gdb` chaining operation is simple and flexible, and is the officially recommended database operation method by the `GoFrame` framework. Chaining operation can be done by using the `db.Model` method of the database object or the `tx.Model` method of the transaction object, which returns a chaining operation object `*Model` based on the specified data table. This object can perform the following methods. The current method list may lag behind the source code; for a detailed method list, please refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Model](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Model) + +## Partially Unsupported Operations + +Below is the support status of the latest version + +| Type | Replace | Save | InsertIgnore | InsertGetId | LastInsertId | Transaction | RowsAffected | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `mysql` | Supported | Supported | Supported | Supported | Supported | Supported | Supported | +| `mariadb` | Supported | Supported | Supported | Supported | Supported | Supported | Supported | +| `tidb` | Supported | Supported | Supported | Supported | Supported | Supported | Supported | +| `pgsql` | Unsupported | Supported | Unsupported | Supported | Supported | Supported | Supported | +| `mssql` | Unsupported | Supported | Supported | Supported | Unsupported | Supported | Supported | +| `sqlite` | Unsupported | Supported | Supported | Supported | Supported | Supported | Supported | +| `oracle` | Unsupported | Supported | Supported | Supported | Unsupported | Supported | Supported | +| `dm` | Unsupported | Supported | Supported | Supported | Supported | Supported | Supported | +| `clickhouse` | Unsupported | Unsupported | Unsupported | Unsupported | Supported | Unsupported | Unsupported | + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" new file mode 100644 index 00000000000..4a7d49158c3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" @@ -0,0 +1,76 @@ +--- +slug: '/docs/core/gdb-senior-raw-sql' +title: 'ORM Senior - RawSQL' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, ORM, RawSQL, SQL injection, gdb.Raw, data insertion, data updating, data querying, prepared mode, SQL statement] +description: "Using the RawSQL feature of ORM in the GoFrame framework, by employing the gdb.Raw type, you can embed custom SQL fragments in generated SQL statements for more flexible database operations. This document provides a detailed explanation of using RawSQL in Insert, Update, and Select operations, along with examples to ensure the security and flexibility of SQL statements." +--- + +Due to the security assurance of `ORM`, all input parameters are executed in a prepared mode at the underlying level to prevent common `SQL` injection risks. In certain scenarios, we expect to embed custom SQL statements in the generated execution SQL statements, then we can use the `RawSQL` feature of `ORM` through the `gdb.Raw` type. Let's look at a few examples. + +## Using `RawSQL` in `Insert` + +`gdb.Raw` is a string type, and the parameter of this type will be directly embedded as an `SQL` fragment into the SQL statement submitted to the underlying level. It will not be automatically converted into a string parameter type, nor will it be treated as a prepared parameter. For example: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES('id+2','john','123456','now()') +g.Model("user").Data(g.Map{ + "id": "id+2", + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": "now()", +}).Insert() +// Execution error: Error Code: 1136. Column count doesn't match value count at row 1 +``` + +After refactoring with `gdb.Raw`: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES(id+2,'john','123456',now()) +g.Model("user").Data(g.Map{ + "id": gdb.Raw("id+2"), + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": gdb.Raw("now()"), +}).Insert() +``` + +## Using `RawSQL` in `Update` + +```go +// UPDATE `user` SET login_count='login_count+1',update_time='now()' WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": "login_count+1", + "update_time": "now()", +}).Where("id", 1).Update() +// Execution error: Error Code: 1136. Column count doesn't match value count at row 1 +``` + +After refactoring with `gdb.Raw`: + +```go +// UPDATE `user` SET login_count=login_count+1,update_time=now() WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": gdb.Raw("login_count+1"), + "update_time": gdb.Raw("now()"), +}).Where("id", 1).Update() +``` + +## Using `RawSQL` in `Select` + +The time function `now()` will be converted into a string to be executed as an `SQL` parameter: + +```go +// SELECT * FROM `user` WHERE `created_at`<'now()' +g.Model("user").WhereLT("created_at", "now()").All() +``` + +After refactoring with `gdb.Raw`: + +```go +// SELECT * FROM `user` WHERE `created_at` The `Value` type is an alias for the `*gvar.Var` type, so you can use all the conversion methods of the `gvar.Var` data type. For details, please refer to the [Generic](../../../组件列表/数据结构/泛型类型-gvar/泛型类型-gvar.md) section. + +Example usage: + +First, the data table is defined as follows: + +``` +# Product Table +CREATE TABLE `goods` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(300) NOT NULL COMMENT 'Product Name', + `price` decimal(10,2) NOT NULL COMMENT 'Product Price', + ... + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +Next, the data in the table is as follows: + +``` +id title price +1 IPhoneX 5999.99 +``` + +Finally, the sample code is as follows: + +```go +if r, err := g.Model("goods").FindOne(1); err == nil { + fmt.Printf("goods id: %d\n", r["id"].Int()) + fmt.Printf("goods title: %s\n", r["title"].String()) + fmt.Printf("goods proce: %.2f\n", r["price"].Float32()) +} else { + g.Log().Error(gctx.New(), err) +} +``` + +After execution, the output is: + +``` +goods id: 1 +goods title: IPhoneX +goods proce: 5999.99 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..a75b456731d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/core/gdb-senior-custom-type-converting' +title: 'ORM Senior - UnmarshalValue' +sidebar_position: 10 +hide_title: true +description: "Utilize the custom type conversion features of ORM in the GoFrame framework to transform query results into desired types through specific interfaces, whether as direct types or struct attributes. This enhances the flexibility of the GoFrame framework, offering efficient solutions to assist developers in achieving advanced database interactions." +keywords: [GoFrame, GoFrame framework, ORM, custom type conversion, type conversion interface, query result processing, flexible expansion, efficient solutions, struct attributes, UnmarshalValue] +--- + +When we need to convert query results into custom types, whether as directly converted types or as attributes of a `struct`, we can achieve this by implementing specific interfaces. For detailed information, please refer to the [Type Conversion - Interface](../../类型转换/类型转换-UnmarshalValue.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..d86b02ebbd0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" @@ -0,0 +1,29 @@ +--- +slug: '/docs/core/gdb-senior-debugging' +title: 'ORM Senior - Debug Mode' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Debug Mode, SQL, Log, Database, Configuration Example, SetDebug, Debug Configuration] +description: "Using debugging mode with ORM advanced features in GoFrame framework. During development, you can enable debugging mode through the Debug configuration file option or SetDebug configuration method, allowing database SQL operation statements to be output at DEBUG level to the terminal or log file, facilitating developers in troubleshooting and performance optimization." +--- + +To facilitate debugging during the development phase, `GoFrame ORM` supports debugging mode, which can be enabled through the `Debug` configuration file option or the `SetDebug` configuration method. Subsequently, any database `SQL` operation statements will be output at the `DEBUG` level to the terminal or log files by the built-in log object. Below is a configuration example with debugging mode enabled: + +```yaml +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + debug: true +``` + +Example of output log content: + +```html +2021-05-22 21:12:10.776 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 4 ms] [default] [rows:0 ] [txid:1] BEGIN +2021-05-22 21:12:10.776 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 0 ms] [default] [rows:0 ] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:12:10.789 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 13 ms] [default] [rows:8 ] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:12:10.790 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:1 ] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:12:10.791 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:0 ] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:12:10.791 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 0 ms] [default] [rows:1 ] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:12:10.792 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:0 ] [txid:1] COMMIT +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" new file mode 100644 index 00000000000..15b864e3523 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/core/gdb-senior-connection-pool' +title: 'ORM Senior - Connection Pool' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Connection Pool, DB Stats, Database Connection, gdb, mysql, GoFrame database, GoFrame gdb] +description: "Use GoFrame framework's `DB.Stats` method to obtain the connection pool status of ORM objects. Through example code, developers can understand how to configure database connections and obtain detailed connection pool status information via GoFrame. Additionally, the specific fields of the connection pool status output and their meanings are introduced." +--- + +We can obtain the connection pool status of an `orm` object using the `DB.Stats` method. Let's look at an example: + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + db, err := gdb.New(gdb.ConfigNode{ + Link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test", + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + err = db.PingMaster() + if err != nil { + g.Log().Fatal(ctx, err) + } + stats := db.Stats(ctx) + g.Dump(stats) +} +``` + +After execution, the terminal output is as follows, showing each database node and its corresponding connection pool status information. + +```html +[ + { + node: { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pass: "12345678", + Name: "test", + Type: "mysql", + Link: "", + Extra: "", + Role: "", + Debug: false, + Prefix: "", + DryRun: false, + Weight: 0, + Charset: "utf8", + Protocol: "tcp", + Timezone: "", + Namespace: "", + MaxIdleConnCount: 0, + MaxOpenConnCount: 0, + MaxConnLifeTime: 0, + QueryTimeout: 0, + ExecTimeout: 0, + TranTimeout: 0, + PrepareTimeout: 0, + CreatedAt: "", + UpdatedAt: "", + DeletedAt: "", + TimeMaintainDisabled: false, + }, + stats: { + MaxOpenConnections: 0, + OpenConnections: 1, + InUse: 0, + Idle: 1, + WaitCount: 0, + WaitDuration: 0, + MaxIdleClosed: 0, + MaxIdleTimeClosed: 0, + MaxLifetimeClosed: 0, + }, + }, +] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..cbb268608a0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-senior' +title: 'ORM - Senior Features' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, GoFrame Framework, ORM, Advanced Features, Database Management, gdb, Go Development, Data Manipulation, Modeling, Query Optimization] +description: "Advanced features of the GoFrame framework's ORM, mainly including advanced functions for database management and operations. These features help developers handle data modeling more efficiently, optimize query performance, and simplify data manipulation processes, thereby improving the efficiency and quality of development using the GoFrame framework." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" new file mode 100644 index 00000000000..a5f684e02bd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" @@ -0,0 +1,83 @@ +--- +slug: '/docs/core/gdb' +title: 'Database ORM🔥' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Database ORM, gdb Module, Database Driver, Connection Pool, Preprocessing, Automation, Observability, DAO Design] +description: "In the GoFrame framework, the gdb module is used to implement ORM functionality for databases, emphasizing the design of connection pools, preprocessing SQL parameters, and automatic recognition of Map/Struct. The GoFrame ORM component supports nested transactions, interface-based design, is compatible with mainstream database drivers, and has strong configuration management and debugging features." +--- + +## Driver Introduction🔥 + +To decouple the database drivers from the main framework library, starting from version `v2.1`, all database drivers need to be introduced manually through community packages. + +For database driver installation and introduction, please refer to: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## Introduction + +The `ORM` functionality of the `GoFrame` framework is implemented by the `gdb` module, used for `ORM` operations of common relational databases. +:::tip +The `gdb` database engine uses a **connection pool design** at the bottom layer, and connections will automatically close when not in use, so there is no need to explicitly use the `Close` method to close the database connection when the connection object is no longer needed. +::: +:::note +Note: To improve the security of database operations, it is not recommended to directly concatenate parameters into an `SQL` string for execution in `ORM` operations. It is recommended to use preprocessing (utilizing `?` placeholders extensively) to pass `SQL` parameters. The bottom layer of `gdb` is implemented using preprocessing to handle the parameters passed by developers, ensuring the safety of database operations. +::: +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +## Features + +The `GoFrame ORM` component has the following notable features: + +1. Fully automated support for nested transactions. +2. Interface-oriented design, easy to use and extend. +3. Built-in support for mainstream database types and drivers, and easy to extend. +4. Powerful configuration management using the framework's unified configuration component. +5. Supports singleton mode to obtain database objects of the same configuration group. +6. Supports two operation methods: native SQL method operations and ORM chain operations. +7. Supports `OpenTelemetry` observability: trace tracing, logging, and metric reporting. +8. Automatically recognizes `Map/Struct` to receive query results through the `Scan` method, automating query result initialization and struct type conversion. +9. Recognizes empty results by returning `nil`, without the need for `sql.ErrNoRows` to identify empty query results. +10. Fully automated struct property-field mapping, without needing to explicitly define struct tags to maintain property-field mapping relationships. +11. Automates field recognition and filtering of given `Map/Struct/Slice` parameter types, greatly enhancing query condition input and result reception. +12. Perfectly supports `DAO` design at the `GoFrame` framework layer, fully automated `Model/DAO` code generation, significantly improving development efficiency. +13. Supports debugging modes, log output, `DryRun`, custom `Handler`, automatic type conversion, custom interface conversion, and other advanced features. +14. Supports query caching, soft delete, automatic time updates, model associations, database cluster configurations (software master-slave mode), and other practical features. + +## `g.DB` vs `gdb.New` & `gdb.Instance` + +There are three ways to obtain database operation objects: using the `g.DB` method (recommended), using the native `gdb.New` method, and using the package's native singleton method `gdb.Instance`, with the first being the recommended usage. The differences between these three methods are as follows: + +1. The `g.DB` object management method obtains a singleton object, integrating the management features of the configuration file and supporting hot updates of configuration files. +2. `gdb.New` creates a new database object (not a singleton) based on the given database node configuration and cannot use configuration files. +3. `gdb.Instance` is the package's native singleton management method, which needs to be used together with the configuration method to obtain the database singleton object for the corresponding configuration by group name (not required). +:::tip +There are so many ways to obtain objects because `GoFrame` is a modular design framework, and each module can be used independently. +::: +### Creating a Database Object with `New` + +```go +db, err := gdb.New(gdb.ConfigNode{ + Link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test", +}) +``` + +### Obtaining the Database Object Singleton + +```go +// Obtain the database object with default configuration (configuration name "default") +db := g.DB() + +// Obtain the database object of the configuration group named "user" +db := g.DB("user") + +// Obtain the database object singleton using the native singleton management method +db, err := gdb.Instance() +db, err := gdb.Instance("user") +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..435a3ecd9c9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,106 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map-example' +title: 'Map Validation - Example' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Parameter Validation, Map Validation, Custom Error Messages, Default Error Messages, Framework Usage, Validation Rules, Code Example, Data Validation] +description: "In the GoFrame framework, Map validation is demonstrated with both default and custom error messages. Example code shows how to validate parameters and output corresponding error messages on validation failures, helping developers better implement data validation and error handling mechanisms." +--- + +## Default Error Messages + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +After execution, the terminal outputs: + +```javascript +{ + "passport": { + "required": "The passport field is required", + "length": "The passport value `` length must be between 6 and 16", + }, + "password": { + "same": "The password value `123456` must be the same as field password2", + }, +} +``` + +## Custom Error Messages + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + messages = map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在{min}到{max}之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + ) + + err := g.Validator().Messages(messages).Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +This example also demonstrates two data types for passing custom error messages through `messages`, either `string` or `map[string]string`. For `map[string]string`, you need to specify corresponding error messages for each field and rule, forming a two-dimensional associative array. After executing this example, the terminal outputs: + +```javascript +{ + "passport": { + "length": "账号长度应当在6到16之间", + "required": "账号不能为空" + }, + "password": { + "same": "两次密码输入不相等" + } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" new file mode 100644 index 00000000000..a4a18ac6960 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map-sequence' +title: 'Map Validation - Sequence' +sidebar_position: 1 +hide_title: true +keywords: [Map Validation,Validation Order,GoFrame,Parameter Validation,golang,map Type,Rule Order,Validation Error Information,gogf,Data Validation] +description: "Implementing the order of Map validation in the GoFrame framework. By modifying the rule parameter type to []string, the order of the returned error messages can be consistent with the set rules, solving the issue of non-fixed validation results caused by the unordered nature of map types in golang. This tutorial provides detailed example code and execution results to help users understand how to perform order validation using GoFrame." +--- + +## Introduction + +If you execute the previous example code several times, you will find that the returned results are unordered, and the order of the fields and rules is completely random. Even if we use other methods like `FirstItem`, `FirstString()` to obtain validation results, they are the same, and the returned validation results are not fixed. This is because the rules we pass are of the `map` type, and the `map` type in `golang` does not have order, so the validation results are random, and the same validation method may return different result values each time it is executed. + +## Sequential Validation + +Let's improve the above example: If the validation result does not satisfy the `required` rule, it returns the corresponding error message; otherwise, it returns the subsequent validation error message. In other words, the returned error messages should be consistent with the sequence defined in the rules. As follows: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = []string{ + "passport@required|length:6,16#账号不能为空|账号长度应当在{min}到{max}之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在{min}到{max}之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + fmt.Println(err.Map()) + fmt.Println(err.FirstItem()) + fmt.Println(err.FirstError()) + } +} +``` + +After execution, the terminal output: + +``` +map[length:账号长度应当在6到16之间 required:账号不能为空] +passport map[length:账号长度应当在6到16之间 required:账号不能为空] +账号不能为空 +``` + +As you can see, if we want the validation results to maintain order, we just need to change the type of the `rules` parameter to `[]string` and set them according to a certain rule. Furthermore, the `msgs` parameter can be defined directly in the `rules` parameter or passed separately (using a third parameter). For detailed rules on writing validation rules in `rules`, please refer to the chapter [Struct Validation - Example](../数据校验-Struct校验/Struct校验-基本使用.md). \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..40139506ccc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map' +title: 'Data Type - Map' +hide_title: true +keywords: [GoFrame, Data Validation, Map Validation, Parameter Validation, GoFrame Framework, Validation Rules, Input Validation, Data Integrity, Golang, Development Framework] +description: "Data validation using the GoFrame framework, particularly for Map type data validation methods. With the GoFrame framework, developers can easily implement input data validation to ensure data validity and integrity. The page details the steps for using validation rules to enhance the security and reliability of applications." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" new file mode 100644 index 00000000000..ea634b18b25 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct-assoc' +title: 'Struct Validation - Assoc' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gvalid, Struct Validation, Assoc Method, Parameter Validation, Struct Validation, goframe v2.0, gvalid tag, Client Request Validation] +description: "To avoid issues caused by default values in structs, the GoFrame framework introduces the Assoc method, which allows strict struct validation based on given parameters. This method is especially useful in scenarios involving client request parameters, ensuring that validation rules are not affected by default values." +--- + +## Introduction + +To avoid confusion caused by default values in structs, starting from version `goframe v2.0`, we have added an `Assoc` method. This method allows struct validation to be strictly based on the given parameters rather than on the struct's property values (to avoid the effect of default struct property values). The validation rules will still be automatically read from the `gvalid tag` within the struct. +:::tip +This is particularly useful for scenarios that require validation of client request parameters. +::: +## Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Name string `v:"required#Please enter user name"` + Type int `v:"required#Please select user type"` + } + var ( + ctx = gctx.New() + user = User{} + data = g.Map{ + "name": "john", + } + ) + err := g.Validator().Assoc(data).Data(user).Run(ctx) + if err != nil { + g.Dump(err.Items()) + } +} +``` + +After execution, the terminal output is: + +``` +[ + { + "Type": { + "required": "Please select user type" + } + } +] +``` + +As you can see, the validation rule `required` for the `Type` attribute in the struct was not affected by the default value and was still executed as expected. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..ace37ba0642 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,225 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct-example' +title: 'Struct Validation - Example' +sidebar_position: 0 +hide_title: true +keywords: [Struct Validation, GoFrame Framework, gvalid, Parameter Validation, Field Alias, Validation Rules, Chain Operation, Nested Validation, Recursive Validation, Go] +description: "Conducting validation for Struct type data using the GoFrame framework, including detailed explanations of validation tag rules and methods for different data types, such as basic validation, using map for custom rules, and recursive validation of structs. Example code demonstrates how to set field aliases and customize error messages, implementing complex validation logic for different attributes within a struct object." +--- + +`Struct` validation is often used in the following chain operation method: + +```go +g.Validator().Data(object).Run(ctx) +``` + +## Validation `tag` Rules Introduction + +Before introducing `Struct` parameter type validation, let's introduce some common validation `tag` rules. The rules are as follows: + +``` +[FieldAlias@]ValidationRule[#ErrorMessage] +``` + +Where: + +- `FieldAlias` and `ErrorMessage` are **optional fields**, and `ValidationRule` is a **mandatory field.** +- `FieldAlias` is an optional field, specifying the alias of the corresponding `struct` field used in validation. The `error` object returned after validation will use this alias. This is particularly useful when dealing with request forms, as field names in forms often do not match the property names in the `struct`. In most scenarios, you don't need to set the field alias and can directly use the property name by default. +- `ValidationRule` is the validation rule for the current property. Multiple validation rules can be combined using the `|` symbol, for example: `required|between:1,100`. +- `ErrorMessage` is an optional field, representing a custom error message that overrides the default error message when the rule validation fails. + +## Validation `tag` Usage Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int `v:"uid @integer|min:1#|Please enter the user ID"` + Name string `v:"name @required|length:6,30#Please enter the user name|Invalid length for user name"` + Pass1 string `v:"password1@required|password3"` + Pass2 string `v:"password2@required|password3|same:Pass1#|Invalid password format|The two passwords do not match, please re-enter"` +} + +func main() { + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass1: "Abc123!@#", + Pass2: "123", + } + ) + + err := g.Validator().Data(user).Run(ctx) + if err != nil { + g.Dump(err.Items()) + } +} +``` + +As you can see, we can bind validation rules and error messages using `gvalid tag` when defining the `struct`. In this sample code, the rule `same:password1` is the same as using `same:Pass1`. **This means that data validation can use both the original `struct` property name and the alias simultaneously. However, the returned result will only use the alias, which is the primary purpose of the alias.** Also, when validating a `struct` object, you can pass validation and error message parameters, which will override the corresponding parameters bound when the `struct` is defined. + +After executing the above example, the output is: + +``` +[ + { + "uid": { + "min": "Please enter the user ID", + }, + }, + { + "name": { + "length": "Invalid length for user name", + }, + }, + { + "password2": { + "password3": "Invalid password format", + }, + }, +] +``` + +## Using `map` to Specify Validation Rules + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Age int + Name string + } + var ( + ctx = gctx.New() + user = User{Name: "john"} + rules = map[string]string{ + "Name": "required|length:6,16", + "Age": "between:18,30", + } + messages = map[string]interface{}{ + "Name": map[string]string{ + "required": "Name cannot be empty", + "length": "Name length must be between {min} and {max} characters", + }, + "Age": "Age must be between 18 to 30 years old", + } + ) + + err := g.Validator().Rules(rules).Messages(messages).Data(user).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +In the above example, the `Age` property has a default value of `0`, which can cause the `required` rule to be ineffective. Therefore, we use the `between` rule for validation instead. After executing the example, the terminal outputs: + +``` +{ + "Age": { + "between": "Age must be between 18 to 30 years old" + }, + "Name": { + "length": "Name length must be between 6 to 16 characters" + } +} +``` + +## Struct Recursive Validation (Nested Validation) + +Supports recursive struct validation (nested validation), meaning if a property is a struct (nested structs are also supported), it will automatically perform recursive validation on that property. Example usage: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Pass struct { + Pass1 string `v:"password1@required|same:password2#Please enter your password|The two passwords do not match"` + Pass2 string `v:"password2@required|same:password1#Please re-enter your password|The two passwords do not match"` + } + type User struct { + Pass + Id int + Name string `valid:"name@required#Please enter your name"` + } + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + ) + err := g.Validator().Data(user).Run(ctx) + g.Dump(err.Maps()) +} +``` + +Or when properties are nested structs (`embedded`): + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Pass struct { + Pass1 string `v:"password1@required|same:password2#Please enter your password|The two passwords do not match"` + Pass2 string `v:"password2@required|same:password1#Please re-enter your password|The two passwords do not match"` + } + type User struct { + Id int + Name string `valid:"name@required#Please enter your name"` + Pass Pass + } + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + ) + err := g.Validator().Data(user).Run(ctx) + g.Dump(err.Maps()) +} +``` + +Once executed, the terminal output is: + +``` +{ + "password1": { + "same": "The two passwords do not match", + }, + "password2": { + "same": "The two passwords do not match", + }, +} +``` + +For more introductions on recursive validation, please refer to the section: [Data Validation - Recursive](../../数据校验-递归校验.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..ee7c4e460ad --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct' +title: 'Data Type - Struct' +hide_title: true +keywords: [data validation, Struct validation, GoFrame, GoFrame framework, parameter type, data verification, input validation, server development, backend architecture, parameter checking] +description: "In the GoFrame framework, using the Struct validation feature for data validation can help developers perform input verification and parameter type checks. By using Struct validation, you can ensure data correctness more efficiently and improve server-side reliability." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..cb66315f68b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,142 @@ +--- +slug: '/docs/core/gvalid-parameter-type-basic' +title: 'Data Type - Value' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Data Validation, Single Data Validation, Validation Rules, Error Messages, Data Length, Validation Types, Regex Validation, Custom Tips] +description: "How to perform single data validation in the GoFrame framework. It discusses the usage of the Data method to specify the data to be validated and the Rule method to specify validation rules. Examples demonstrate different validation scenarios such as validating data length, data type, and size, and regex validation, including how to apply multiple custom error messages." +--- + +We can treat a given variable as a complete parameter for validation, i.e., single data validation. If a variable is a complex type like `Struct/Map`, and we need to validate its internal attributes/key-value pairs, this scenario will be introduced in later chapters. Single data validation must specify the data to be validated through the `Data` method and the validation rules through the `Rule` method. Single data validation is relatively straightforward. Let's look at some examples. + +## Validate Data Length With Default Error Messages + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "length:6,16" + ) + + if err := g.Validator().Rules(rule).Data("123456").Run(ctx); err != nil { + fmt.Println(err.String()) + } + if err := g.Validator().Rules(rule).Data("12345").Run(ctx); err != nil { + fmt.Println(err.String()) + } +} +``` + +After execution, the terminal output: + +``` +The value `12345` length must be between 6 and 16 +``` + +## Validate Data Type and Size With Custom Error Messages + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "integer|between:6,16" + messages = "Please enter an integer|The parameter size is incorrect, my friend" + value = 5.66 + ) + + if err := g.Validator().Rules(rule).Messages(messages).Data(value).Run(ctx); err != nil { + g.Dump(err.Map()) + } +} +``` + +After execution, the terminal output: + +``` +{ + "integer": "Please enter an integer", + "between": "The parameter size is incorrect, my friend", +} +``` + +As you can see, multiple rules and multiple custom error messages are separated using the `|` character in English. Note that the order of custom error messages corresponds one-to-one with the order of multiple rules. The `messages` parameter supports not only `string` types but also `map[string]string` types, as shown in the following example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "url|min-length:11" + value = "goframe.org" + messages = map[string]string{ + "url": "Please enter a valid URL address", + "min-length": "The address must be at least {min} characters long", + } + ) + if err := g.Validator().Rules(rule).Messages(messages).Data(value).Run(ctx); err != nil { + g.Dump(err.Map()) + } +} +``` + +After execution, the terminal output: + +``` +{ + "url": "Please enter a valid URL address", +} +``` + +## Use Custom Regex to Validate Data Format With Default Error Messages + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = `regex:\d{6,}|\D{6,}|max-length:16` + ) + + if err := g.Validator().Rules(rule).Data(`123456`).Run(ctx); err != nil { + fmt.Println(err) + } + + if err := g.Validator().Rules(rule).Data(`abcde6`).Run(ctx); err != nil { + fmt.Println(err) + } +} +``` + +After execution, the terminal output: + +``` +The value `abcde6` must be in regex of: \d{6,}|\D{6,} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..623c268b595 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gvalid-parameter-type' +title: 'Data Validation - Data Type' +sidebar_position: 3 +hide_title: true +keywords: [data validation, parameter type, GoFrame, GoFrame framework, backend development, parameter verification, input validation, programming framework, development tools, validation module] +description: "Handling parameter types in data validation using the GoFrame framework. This chapter elaborates on data validation methods for different types of parameters, helping developers effectively handle and validate user inputs in backend development, enhancing the reliability and security of applications." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..9f65dedfed6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" @@ -0,0 +1,161 @@ +--- +slug: '/docs/core/gvalid-optional-rule' +title: 'Data Validation - Optional Rule' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Data Validation, Optional Validation, Validation Rules, Example, Validation Component, Parameter Validation, Programming, GoFrame Framework] +description: "Using the GoFrame framework for data validation, especially focusing on the use of optional validation rules. When the validation rules do not include 'required', optional validation rules do not enforce validation on empty strings or nil values. The document provides multiple practical code examples to demonstrate the process and considerations for implementing optional data validation with the GoFrame framework in Go language, suitable for developers to apply in projects." +--- + +## Optional Validation Rules + +When the given data validation rules do not include the `required*` rule, it indicates that the rule is not a mandatory rule. If the given value is `nil` or an `empty string`, its validation will be ignored. + +## Example 1: Empty String + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId string `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +## Example 2: Null Pointer Attribute + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId *gvar.Var `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +## Example 3: Empty Integer Attribute + +**Note that if the key value is `0` or `false`, the parameter value will still be validated.** + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId int `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +After execution, the terminal outputs: + +``` +project id must between 1, 10000 +``` + +## Example 4: Parameter Passing Through `map` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = []string{ + "passport@length:6,16", + "password@required|length:6,16|same:password2", + "password2@required|length:6,16", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +Note that the `passport` key does not have the `required` rule, so even if the given `passport` parameter is an empty string and does not meet the rule, it does not trigger an error, since the validation component considers it an optional validation rule. + +After execution, the terminal outputs: + +``` +{ + "password": { + "same": "The password value `123456` must be the same as field password2", + }, +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..db98ae62a93 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/gvalid-faq' +title: 'Data Validation - FAQ' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Data Validation, Struct Default Value, Required Rule, Pointer Type, Combined Validation Rules, Assoc Joint Validation, API Input/Output, Server] +description: "The impact of Struct's default values on the required rule and its solutions when using the GoFrame framework for data validation, including using pointer types to bypass default value impacts, combined validation rules, and the Assoc joint validation method to ensure validation accuracy." +--- + +## Impact of `Struct` Default Values on the `required` Rule + +The properties of a `Struct` have `default values`, which in some cases can cause the `required` rule to become ineffective. For example: + +```go +type User struct { + Name string `v:"required"` + Age uint `v:"required"` +} +``` + +In this structure validation, the `required` check for the `Age` property will fail because `Age` will have a default value of `0` even if no input is provided. + +There are **three** solutions: + +1. Change the attribute to a pointer type, such as `*int`, `*float64`, `*g.Var`, etc., taking advantage of the pointer type's default value of `nil` to bypass this issue. +2. Use a combined validation rule to compensate for the impact of default values on the `required` rule. For example, modifying the validation rule of the `Age` attribute in the example above to `required|min:1` will achieve the desired business validation effect. +3. Use the `Assoc` joint validation method in `Struct` validation to set joint validation parameters. When validating parameters of the `Struct` type, the parameter values will be validated according to the parameters given in the `Assoc` method. If using the framework's `Server`, with structured `API` input/output (`XxxReq/XxxRes`), the `Server` will automatically call `Assoc` for validation, so developers need not worry about the impact of default values. Documentation link: [Struct Validation - Assoc](数据校验-参数类型/数据校验-Struct校验/Struct校验-Assoc关联.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..401a80fd791 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,505 @@ +--- +slug: '/docs/core/gvalid-funcs' +title: 'Data Validation - Methods' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame Framework, Validator, Data Validation, Validation Rules, Custom Validation, I18N Internationalization, Field Comparison, Validation Methods, Validation Examples, Error Messages] +description: "The data validation function in the GoFrame framework, detailing the use of common validation methods including New, Run, Clone, I18n, Bail, and Ci. Through specific examples, it explains how to effectively use these methods for data validation and provides ways to customize validation rules and error messages to help developers better complete data validation tasks for their applications." +--- +:::tip +The following list of common methods in the document may be delayed compared to new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) +::: +## `New` + +- Description: `New` creates and returns a new object of `Validator`. + +- Format: + +```go +New() *Validator +``` + +- Example: + +```go +func ExampleNew() { + validator := gvalid.New() + + if err := validator.Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Print(err) + } + + // Output: + // The value `16` must be equal or greater than 18 +} +``` + + +## `Run` + +- Description: `Run` performs validation operations on data with given rules and information. + +- Format: + +```go +Run(ctx context.Context) Error +``` + +- Example: + +```go +func ExampleValidator_Run() { + // check value mode + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println("check value err:", err) + } + // check map mode + data := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + if err := g.Validator().Data(data).Rules(rules).Run(context.Background()); err != nil { + fmt.Println("check map err:", err) + } + // check struct mode + type Params struct { + Page int `v:"required|min:1"` + Size int `v:"required|between:1,100"` + ProjectId string `v:"between:1,10000"` + } + rules = map[string]string{ + "Page": "required|min:1", + "Size": "required|between:1,100", + "ProjectId": "between:1,10000", + } + obj := &Params{ + Page: 0, + Size: 101, + } + if err := g.Validator().Data(obj).Run(context.Background()); err != nil { + fmt.Println("check struct err:", err) + } + + // May Output: + // check value err: The value `16` must be equal or greater than 18 + // check map err: The passport field is required; The passport value `` length must be between 6 and 16; The password value `123456` must be the same as field password2 + // check struct err: The Page value `0` must be equal or greater than 1; The Size value `101` must be between 1 and 100 +} +``` + + +## `Clone` + +- Description: Clone creates and returns a value copy object of the current Validator. + +- Format: + +``` +(v *Validator) Clone() *Validator +``` + +- Example: + +```go +func ExampleValidator_Clone() { + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println(err) + } + + if err := g.Validator().Clone().Data(20).Run(context.Background()); err != nil { + fmt.Println(err) + } else { + fmt.Println("Check Success!") + } + + // Output: + // The value `16` must be equal or greater than 18 + // Check Success! +} +``` + + +## I18n + +- Description: The `I18n` method is used to set the `I18N` internationalization component for the current validation object. By default, the validation component uses the framework's global default `i18n` component object. + +- Format: + +```go +I18n(i18nManager *gi18n.Manager) *Validator +``` + +- Example: + +```go +func ExampleValidator_I18n() { + var ( + i18nManager = gi18n.New() + ctxCn = gi18n.WithLanguage(context.Background(), "cn") + validator = gvalid.New() + ) + + validator = validator.Data(16).Rules("min:18") + + if err := validator.Run(context.Background()); err != nil { + fmt.Println(err) + } + + if err := validator.I18n(i18nManager).Run(ctxCn); err != nil { + fmt.Println(err) + } + + // Output: + // The value `16` must be equal or greater than 18 + // 字段值`16`字段最小值应当为18 +} +``` + + +## Bail + +- Description: The `Bail` method is used to set that if any rule fails in subsequent validations, it stops validation immediately and returns the error result. + +- Format: + +```go +Bail() *Validator +``` + +- Example: + +```go +func ExampleValidator_Bail() { + type BizReq struct { + Account string `v:"required|length:6,16|same:QQ"` + QQ string + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + QQ: "123456", + Password: "goframe.org", + Password2: "goframe.org", + } + ) + + if err := g.Validator().Bail().Data(req).Run(ctx); err != nil { + fmt.Println("Use Bail Error:", err) + } + + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println("Not Use Bail Error:", err) + } + + // output: + // Use Bail Error: The Account value `gf` length must be between 6 and 16 + // Not Use Bail Error: The Account value `gf` length must be between 6 and 16; The Account value `gf` must be the same as field QQ +} +``` + + +## `Ci` + +- Description: The `Ci` method is used to set case-insensitive comparison when requiring value comparison in rules. + +- Format: + +```go +Ci() *Validator +``` + +- Example: + +```go +func ExampleValidator_Ci() { + + type BizReq struct { + Account string `v:"required"` + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + Password: "Goframe.org", // Diff from Password2, but because of "ci", rule check passed + Password2: "goframe.org", + } + ) + + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println("Not Use CI Error:", err) + } + + if err := g.Validator().Ci().Data(req).Run(ctx); err == nil { + fmt.Println("Use CI Passed!") + } + + // output: + // Not Use CI Error: The Password value `Goframe.org` must be the same as field Password2 + // Use CI Passed! +} +``` + + +## Data + +- Description: The `Data` method is used to provide data that needs to be jointly validated. + +- Format: + +```go +Data(data interface{}) *Validator +``` + +- Example: + +```go +func ExampleValidator_Data() { + type BizReq struct { + Password1 string `v:"password"` + Password2 string `v:"password"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "goframe", + Password2: "gofra", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Password2 value `gofra` is not a valid password format +} +``` + + +## Assoc + +- Description: `Assoc` is a chain operation function that sets validation data for the current `Validator`. The parameter `assoc` is usually of type `map`, specifying the value of `union validator` in the `map`. + +- Note: Using a non-`nil` `assoc` parameter will set the `useDataInsteadOfObjectAttributes` attribute to `true`. +- Format: + +```go +Assoc(assoc interface{}) *Validator +``` + +- Example: + +```go +func ExampleValidator_Assoc() { + + type User struct { + Name string `v:"required"` + Type int `v:"required"` + } + + data := g.Map{ + "name": "john", + } + + user := User{} + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + + if err := g.Validator().Data(user).Assoc(data).Run(context.Background()); err != nil { + fmt.Print(err) + } + + // Output: + // The Type field is required +} +``` + + +## Rules + +- Description: The `Rules` method is used to provide custom validation rules for the current chain operation validation. + +- Format: + +```go +Rules(rules interface{}) *Validator +``` + +- Example: + +```go +func ExampleValidator_Rules() { + + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println(err) + } + + // Output: + // The value `16` must be equal or greater than 18 +} +``` + + +## `Message` + +- Description: The `Messages` method is used to provide custom error messages for the current chain operation validation. + +- Format: + +```go +Messages(messages interface{}) *Validator +``` + +- Example: + +```go +func ExampleValidator_Messages() { + if err := g.Validator().Data(16).Rules("min:18").Messages("Can not regist, Age is less then 18!").Run(context.Background()); err != nil { + fmt.Println(err) + } + + // Output: + // Can not regist, Age is less then 18! +} +``` + + +## RuleFunc + +- Description: `RuleFunc` registers a custom validation rule function to the current `Validator`. + +- Format: + +```go +RuleFunc(rule string, f RuleFunc) *Validator +``` + +- Example: + +```go +func ExampleValidator_RuleFunc() { + var ( + ctx = context.Background() + lenErrRuleName = "LenErr" + passErrRuleName = "PassErr" + lenErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if len(pass) != 6 { + return errors.New(in.Message) + } + return nil + } + passErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if m := in.Data.Map(); m["data"] != pass { + return errors.New(in.Message) + } + return nil + } + ) + + type LenErrStruct struct { + Value string `v:"uid@LenErr#Value Length Error!"` + Data string `p:"data"` + } + + st := &LenErrStruct{ + Value: "123", + Data: "123456", + } + // single error sample + if err := g.Validator().RuleFunc(lenErrRuleName, lenErrRuleFunc).Data(st).Run(ctx); err != nil { + fmt.Println(err) + } + + type MultiErrorStruct struct { + Value string `v:"uid@LenErr|PassErr#Value Length Error!|Pass is not Same!"` + Data string `p:"data"` + } + + multi := &MultiErrorStruct{ + Value: "123", + Data: "123456", + } + // multi error sample + if err := g.Validator().RuleFunc(lenErrRuleName, lenErrRuleFunc).RuleFunc(passErrRuleName, passErrRuleFunc).Data(multi).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // Value Length Error! + // Value Length Error!; Pass is not Same! +} +``` + + +## RuleFuncMap + +- Description: `RuleFuncMap` registers multiple custom validation rule functions to the current `Validator`. + +- Format: + +```go +RuleFuncMap(m map[string]RuleFunc) *Validator +``` + +- Example: + +```go +func ExampleValidator_RuleFuncMap() { + var ( + ctx = context.Background() + lenErrRuleName = "LenErr" + passErrRuleName = "PassErr" + lenErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if len(pass) != 6 { + return errors.New(in.Message) + } + return nil + } + passErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if m := in.Data.Map(); m["data"] != pass { + return errors.New(in.Message) + } + return nil + } + ruleMap = map[string]gvalid.RuleFunc{ + lenErrRuleName: lenErrRuleFunc, + passErrRuleName: passErrRuleFunc, + } + ) + + type MultiErrorStruct struct { + Value string `v:"uid@LenErr|PassErr#Value Length Error!|Pass is not Same!"` + Data string `p:"data"` + } + + multi := &MultiErrorStruct{ + Value: "123", + Data: "123456", + } + + if err := g.Validator().RuleFuncMap(ruleMap).Data(multi).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // Value Length Error!; Pass is not Same! +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..7cc6228fc51 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" @@ -0,0 +1,148 @@ +--- +slug: '/docs/core/gvalid-validator' +title: 'Data Validation - Object' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Data Validation, Validator Object, Validator, Chained Operations, Internationalization, Custom Validation, Error Messages, Data Association] +description: "Using the data validation component in the GoFrame framework for data validation, including configuration management and chained operations of the validation object. Detailed examples show single data validation, struct, and map data validation methods to help developers quickly master data validation techniques." +--- + +## Validator Object + +The data validation component provides a validator object for unified configuration management and convenient chained operations for data validation. + +**API Documentation**: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +```go +type Validator + func New() *Validator + func (v *Validator) Assoc(assoc interface{}) *Validator + func (v *Validator) Bail() *Validator + func (v *Validator) Ci() *Validator + func (v *Validator) Clone() *Validator + func (v *Validator) Data(data interface{}) *Validator + func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator + func (v *Validator) Messages(messages interface{}) *Validator + func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator + func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator + func (v *Validator) Rules(rules interface{}) *Validator + func (v *Validator) Run(ctx context.Context) Error +``` + +**Brief Explanation:** + +1. The `New` method is used to create a new validation object. +2. `Assoc` is used for associating data validation; see subsequent sections for details. +3. The `Bail` method is used to set that validation stops immediately and returns an error result if any rule fails in multiple subsequent validations. +4. The `Ci` method is used for setting case-insensitive field names when comparing values. +5. The `Run` method performs validation operations on data given rules and messages. +6. The `I18n` method is used to set the `I18N` internationalization component for the current validator object. By default, the validator component uses the framework's global default `i18n` component object. +7. The `Data` method is used to pass the data set for joint validation, often `map` type or `struct` type. +8. The `Rules` method is used to pass custom validation rules for the current chained operation, often using `[]string` type or `map` type. +9. The `Messages` method is used to pass custom error messages for the current chained operation, often using `map` type for passing; see subsequent code examples for details. +:::tip +Since the validator object is also a very commonly used object, the `g` module defines the `Validator` method for quickly creating validator objects. In most scenarios, we recommend using the `g` module's `g.Validator()` method to quickly create a validator object. For an introduction to the `g` module, refer to the section: [Objects](../对象管理.md) +::: +## Usage Examples + +### Single Data Validation + +```go +var ( + err error + ctx = gctx.New() +) +err = g.Validator(). + Rules("min:18"). + Data(16). + Messages("Minors are not allowed to register"). + Run(ctx) +fmt.Println(err.Error()) + +// Output: +// Minors are not allowed to register +``` + +```go +var ( + err error + ctx = gctx.New() + data = g.Map{ + "password": "123", + } +) + +err = g.Validator().Data("").Assoc(data). + Rules("required-with:password"). + Messages("Please enter the confirmation password"). + Run(ctx) + +fmt.Println(err.Error()) +``` + +### `Struct` Data Validation + +```go +type User struct { + Name string `v:"required#Please enter the user name"` + Type int `v:"required#Please select the user type"` +} +var ( + err error + ctx = gctx.New() + user = User{} + data = g.Map{ + "name": "john", + } +) +if err = gconv.Scan(data, &user); err != nil { + panic(err) +} +err = g.Validator().Assoc(data).Data(user).Run(ctx) +if err != nil { + fmt.Println(err.(gvalid.Error).Items()) +} + +// Output: +// [map[Type:map[required:Please select the user type]]] +``` + +### `Map` Data Validation + +```go +params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", +} +rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", +} +messages := map[string]interface{}{ + "passport": "Account cannot be empty|Account length should be between {min} and {max}", + "password": map[string]string{ + "required": "Password cannot be empty", + "same": "The passwords entered are not the same", + }, +} +err := g.Validator().Messages(messages).Rules(rules).Data(params).Run(gctx.New()) +if err != nil { + g.Dump(err.Maps()) +} +``` + +After execution, the terminal output is: + +``` +{ + "passport": { + "length": "Account length should be between 6 and 16", + "required": "Account cannot be empty" + }, + "password": { + "same": "The passwords entered are not the same" + } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" new file mode 100644 index 00000000000..578fa5c52b2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/gvalid-result-handling' +title: 'Data Validation - Result' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame framework, Data Validation, Validation Results, Error Interface, gvalid.Error, GoFrame v2, Validation Methods, Error Messages, gerror.Current, Sequential Validation] +description: "Using the GoFrame framework for data validation, this document describes the implementation and usage of the gvalid.Error interface. It details methods for obtaining validation error messages, including the impact of sequential and non-sequential validation. Additionally, examples show how to retrieve the first error message from validation errors, providing practical guidance for developers." +--- + +## Introduction + +The validation result is an `error` object, internally implemented using the `gvalid.Error` object. When data rule validation succeeds, the validation method returns `nil`. When data rule validation fails, the returned object is a structured hierarchical `map` containing multiple fields and their rules along with the corresponding error messages, allowing the receiver to accurately pinpoint the error rules. The related data structure and methods are as follows: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +```go +type Error interface { + Code() gcode.Code + Current() error + Error() string + FirstItem() (key string, messages map[string]error) + FirstRule() (rule string, err error) + FirstError() (err error) + Items() (items []map[string]map[string]error) + Map() map[string]error + Maps() map[string]map[string]error + String() string + Strings() (errs []string) +} +``` + +This data structure can be understood in conjunction with subsequent examples. We can obtain the raw error message data structure `map` through the `Maps()` method. However, in most cases, specific error messages can be conveniently obtained through other methods of the `Error` interface. +:::tip +In most cases, we are not concerned with the specific failed validation rules and can use the `Error/String` method to directly return all error messages. The order of results when obtaining error messages may vary depending on whether the validation rule is sequential. +::: +**Brief Description:** + +The value of the validation result can be obtained through multiple validation result methods. To provide developers with a comprehensive understanding, the following details are provided: + +| Method | Description | +| --- | --- | +| `Code` | Common method. Implements the `gerror` `Code` interface, in which this method consistently returns the error code `gcode.CodeValidationFailed` within the validation component. | +| `Error` | Common method. Implements the standard library `error.Error` interface to obtain a string composed of all validation errors. Its internal logic is the same as the `String` method. | +| `Current` | Common method. Implements the `gerror` `Current` interface for obtaining the first error object among validation errors. | +| `Items` | In sequential validation, returns an array of validation errors in the order of validation rules; this order is only valid in sequential validation. Otherwise, the result is random. | +| `Map` | Returns the erroneous sub-rule and corresponding error message `map` obtained from `FirstItem`. | +| `Maps` | Returns all error keys and corresponding error rules and error messages ( `map[string]map[string]error` ). | +| `String` | Returns all error messages as a single string, with multiple rule error messages connected by `;`. Its sequence is only valid when using sequential validation rules; otherwise, the result is random. | +| `Strings` | Returns all error messages formed into a `[]string` type. Its sequence is only valid when using sequential validation rules; otherwise, the result is random. | +| `FirstItem` | When there are multiple key/attribute validation errors, it retrieves the first erroneous key name, along with its corresponding error rule and message. Its sequence is only valid when using sequential validation rules; otherwise, the result is random. | +| `FirstRule` | Returns the first erroneous rule and corresponding error message from `FirstItem`. Its sequence is only valid when using sequential validation rules; otherwise, the result is random. | +| `FirstString` | Returns the first rule error message from `FirstRule`. Its sequence is only valid when using sequential validation rules; otherwise, the result is random. | + +## Support for `gerror.Current` + +We can see that `gvalid.Error` implements the `Current() error` interface, allowing the first error message to be retrieved via the `gerror.Current` method, which is quite convenient for returning error messages when interface validation fails. Let's look at an example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" +) + +func main() { + type User struct { + Name string `v:"required#Please enter user name"` + Type int `v:"required|min:1#|Please select user type"` + } + var ( + err error + ctx = gctx.New() + user = User{} + ) + if err = g.Validator().Data(user).Run(ctx); err != nil { + g.Dump(err.(gvalid.Error).Maps()) + g.Dump(gerror.Current(err)) + } +} +``` + +Here, `gerror.Current(err)` is used to obtain the first validation error message. After execution, the terminal outputs: + +``` +{ + "Name": { + "required": "Please enter user name", + }, + "Type": { + "min": "Please select user type", + }, +} +"Please enter user name" +``` +:::warning +It is important to note that during data validation, **sequential validation** and **non-sequential validation** exist, which will affect the result of obtaining the first error message. For details about sequential and non-sequential validation, refer to subsequent chapters. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" new file mode 100644 index 00000000000..baf4901b99d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" @@ -0,0 +1,1969 @@ +--- +slug: '/docs/core/gvalid-rules' +title: 'Data Validation - Rules' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, data validation, validation rules, GoFrame framework, validation logic, format, parameter validation, built-in rules, joint validation, intelligent matching] +description: "Data validation rules and their applications in the GoFrame framework. The GoFrame framework comes with multiple commonly used validation rules, assisting developers in parameter validation and struct validation when processing data. The article provides a detailed explanation of how to use various validation rules in different scenarios, including required parameters, date formats, numeric ranges, enumeration validation, etc., offering developers a flexible validation mechanism." +--- + +The `GoFrame` framework's validation component comes with dozens of common validation rules. The validation component is one of the most frequently used core components by developers. +:::info +When the validation rules involve scenarios of joint validation, the **parameter name** associated in the rules will be automatically intelligently matched in a case-insensitive manner and ignoring special characters. In the following example codes, we use the common struct validation method, where the `v` tag in the struct property stands for `validation` abbreviation, used to set the validation specifications for that property. +::: +## Modifier Rules + +Modifier rules have no validation logic themselves but modify the implementation logic of subsequent functional rules. + +### `ci` + +- Format: `ci` +- Description: By default, field value comparisons are **case-sensitive** strict match comparisons. Using the `ci(Case Insensitive)` modifier rule, you can set the subsequent rule fields requiring comparison to be **case-insensitive**. Such as: `same`, `different`, `in`, `not-in`, etc. +- Example: + + ```go + func Example_Rule_CaseInsensitive() { + type BizReq struct { + Account string `v:"required"` + Password string `v:"required|ci|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + Password: "Goframe.org", // Diff from Password2, but because of "ci", rule check passed + Password2: "goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // output: + } + ``` + + +### `bail` + +Special attention is needed: If there are multiple validation rules for parameter validation, and there is a `required*` rule used together with the `bail` modifier rule, it is recommended to place the `required*` validation rule before all other rules. Otherwise, the feature of the `bail` validation rule (stopping subsequent validations upon failure) may cause the subsequent `required*` rules not to take effect. + +- Format: `bail` +- Description: As soon as one of the multiple subsequent validations fails, it stops validation and immediately returns the validation result. +- Note: In the framework's `HTTP Server` component, if using the standard route registration method, the automatic validation feature will automatically enable the `bail` modifier rule, and developers do not need to explicitly set `bail` in the `Req` object. +- Example: + + ```go + func Example_Rule_Bail() { + type BizReq struct { + Account string `v:"bail|required|length:6,16|same:QQ"` + QQ string + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + QQ: "123456", + Password: "goframe.org", + Password2: "goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // output: + // The Account value `gf` length must be between 6 and 16 + } + ``` + + +### `foreach` + +- Format: `foreach` +- Description: **Used for array parameters**, it iterates the parameter as an array and applies the subsequent validation rule to each item in the array. +- Version: Framework version `>=v2.2.0` +- Example: + + ```go + func Example_Rule_Foreach() { + type BizReq struct { + Value1 []int `v:"foreach|in:1,2,3"` + Value2 []int `v:"foreach|in:1,2,3"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: []int{1, 2, 3}, + Value2: []int{3, 4, 5}, + } + ) + if err := g.Validator().Bail().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `4` is not in acceptable range: 1,2,3 + } + ``` + + +## Functional Rules + +Functional rules implement specific validation logic, with the framework providing a very rich set of built-in validation rules. + +### `required` + +- Format: `required` +- Description: Required parameter, supporting not only common strings but also `Slice/Map` types. +- Example: The name field `Name` is a required parameter and must not be empty. + + ```go + func Example_Rule_Required() { + type BizReq struct { + ID uint `v:"required"` + Name string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Name field is required + } + ``` + + +### `required-if` + +- Format: `required-if:field,value,...` +- Description: Required parameter (when any of the given field values equals the given value, i.e.: when the `field`'s value is `value`, the current validation field is a required parameter). Multiple fields are separated by `,`. +- Example: When the `Gender` field is `1`, the `WifeName` field must not be empty, and when the `Gender` field is `2`, the `HusbandName` field must not be empty. + + ```go + func Example_Rule_RequiredIf() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string `v:"required-if:gender,1"` + HusbandName string `v:"required-if:gender,2"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The WifeName field is required + } + ``` + +### `required-if-all` + +- Format: `required-if-all:field1,value1,field2,value2,...` +- Description: Required parameter (when all given field values equal the given values, i.e.: when the `field1`'s value is `value1`, and the `field2`'s value is `value2`, and so on, the current validation field is a required parameter). Multiple fields are separated by `,`. +- Example: When the `ID` field is `1`, and the `Age` field is `18`, the `MoreInfo` field must not be empty. + + ```go + func ExampleRule_RequiredIfAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Age int `v:"required" dc:"Your age"` + MoreInfo string `v:"required-if-all:id,1,age,18" dc:"Your more info"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Age: 18, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The MoreInfo field is required + } + ``` + + +### `required-unless` + +- Format: `required-unless:field,value,...` +- Description: Required parameter (when the given field values are all not equal to the given values, i.e.: when the `field`'s value is not `value`, the current validation field is a required parameter). Multiple fields are separated by `,`. +- Example: When `Gender` is not equal to `0` and `Gender` is not equal to `2`, the `WifeName` must not be empty; when `Id` is not equal to `0` and `Gender` is not equal to `2`, the `HusbandName` must not be empty. + + ```go + func Example_Rule_RequiredUnless() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string `v:"required-unless:gender,0,gender,2"` + HusbandName string `v:"required-unless:id,0,gender,2"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The WifeName field is required; The HusbandName field is required + } + ``` + + + + +### `required-with` + +- Format: `required-with:field1,field2,...` +- Description: Required parameter (when any of the given field values is not empty). +- Example: When `WifeName` is not empty, `HusbandName` must not be empty. + + ```go + func Example_Rule_RequiredWith() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-with:WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + WifeName: "Ann", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-with-all` + +- Format: `required-with-all:field1,field2,...` +- Description: Required parameter (when all of the given field values are not empty). +- Example: When `Id, Name, Gender, WifeName` are all not empty, `HusbandName` must not be empty. + + ```go + func Example_Rule_RequiredWithAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-with-all:Id,Name,Gender,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + WifeName: "Ann", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-without` + +- Format: `required-without:field1,field2,...` +- Description: Required parameter (when any of the given field values is empty). +- Example: When `Id` or `WifeName` is empty, `HusbandName` must not be empty. + + ```go + func Example_Rule_RequiredWithout() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-without:Id,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-without-all` + +- Format: `required-without-all:field1,field2,...` +- Description: Required parameter (when all of the given field values are empty). +- Example: When `Id` and `WifeName` are both empty, `HusbandName` must not be empty. + + ```go + func Example_Rule_RequiredWithoutAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-without-all:Id,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `date` + +- Format: `date` +- Description: Parameter is in common date format, supporting separators `-` or `/` or `.`, and also supports 8-digit date without separators, formats like: `2006-01-02`, `2006/01/02`, `2006.01.02`, `20060102` +- Example: + + ```go + func Example_Rule_Date() { + type BizReq struct { + Date1 string `v:"date"` + Date2 string `v:"date"` + Date3 string `v:"date"` + Date4 string `v:"date"` + Date5 string `v:"date"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-10-31", + Date2: "2021.10.31", + Date3: "2021-Oct-31", + Date4: "2021 Octa 31", + Date5: "2021/Oct/31", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date3 value `2021-Oct-31` is not a valid date + // The Date4 value `2021 Octa 31` is not a valid date + // The Date5 value `2021/Oct/31` is not a valid date + } + ``` + + +### `datetime` + +- Format: `datetime` +- Description: Parameter is in common date-time format; only `-` is supported as a separator between dates, formats like: `2006-01-02 12:00:00` +- Example: + + ```go + func Example_Rule_Datetime() { + type BizReq struct { + Date1 string `v:"datetime"` + Date2 string `v:"datetime"` + Date3 string `v:"datetime"` + Date4 string `v:"datetime"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-11-01 23:00:00", + Date2: "2021-11-01 23:00", // error + Date3: "2021/11/01 23:00:00", // error + Date4: "2021/Dec/01 23:00:00", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date2 value `2021-11-01 23:00` is not a valid datetime + // The Date3 value `2021/11/01 23:00:00` is not a valid datetime + // The Date4 value `2021/Dec/01 23:00:00` is not a valid datetime + } + ``` + + +### `date-format` + +- Format: `date-format:format` +- Description: Checks whether the date is in the specified date/time format, with the `format` parameter in `gtime` date format (can include date and time), format description refer to section: [Time](../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) +- Example: `date-format:Y-m-d H:i:s` + + ```go + func Example_Rule_DateFormat() { + type BizReq struct { + Date1 string `v:"date-format:Y-m-d"` + Date2 string `v:"date-format:Y-m-d"` + Date3 string `v:"date-format:Y-m-d H:i:s"` + Date4 string `v:"date-format:Y-m-d H:i:s"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-11-01", + Date2: "2021-11-01 23:00", // error + Date3: "2021-11-01 23:00:00", + Date4: "2021-11-01 23:00", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date2 value `2021-11-01 23:00` does not match the format: Y-m-d + // The Date4 value `2021-11-01 23:00` does not match the format: Y-m-d H:i:s + } + ``` + + +### `before` + +- Format: `before:field` +- Description: Checks whether the given date/time is before the date/time of the specified field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_Before() { + type BizReq struct { + Time1 string `v:"before:Time3"` + Time2 string `v:"before:Time3"` + Time3 string + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-03", + Time3: "2022-09-03", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Time2 value `2022-09-03` must be before field Time3 value `2022-09-03` + } + ``` + + +### `before-equal` + +- Format: `before-equal:field` +- Description: Checks whether the given date/time is before or equal to the date/time of the specified field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_BeforeEqual() { + type BizReq struct { + Time1 string `v:"before-equal:Time3"` + Time2 string `v:"before-equal:Time3"` + Time3 string + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-01", + Time3: "2022-09-01", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Time1 value `2022-09-02` must be before or equal to field Time3 + } + ``` + + +### `after` + +- Format: `after:field` +- Description: Checks whether the given date/time is after the date/time of the specified field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_After() { + type BizReq struct { + Time1 string + Time2 string `v:"after:Time1"` + Time3 string `v:"after:Time1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-01", + Time2: "2022-09-01", + Time3: "2022-09-02", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Time2 value `2022-09-01` must be after field Time1 value `2022-09-01` + } + ``` + + +### `after-equal` + +- Format: `after-equal:field` +- Description: Checks whether the given date/time is after or equal to the date/time of the specified field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_AfterEqual() { + type BizReq struct { + Time1 string + Time2 string `v:"after-equal:Time1"` + Time3 string `v:"after-equal:Time1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-01", + Time3: "2022-09-02", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Time2 value `2022-09-01` must be after or equal to field Time1 value `2022-09-02` + } + ``` + + +### `array` + +- Format: `array` +- Description: Checks whether the given parameter is in array format. If the given parameter is a `JSON` array string, validation will also pass. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_Array() { + type BizReq struct { + Value1 string `v:"array"` + Value2 string `v:"array"` + Value3 string `v:"array"` + Value4 []string `v:"array"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: "1,2,3", + Value2: "[]", + Value3: "[1,2,3]", + Value4: []string{}, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Value1 value `1,2,3` is not of valid array type + } + ``` + + +### `enums` + +- Format: `enums` +- Description: Validates whether the submitted parameter is within the enum values of the field type. This rule needs to be used in conjunction with the `gf gen enums` command, for more details refer to: [Enums Maintenance](../../开发工具/代码生成-gen/枚举维护-gen%20enums.md) + + ```go + func ExampleRule_Enums() { + type Status string + const ( + StatusRunning Status = "Running" + StatusOffline Status = "Offline" + ) + type BizReq struct { + Id int `v:"required"` + Name string `v:"required"` + Status Status `v:"enums"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Id: 1, + Name: "john", + Status: Status("Pending"), + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // May Output: + // The Status value `Pending` should be in enums of: ["Running","Offline"] + } + ``` + + +### `email` + +- Format: `email` +- Description: `EMAIL` address format. + + ```go + func Example_Rule_Email() { + type BizReq struct { + MailAddr1 string `v:"email"` + MailAddr2 string `v:"email"` + MailAddr3 string `v:"email"` + MailAddr4 string `v:"email"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MailAddr1: "gf@goframe.org", + MailAddr2: "gf@goframe", // error + MailAddr3: "gf@goframe.org.cn", + MailAddr4: "gf#goframe.org", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The MailAddr2 value `gf@goframe` is not a valid email address + // The MailAddr4 value `gf#goframe.org` is not a valid email address + } + ``` + + +### `phone` + +- Format: `phone` +- Description: Mobile phone number format for China. + + ```go + func Example_Rule_Phone() { + type BizReq struct { + PhoneNumber1 string `v:"phone"` + PhoneNumber2 string `v:"phone"` + PhoneNumber3 string `v:"phone"` + PhoneNumber4 string `v:"phone"` + } + + var ( + ctx = context.Background() + req = BizReq{ + PhoneNumber1: "13578912345", + PhoneNumber2: "11578912345", // error 11x not exist + PhoneNumber3: "11178912345", // error 171 not exit + PhoneNumber4: "1357891234", // error len must be 11 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The PhoneNumber2 value `11578912345` is not a valid phone number + // The PhoneNumber3 value `11178912345` is not a valid phone number + // The PhoneNumber4 value `1357891234` is not a valid phone number + } + ``` + + +### `phone-loose` + +- Format: `phone` +- Description: Loose phone number verification, as long as it starts with `13, 14, 15, 16, 17, 18, 19` and is an 11-digit number, verification will pass. This can be used in less strict business scenarios. + + ```go + func Example_Rule_PhoneLoose() { + type BizReq struct { + PhoneNumber1 string `v:"phone-loose"` + PhoneNumber2 string `v:"phone-loose"` + PhoneNumber3 string `v:"phone-loose"` + PhoneNumber4 string `v:"phone-loose"` + } + + var ( + ctx = context.Background() + req = BizReq{ + PhoneNumber1: "13578912345", + PhoneNumber2: "11578912345", // error 11x not exist + PhoneNumber3: "17178912345", + PhoneNumber4: "1357891234", // error len must be 11 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The PhoneNumber2 value `11578912345` is invalid + // The PhoneNumber4 value `1357891234` is invalid + } + ``` + + +### `telephone` + +- Format: `telephone` +- Description: Landline number in China, formats like “XXXX-XXXXXXX”, “XXXX-XXXXXXXX”, “XXX-XXXXXXX”, “XXX-XXXXXXXX”, “XXXXXXX”, “XXXXXXXX”. + + ```go + func Example_Rule_Telephone() { + type BizReq struct { + Telephone1 string `v:"telephone"` + Telephone2 string `v:"telephone"` + Telephone3 string `v:"telephone"` + Telephone4 string `v:"telephone"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Telephone1: "010-77542145", + Telephone2: "0571-77542145", + Telephone3: "20-77542145", // error + Telephone4: "775421451", // error len must be 7 or 8 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Telephone3 value `20-77542145` is not a valid telephone number + // The Telephone4 value `775421451` is not a valid telephone number + } + ``` + + +### `passport` + +- Format: `passport` +- Description: General account rule (**starts with a letter, can only contain letters, numbers, and underscores, with a length between 6~18**). + + ```go + func Example_Rule_Passport() { + type BizReq struct { + Passport1 string `v:"passport"` + Passport2 string `v:"passport"` + Passport3 string `v:"passport"` + Passport4 string `v:"passport"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Passport1: "goframe", + Passport2: "1356666", // error starting with letter + Passport3: "goframe#", // error containing only numbers or underscores + Passport4: "gf", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Passport2 value `1356666` is not a valid passport format + // The Passport3 value `goframe#` is not a valid passport format + // The Passport4 value `gf` is not a valid passport format + } + ``` + + +### `password` + +- Format: `password` +- Description: General password rule (**any visible character, length between 6~18**). + + ```go + func Example_Rule_Password() { + type BizReq struct { + Password1 string `v:"password"` + Password2 string `v:"password"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "goframe", + Password2: "gofra", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + } + ``` + + +### `password2` + +- Format: `password2` +- Description: Medium strength password (**on the basis of general password rule, it must contain uppercase and lowercase letters and numbers**). + + ```go + func Example_Rule_Password2() { + type BizReq struct { + Password1 string `v:"password2"` + Password2 string `v:"password2"` + Password3 string `v:"password2"` + Password4 string `v:"password2"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "Goframe123", + Password2: "gofra", // error length between 6 and 18 + Password3: "Goframe", // error must contain lower and upper letters and numbers. + Password4: "goframe123", // error must contain lower and upper letters and numbers. + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + // The Password3 value `Goframe` is not a valid password format + // The Password4 value `goframe123` is not a valid password format + } + ``` + + +### `password3` + +- Format: `password3` +- Description: Strong strength password (**on the basis of general password rule, it must contain uppercase and lowercase letters, numbers, and special characters**). + + ```go + func Example_Rule_Password3() { + type BizReq struct { + Password1 string `v:"password3"` + Password2 string `v:"password3"` + Password3 string `v:"password3"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "Goframe123#", + Password2: "gofra", // error length between 6 and 18 + Password3: "Goframe123", // error must contain lower and upper letters, numbers and special chars. + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + // The Password3 value `Goframe123` is not a valid password format + } + ``` + + +### `postcode` + +- Format: `postcode` +- Description: Postal code rule for China. + + ```go + func Example_Rule_Postcode() { + type BizReq struct { + Postcode1 string `v:"postcode"` + Postcode2 string `v:"postcode"` + Postcode3 string `v:"postcode"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Postcode1: "100000", + Postcode2: "10000", // error length must be 6 + Postcode3: "1000000", // error length must be 6 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Postcode2 value `10000` is not a valid postcode format + // The Postcode3 value `1000000` is not a valid postcode format + } + ``` + + +### `resident-id` + +- Format: resident-id +- Description: Resident ID card number. + + ```go + func Example_Rule_ResidentId() { + type BizReq struct { + ResidentID1 string `v:"resident-id"` + } + + var ( + ctx = context.Background() + req = BizReq{ + ResidentID1: "320107199506285482", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The ResidentID1 value `320107199506285482` is not a valid resident id number + } + ``` + + +### `bank-card` + +- Format: `bank-card` +- Description: Bank card number validation for China. + + ```go + func Example_Rule_BankCard() { + type BizReq struct { + BankCard1 string `v:"bank-card"` + } + + var ( + ctx = context.Background() + req = BizReq{ + BankCard1: "6225760079930218", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The BankCard1 value `6225760079930218` is not a valid bank card number + } + ``` + + +### `qq` + +- Format: `qq` +- Description: Tencent QQ number rule. + + ```go + func Example_Rule_QQ() { + type BizReq struct { + QQ1 string `v:"qq"` + QQ2 string `v:"qq"` + QQ3 string `v:"qq"` + } + + var ( + ctx = context.Background() + req = BizReq{ + QQ1: "389961817", + QQ2: "9999", // error >= 10000 + QQ3: "514258412a", // error all number + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The QQ2 value `9999` is not a valid QQ number + // The QQ3 value `514258412a` is not a valid QQ number + } + ``` + + +### `ip` + +- Format: `ip` +- Description: `IPv4/IPv6` address. + + ```go + func Example_Rule_IP() { + type BizReq struct { + IP1 string `v:"ip"` + IP2 string `v:"ip"` + IP3 string `v:"ip"` + IP4 string `v:"ip"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "127.0.0.1", + IP2: "fe80::812b:1158:1f43:f0d1", + IP3: "520.255.255.255", // error >= 10000 + IP4: "ze80::812b:1158:1f43:f0d1", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The IP3 value `520.255.255.255` is not a valid IP address + // The IP4 value `ze80::812b:1158:1f43:f0d1` is not a valid IP address + } + ``` + + +### `ipv4` + +- Format: `ipv4` +- Description: `IPv4` address. + + ```go + func Example_Rule_IPV4() { + type BizReq struct { + IP1 string `v:"ipv4"` + IP2 string `v:"ipv4"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "127.0.0.1", + IP2: "520.255.255.255", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The IP2 value `520.255.255.255` is not a valid IPv4 address + } + ``` + + +### `ipv6` + +- Format: `ipv6` +- Description: `IPv6` address. + + ```go + func Example_Rule_IPV6() { + type BizReq struct { + IP1 string `v:"ipv6"` + IP2 string `v:"ipv6"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "fe80::812b:1158:1f43:f0d1", + IP2: "ze80::812b:1158:1f43:f0d1", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The IP2 value `ze80::812b:1158:1f43:f0d1` is not a valid IPv6 address + } + ``` + + +### `mac` + +- Format: `mac` +- Description: `MAC` address. + + ```go + func Example_Rule_Mac() { + type BizReq struct { + Mac1 string `v:"mac"` + Mac2 string `v:"mac"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Mac1: "4C-CC-6A-D6-B1-1A", + Mac2: "Z0-CC-6A-D6-B1-1A", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Mac2 value `Z0-CC-6A-D6-B1-1A` is not a valid MAC address + } + ``` + + +### `url` + +- Format: `url` +- Description: URL +- Example: Supports addresses starting with `http, https, ftp, file`. + + ```go + func Example_Rule_Url() { + type BizReq struct { + URL1 string `v:"url"` + URL2 string `v:"url"` + URL3 string `v:"url"` + } + + var ( + ctx = context.Background() + req = BizReq{ + URL1: "http://goframe.org", + URL2: "ftp://goframe.org", + URL3: "ws://goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The URL3 value `ws://goframe.org` is not a valid URL address + } + ``` + + +### `domain` + +- Format: `domain` +- Description: Domain +- Example: Domain rule. `xxx.yyy` (must start with a letter). + + ```go + func Example_Rule_Domain() { + type BizReq struct { + Domain1 string `v:"domain"` + Domain2 string `v:"domain"` + Domain3 string `v:"domain"` + Domain4 string `v:"domain"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Domain1: "goframe.org", + Domain2: "a.b", + Domain3: "goframe#org", + Domain4: "1a.2b", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Domain3 value `goframe#org` is not a valid domain format + // The Domain4 value `1a.2b` is not a valid domain format + } + ``` + + +### `size` + +- Format: `size:size` +- Description: The parameter **length** is `size` (length parameter is integer), note that `Unicode` is used for length calculation at the underlying layer, thus a Chinese character takes up `1` length unit. + + ```go + func Example_Rule_Size() { + type BizReq struct { + Size1 string `v:"size:10"` + Size2 string `v:"size:5"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Size1: "goframe欢迎你", + Size2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Size2 value `goframe` length must be 5 + } + ``` + + +### `length` + +- Format: `length:min,max` +- Description: The parameter **length** is between `min` and `max` (length parameter is integer), note that `Unicode` is used for length calculation at the underlying layer, thus a Chinese character takes up `1` length unit. + + ```go + func Example_Rule_Length() { + type BizReq struct { + Length1 string `v:"length:5,10"` + Length2 string `v:"length:10,15"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Length1: "goframe欢迎你", + Length2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Length2 value `goframe` length must be between 10 and 15 + } + ``` + + +### `min-length` + +- Format: `min-length:min` +- Description: The parameter **length** minimum is `min` (length parameter is integer), note that `Unicode` is used for length calculation at the underlying layer, thus a Chinese character takes up `1` length unit. + + ```go + func Example_Rule_MinLength() { + type BizReq struct { + MinLength1 string `v:"min-length:10"` + MinLength2 string `v:"min-length:8"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MinLength1: "goframe欢迎你", + MinLength2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The MinLength2 value `goframe` length must be equal or greater than 8 + } + ``` + + +### `max-length` + +- Format: `max-length:max` +- Description: The parameter **length** maximum is `max` (length parameter is integer), note that `Unicode` is used for length calculation at the underlying layer, thus a Chinese character takes up `1` length unit. + + ```go + func Example_Rule_MaxLength() { + type BizReq struct { + MaxLength1 string `v:"max-length:10"` + MaxLength2 string `v:"max-length:5"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MaxLength1: "goframe欢迎你", + MaxLength2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The MaxLength2 value `goframe` length must be equal or lesser than 5 + } + ``` + + +### `between` + +- Format: `between:min,max` +- Description: The parameter **size** is between `min` and `max` (supports integer and float type parameters). + + ```go + func Example_Rule_Between() { + type BizReq struct { + Age1 int `v:"between:1,100"` + Age2 int `v:"between:1,100"` + Score1 float32 `v:"between:0,10"` + Score2 float32 `v:"between:0,10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 50, + Age2: 101, + Score1: 9.8, + Score2: -0.5, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age2 value `101` must be between 1 and 100 + // The Score2 value `-0.5` must be between 0 and 10 + } + ``` + + +### `min` + +- Format: `min:min` +- Description: The parameter **size** minimum is `min` (supports integer and float type parameters). + + ```go + func Example_Rule_Min() { + type BizReq struct { + Age1 int `v:"min:100"` + Age2 int `v:"min:100"` + Score1 float32 `v:"min:10"` + Score2 float32 `v:"min:10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 50, + Age2: 101, + Score1: 9.8, + Score2: 10.1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age1 value `50` must be equal or greater than 100 + // The Score1 value `9.8` must be equal or greater than 10 + } + ``` + + +### `max` + +- Format: `max:max` +- Description: The parameter **size** maximum is `max` (supports integer and float type parameters). + + ```go + func Example_Rule_Max() { + type BizReq struct { + Age1 int `v:"max:100"` + Age2 int `v:"max:100"` + Score1 float32 `v:"max:10"` + Score2 float32 `v:"max:10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 99, + Age2: 101, + Score1: 9.9, + Score2: 10.1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age2 value `101` must be equal or lesser than 100 + // The Score2 value `10.1` must be equal or lesser than 10 + } + ``` + + +### `json` + +- Format: `json` +- Description: Checks whether the data format is `JSON`. + + ```go + func Example_Rule_Json() { + type BizReq struct { + JSON1 string `v:"json"` + JSON2 string `v:"json"` + } + + var ( + ctx = context.Background() + req = BizReq{ + JSON1: "{\"name\":\"goframe\",\"author\":\"郭强\"}", + JSON2: "{\"name\":\"goframe\",\"author\":\"郭强\",\"test\"}", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The JSON2 value `{"name":"goframe","author":"郭强","test"}` is not a valid JSON string + } + ``` + + +### `integer` + +- Format: `integer` +- Description: Integer (positive or negative). + + ```go + func Example_Rule_Integer() { + type BizReq struct { + Integer string `v:"integer"` + Float string `v:"integer"` + Str string `v:"integer"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Integer: "100", + Float: "10.0", + Str: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Float value `10.0` is not an integer + // The Str value `goframe` is not an integer + } + ``` + + +### `float` + +- Format: `float` +- Description: Float numbers. + + ```go + func Example_Rule_Float() { + type BizReq struct { + Integer string `v:"float"` + Float string `v:"float"` + Str string `v:"float"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Integer: "100", + Float: "10.0", + Str: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Str value `goframe` is invalid + } + ``` + + +### `boolean` + +- Format: `boolean` +- Description: Boolean value (`1`, `true`, `on`, `yes` for `true` \| `0`, `false`, `off`, `no`, `""` for `false`). + + ```go + func Example_Rule_Boolean() { + type BizReq struct { + Boolean bool `v:"boolean"` + Integer int `v:"boolean"` + Float float32 `v:"boolean"` + Str1 string `v:"boolean"` + Str2 string `v:"boolean"` + Str3 string `v:"boolean"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Boolean: true, + Integer: 1, + Float: 10.0, + Str1: "on", + Str2: "", + Str3: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Float value `10` field must be true or false + // The Str3 value `goframe` field must be true or false + } + ``` + + +### `same` + +- Format: `same:field` +- Description: The parameter value must be the same as the value of the `field` parameter. +- Example: During user registration, the submitted password `Password` and confirm password `Password2` must be equal (server-side validation). + + ```go + func Example_Rule_Same() { + type BizReq struct { + Name string `v:"required"` + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + Password: "goframe.org", + Password2: "goframe.net", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Password value `goframe.org` must be the same as field Password2 + } + ``` + + +### `different` + +- Format: `different:field` +- Description: Parameter value cannot be the same as the field parameter value. +- Example: Backup email `OtherMailAddr` and email address `MailAddr` must be different. + + ```go + func Example_Rule_Different() { + type BizReq struct { + Name string `v:"required"` + MailAddr string `v:"required"` + ConfirmMailAddr string `v:"required|different:MailAddr"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + MailAddr: "gf@goframe.org", + ConfirmMailAddr: "gf@goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The ConfirmMailAddr value `gf@goframe.org` must be different from field MailAddr + } + ``` + + +### `eq` + +- Format: `eq:field` +- Description: Parameter value must be the same as the value of the `field` parameter. Alias for `same` rule, identical to `same` rule. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_EQ() { + type BizReq struct { + Name string `v:"required"` + Password string `v:"required|eq:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + Password: "goframe.org", + Password2: "goframe.net", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Password value `goframe.org` must be equal to field Password2 value `goframe.net` + } + ``` + + +### `not-eq` + +- Format: `not-eq:field` +- Description: Parameter value must be different from the field's parameter value. Alias for `different` rule, identical to `different` rule. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_NotEQ() { + type BizReq struct { + Name string `v:"required"` + MailAddr string `v:"required"` + OtherMailAddr string `v:"required|not-eq:MailAddr"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + MailAddr: "gf@goframe.org", + OtherMailAddr: "gf@goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The OtherMailAddr value `gf@goframe.org` must not be equal to field MailAddr value `gf@goframe.org` + } + ``` + + +### `gt` + +- Format: `gt:field` +- Description: Parameter value must be greater than the value corresponding to the given field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_GT() { + type BizReq struct { + Value1 int + Value2 int `v:"gt:Value1"` + Value3 int `v:"gt:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 1, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `1` must be greater than field Value1 value `1` + } + ``` + + +### `gte` + +- Format: `gte:field` +- Description: Parameter value must be greater than or equal to the value corresponding to the given field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_GTE() { + type BizReq struct { + Value1 int + Value2 int `v:"gte:Value1"` + Value3 int `v:"gte:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 2, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `1` must be greater than or equal to field Value1 value `2` + } + ``` + + +### `lt` + +- Format: `lt:field` +- Description: Parameter value must be less than the value corresponding to the given field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_LT() { + type BizReq struct { + Value1 int + Value2 int `v:"lt:Value1"` + Value3 int `v:"lt:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 2, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value3 value `2` must be lesser than field Value1 value `2` + } + ``` + + +### `lte` + +- Format: `lte:field` +- Description: Parameter value must be less than or equal to the value corresponding to the given field. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_LTE() { + type BizReq struct { + Value1 int + Value2 int `v:"lte:Value1"` + Value3 int `v:"lte:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 1, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value3 value `2` must be lesser than or equal to field Value1 value `1` + } + ``` + + +### `in` + +- Format: `in:value1,value2,...` +- Description: Parameter value should be within `value1,value2,...` (string matching). +- Example: The value of the gender field `Gender` must be within `0/1/2`. + + ```go + func Example_Rule_In() { + type BizReq struct { + ID uint `v:"required" dc:"Your Id"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 3, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Gender value `3` is not in acceptable range: 0,1,2 + } + ``` + + +### `not-in` + +- Format: `not-in:value1,value2,...` +- Description: Parameter value should not be within `value1,value2,...` (string matching). +- Example: The invalid index `InvalidIndex` must not be within `-1/0/1`. + + ```go + func Example_Rule_NotIn() { + type BizReq struct { + ID uint `v:"required" dc:"Your Id"` + Name string `v:"required" dc:"Your name"` + InvalidIndex uint `v:"not-in:-1,0,1"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + InvalidIndex: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The InvalidIndex value `1` must not be in range: -1,0,1 + } + ``` + + +### `regex` + +- Format: `regex:pattern` +- Description: The parameter value should meet the regex matching rule `pattern`. + + ```go + func Example_Rule_Regex() { + type BizReq struct { + Regex1 string `v:"regex:[1-9][0-9]{4,14}"` + Regex2 string `v:"regex:[1-9][0-9]{4,14}"` + Regex3 string `v:"regex:[1-9][0-9]{4,14}"` + } + var ( + ctx = context.Background() + req = BizReq{ + Regex1: "1234", + Regex2: "01234", + Regex3: "10000", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Regex1 value `1234` must be in regex of: [1-9][0-9]{4,14} + // The Regex2 value `01234` must be in regex of: [1-9][0-9]{4,14} + } + ``` + + +### `not-regex` + +- Format: `not-regex:pattern` +- Description: The parameter value should not meet the regex matching rule `pattern`. +- Version: Framework version `>=v2.2.0` + + ```go + func Example_Rule_NotRegex() { + type BizReq struct { + Regex1 string `v:"regex:\\d{4}"` + Regex2 string `v:"not-regex:\\d{4}"` + } + var ( + ctx = context.Background() + req = BizReq{ + Regex1: "1234", + Regex2: "1234", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Regex2 value `1234` should not be in regex of: \d{4} + } + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" new file mode 100644 index 00000000000..06a9455539e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/core/gvalid-custom-rules' +title: 'Data Validation - Custom' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gvalid, Custom Validation, Data Validation, Business Scenarios, Validation Rules, Flexibility, Reusability, Validation Features] +description: "Using gvalid for data validation with custom rules in the GoFrame framework. By flexible configuration, developers can define validation standards needed by the business, improving code reusability and adaptability, offering stronger validation capabilities for various business scenarios, effectively meeting complex data validation requirements." +--- + +Although `gvalid` has built-in dozens of common validation rules, in some business scenarios, we need to define custom validation rules, especially some business-related validation rules that can be reused. Of course, `gvalid` is so powerful and considerate that it has already thought this through for you. Custom validation rules can achieve flexible and highly reusable validation features. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..ed5c9ea11cb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/core/gvalid-custom-rules-handle-input-parameters' +title: 'Custom Rule - Input Object' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Custom Rule, Complete Data Validation, UserCreateReq, Validation Component, Struct Validation, GoFrame Framework, gvalid, Data Validation, Validation Rule] +description: "Use custom rules in the GoFrame framework to perform complete data validation on structs. By adding the metadata g.Meta to a struct, you can register and use custom validation rules such as UserCreateReq to validate user creation requests. Sample code demonstrates how to implement and apply custom validation methods to ensure data uniqueness and validity." +--- + +## Introduction + +You may have noticed that when we specify a `struct`, our rules can only validate its key-values or attributes. If we want to use the rules to completely validate the `struct` object, we find that we cannot register custom validation rules for the validation component. However, our validation component also supports directly validating the current `struct` object. Let's look at an example where we need to perform complete custom validation on a user creation request and register a validation rule for `UserCreateReq` to achieve this. + +## Usage Example + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type UserCreateReq struct { + g.Meta `v:"UserCreateReq"` + Name string + Pass string +} + +func RuleUserCreateReq(ctx context.Context, in gvalid.RuleFuncInput) error { + var req *UserCreateReq + if err := in.Data.Scan(&req); err != nil { + return gerror.Wrap(err, `Scan data to UserCreateReq failed`) + } + // SELECT COUNT(*) FROM `user` WHERE `name` = xxx + count, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }).Where("name", req.Name).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.Newf(`The name "%s" is already taken by others`, req.Name) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + user = &UserCreateReq{ + Name: "john", + Pass: "123456", + } + ) + err := g.Validator().RuleFunc("UserCreateReq", RuleUserCreateReq).Data(user).Run(ctx) + fmt.Println(err) +} +``` + +As you can see, by embedding `g.Meta` metadata into the struct and binding the custom rule `UserCreateReq`, when we validate the struct object through `CheckStruct`, we can use `UserCreateReq` to achieve validation. + +After executing the above example, the terminal output: + +``` +The name "john" is already taken +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..63407758b1c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" @@ -0,0 +1,287 @@ +--- +slug: '/docs/core/gvalid-custom-rules-register' +title: 'Custom Rule - Registration' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Custom Rules, Data Validation, Rule Registration, Global Validation Rules, Local Validation Rules, i18n Feature, validation, gvalid] +description: "How to perform custom rule registration and data validation under the GoFrame framework. Detailed explanation on the definition method of custom rules, parameter descriptions, and how to register global and local rules. Example code demonstrates the implementation of two common scenarios: order ID existence validation and user uniqueness validation, allowing developers to flexibly apply custom validation rule features." +--- + +## Relevant Data Structure + +Custom rule method definition, and the corresponding input parameter data structure. + +```go +// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc. +type RuleFuncInput struct { + // Rule specifies the validation rule string, like "required", "between:1,100", etc. + Rule string + + // Message specifies the custom error message or configured i18n message for this rule. + Message string + + // Value specifies the value for this rule to validate. + Value *gvar.Var + + // Data specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value. + // You can ignore the parameter `Data` if you do not really need it in your custom validation rule. + Data *gvar.Var +} + +// RuleFunc is the custom function for data validation. +type RuleFunc func(ctx context.Context, in RuleFuncInput) error +``` + +Brief method parameter explanation: + +1. Context parameter `ctx` is required. +2. `RuleFuncInput` data structure explanation: + - `Rule` indicates the current validation rule, including rule parameters, such as: `required`, `between:1,100`, `length:6` etc. + - `Message` specifies the validation error message returned upon validation failure. + - `Value` specifies the data value being validated. Note that the type is a `*gvar.Var` generic, so you can pass any type of parameter. + - `Data` specifies the parameters passed during validation, useful when validating a `map` or `struct`, particularly in joint validation. This value is runtime input and may be `nil`. + +:::tip +Custom errors natively support `i18n` features. You only need to configure i18n translation information following `gf.gvalid.rule.custom_rule_name`. This information is automatically fetched from the i18n manager upon validation failure and passed to your registered custom validation method via the `Message` parameter. +::: + +## Global Validation Rule Registration + +Custom rules are divided into two types: global rule registration and local rule registration. + +Global rules are rules that take effect globally. After registration, they can be used for data validation by method or object usage. + +Register validation method: + +```go +// RegisterRule registers custom validation rule and function for package. +func RegisterRule(rule string, f RuleFunc) { + customRuleFuncMap[rule] = f +} + +// RegisterRuleByMap registers custom validation rules using map for package. +func RegisterRuleByMap(m map[string]RuleFunc) { + for k, v := range m { + customRuleFuncMap[k] = v + } +} +``` + +You need to define a validation method as per `RuleFunc` type, implement the validation you need, and then use `RegisterRule` to register it in the global management of the `gvalid` module. This registration logic is generally executed during program initialization. This method will be automatically called when data validation is performed; returning `nil` indicates validation success, otherwise, a non-empty `error` type value should be returned. + +:::warning +Note: The custom rule registration methods do not support concurrent calls. You need to register it during program startup (such as in the `boot` package); dynamic registration at runtime is not possible, otherwise, it will cause concurrency safety issues. +::: + +### Example 1, Order ID Existence Validation + +In e-commerce business, when we perform operations on orders, we can validate whether the given order ID exists through custom rules. Therefore, we can register a global rule `order-exist` to achieve this. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type Request struct { + OrderId int64 `v:"required|order-exist"` + ProductName string + Amount int64 + // ... +} + +func init() { + rule := "order-exist" + gvalid.RegisterRule(rule, RuleOrderExist) +} + +func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error { + // SELECT COUNT(*) FROM `order` WHERE `id` = xxx + count, err := g.Model("order"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", in.Value.Int64()). + Count() + if err != nil { + return err + } + if count == 0 { + return gerror.Newf(`invalid order id "%d"`, in.Value.Int64()) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + req = &Request{ + OrderId: 65535, + ProductName: "HikingShoe", + Amount: 10000, + } + ) + err := g.Validator().CheckStruct(ctx, req) + fmt.Println(err) +} +``` + +### Example 2, User Uniqueness Rule + +During user registration, we often need to check whether the submitted name/account is unique. Therefore, we can register a global rule `unique-name` to achieve this. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type User struct { + Id int + Name string `v:"required|unique-name#Please enter the user name|User name is already taken"` + Pass string `v:"required|length:6,18"` +} + +func init() { + rule := "unique-name" + gvalid.RegisterRule(rule, RuleUniqueName) +} + +func RuleUniqueName(ctx context.Context, in gvalid.RuleFuncInput) error { + var user *User + if err := in.Data.Scan(&user); err != nil { + return gerror.Wrap(err, `Scan data to user failed`) + } + // SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx + count, err := g.Model("user"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", user.Id). + WhereNot("name", user.Name). + Count() + if err != nil { + return err + } + if count > 0 { + if in.Message != "" { + return gerror.New(in.Message) + } + return gerror.Newf(`user name "%s" is already token by others`, user.Name) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + user = &User{ + Id: 1, + Name: "john", + Pass: "123456", + } + ) + err := g.Validator().CheckStruct(ctx, user) + fmt.Println(err) +} +``` + +## Local Validation Rule Registration + +Local rules are rules that take effect only within the current validation object. Validation rules are registered into the current chain operation process being used rather than globally. + +Registration method: + +```go +// RuleFunc registers one custom rule function to current Validator. +func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator + +// RuleFuncMap registers multiple custom rule functions to current Validator. +func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator +``` + +Brief introduction: + +- `RuleFunc` method is used to register a single custom validation rule to the current object. +- `RuleFuncMap` method is used to register multiple custom validation rules to the current object. + +Usage example: + +We will change one of the examples above to local validation rule registration. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type Request struct { + OrderId int64 + ProductName string + Amount int64 + // ... +} + +func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error { + // SELECT COUNT(*) FROM `order` WHERE `id` = xxx + count, err := g.Model("order"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", in.Value.Int64()). + Count() + if err != nil { + return err + } + if count == 0 { + return gerror.Newf(`invalid order id "%d"`, in.Value.Int64()) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + req = &Request{ + OrderId: 65535, + ProductName: "HikingShoe", + Amount: 10000, + } + ) + err := g.Validator().RuleFunc("order-exist", RuleOrderExist).Data(req).Run(ctx) + fmt.Println(err) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" new file mode 100644 index 00000000000..b7bb58aa343 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" @@ -0,0 +1,105 @@ +--- +slug: '/docs/core/gvalid-custom-validating-message' +title: 'Data Validation - Error Msg' +sidebar_position: 7 +hide_title: true +keywords: [Data Validation, Custom Error, GoFrame, GoFrame Framework, i18n, Internationalization, Validation Message, Chinese Error Message, English Error Message, Error Message Configuration] +description: "When using the data validation component of the GoFrame framework, customize error messages and support the i18n internationalization feature. By combining with the i18n component, you can easily set error messages in different languages. The document explains in detail how to configure i18n files in English and Chinese, and how to set the error message language through middleware, helping developers to handle internationalization more efficiently." +--- + +The data validation component supports the `i18n` feature, implemented internally using the unified `i18n` component of the `goframe` framework. By default, it uses the default `i18n` singleton object, i.e., the `g.I18n()` object. + +Before further use, please refer to the chapter on configuring and using the `i18n` internationalization feature: [I18N](../I18N国际化/I18N国际化.md) + +## Configuration Example + +### Default `i18n` Error Message + +The reference for the default English internationalization language configuration file: [https://github.com/gogf/gf/tree/master/util/gvalid/i18n/en](https://github.com/gogf/gf/tree/master/util/gvalid/i18n/en) + +### Chinese Error Message + +We provide a recommended Chinese `i18n` internationalization language configuration file: [https://github.com/gogf/gf/tree/master/util/gvalid/i18n/cn](https://github.com/gogf/gf/tree/master/util/gvalid/i18n/cn) + +### Default Error Message + +When the corresponding rule's error message cannot be found in `i18n`, it will use the error message configured in `__default__`. This is often used in custom rules. + +## Development Example + +We set the error message `i18n` language for requests uniformly through middleware. + +### Directory Structure +:::warning +Pay attention to the project directory structure so that the default `g.i18n()` object can automatically read the configuration. A significant number of users stumble here. +::: +``` +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +### `i18n` Files + +`en.toml` + +``` +"ReuiredUserName" = "Please input user name" +"ReuiredUserType" = "Please select user type" +``` + +`zh-CN.toml` + +``` +"ReuiredUserName" = "请输入用户名称" +"ReuiredUserType" = "请选择用户类型" +``` + +### Example Code + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + } + var ( + ctx = gctx.New() + data = g.Map{ + "name": "john", + } + user = User{} + ctxEn = gi18n.WithLanguage(ctx, "en") + ctxCh = gi18n.WithLanguage(ctx, "zh-CN") + ) + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + // English + if err := g.Validator().Assoc(data).Data(user).Run(ctxEn); err != nil { + g.Dump(err.String()) + } + // Chinese + if err := g.Validator().Assoc(data).Data(user).Run(ctxCh); err != nil { + g.Dump(err.String()) + } +} +``` + +After execution, the terminal output: + +``` +Please select user type +请选择用户类型 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..c63df0d2138 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" @@ -0,0 +1,205 @@ +--- +slug: '/docs/core/gvalid-recursive-validating' +title: 'Data Validation - Recursive' +sidebar_position: 5 +hide_title: true +keywords: [Data Validation, Recursive Validation, GoFrame, GoFrame Framework, Nested Validation, Struct Validation, Map Validation, Slice Validation, Validation Component, GoFrame Validator] +description: "The recursive validation feature of the data validation component in the GoFrame framework. Through example code, it demonstrates how to perform nested validation on struct, slice, and map types of data to achieve effective validation of complex data structures. Special note is made on handling empty objects in recursive validation, and empty objects with default values are considered passed and validated." +--- + +The validation component supports powerful recursive validation (nested validation) features. If the properties or key values in the given validation data are of type `struct/map/slice`, recursive validation will be automatically performed. Let's look at a few examples: + +## Example 1, Recursive Validation: `struct` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type SearchReq struct { + Key string `v:"required"` + Option SearchOption +} + +type SearchOption struct { + Page int `v:"min:1"` + Size int `v:"max:100"` +} + +func main() { + var ( + ctx = gctx.New() + req = SearchReq{ + Key: "GoFrame", + Option: SearchOption{ + Page: 1, + Size: 10000, + }, + } + ) + err := g.Validator().Data(req).Run(ctx) + fmt.Println(err) +} +``` + +After execution, the terminal outputs: + +``` +The Size value `10000` must be equal or lesser than 100 +``` + +## Example 2, Recursive Validation: `slice` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required#Student Name is required"` + Age int + } + type Teacher struct { + Name string + Students []Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "name": "john", + "students": `[{"age":2},{"name":"jack", "age":4}]`, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +After execution, the terminal outputs: + +``` +Student Name is required +``` + +## Example 3, Recursive Validation: `map` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required#Student Name is required"` + Age int + } + type Teacher struct { + Name string + Students map[string]Student + } + var ( + ctx = gctx.New() + teacher = Teacher{ + Name: "Smith", + Students: map[string]Student{ + "john": {Name: "", Age: 18}, + }, + } + ) + err := g.Validator().Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +After execution, the terminal outputs: + +``` +Student Name is required +``` + +## Note: Impact of Empty Objects on Recursive Validation + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required"` + } + type Teacher struct { + Students Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "students": nil, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +After execution, the terminal outputs: + +``` +Student Name is required +``` + +Some may find it strange why the `Name` field of the `Student` struct is validated even though the `Student` field value was not passed. This is because the `Student` property is an empty struct with default values (the `Name` default value is an empty string). In recursive validation, although `Student` is not a required parameter, meaning you could choose not to pass it, if it is passed, it will be validated according to the validation rules of the properties within it (an empty object with default values is also considered to have a value). You can compare this with the differences in the code below: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required"` + } + type Teacher struct { + Students *Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "students": nil, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +The only difference from the previous example is that the `Student` property has been changed from a struct to a struct pointer `*Student`, thus the property is no longer an empty object with default values. After execution, the terminal outputs nothing, indicating validation passed. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..7f000350eed --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gvalid' +title: 'Data Validation' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame framework, data validation, form validation, gvalid, validation rules, custom validation, internationalization, struct attribute validation, server automation validation] +description: "The gvalid component in the GoFrame framework is a powerful, flexible, and easily extensible tool for data and form validation. The gvalid component offers a variety of common validation rules, supports multi-data and multi-rule validation, custom error messages, internationalization processing, and more, making it the most powerful data validation module in Go language." +--- + +## Introduction + +The `goframe` framework provides a powerful, easy-to-use, flexible, and easily extensible data/form validation component implemented by the `gvalid` component. The `gvalid` component implements a very powerful data validation functionality, with dozens of built-in common validation rules. It supports single data multiple rule validation, multi-data multi-rule batch validation, custom error messages, custom regular expression validation, custom validation rule registration, supports `i18n` internationalization processing, supports `struct tag` rules and prompt information binding and other features, making it currently the most powerful `Go` data validation module. +:::tip +The design inspiration for data validation comes from the classic `PHP Laravel` framework [https://laravel.com/docs/8.x/validation](https://laravel.com/docs/8.x/validation). Thank you `Laravel` ❤️ +::: +**Usage**: + +```go +import "github.com/gogf/gf/v2/util/gvalid" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +## Features + +The `gvalid` component has the following notable features: + +- Built-in dozens of common data validation rules, supporting most business scenarios +- Supports `Server` layer and command-line component automated validation +- Supports basic types and complex object type parameter validation +- Supports sequential validation and flexible validation result handling +- Supports custom validation error messages +- Supports recursive validation of struct attributes +- Supports custom validation rules +- Supports internationalization feature `I18n` + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" new file mode 100644 index 00000000000..bce5d3cf251 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/core/glog-context' +title: 'Logging - Context' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame framework,glog,log component,Context,CtxKeys,log output,OpenTelemetry,trace,Handler] +description: "Usage of glog log component in the GoFrame framework, particularly how to achieve log printing through Context variables. The article provides a detailed explanation on configuring and using custom CtxKeys, and offers functionalities supporting trace. Additionally, it covers the implementation of the log Handler to help developers better integrate logging features." +--- + +Starting from version `v2`, the `glog` component uses the `ctx` context variable as a necessary parameter for log printing. + +## Custom `CtxKeys` + +The log component supports custom key-value printing by reading from the `ctx` context variable. + +### Configuration Usage + +```yaml +# Log component configuration +logger: + Level: "all" + Stdout: true + CtxKeys: ["RequestId", "UserId"] +``` + +Here, `CtxKeys` is used to configure the key names that need to be read and output from the `context.Context` interface object. + +### Log Output + +With the above configuration, when outputting logs, specify the output `context.Context` interface object using the `Ctx` chained operation method. Please note **not to use a custom type as the Key**, otherwise, it cannot be output to the log file, for example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var ctx = context.Background() + + // You can directly use String as Key + ctx = context.WithValue(ctx, "RequestId", "123456789") + + // To extract the Key as a public variable, you can use the gctx.StrKey type, or directly use string type + const userIdKey gctx.StrKey = "UserId" // or const userIdKey = "UserId" + ctx = context.WithValue(ctx, userIdKey, "10000") + + // Cannot use custom type + type notPrintKeyType string + const notPrintKey notPrintKeyType = "NotPrintKey" + ctx = context.WithValue(ctx, notPrintKey, "notPrintValue") // Will not print notPrintValue + + g.Log().Error(ctx, "runtime error") +} +``` + +After execution, the terminal output will be: + +```html +2024-09-26 11:45:33.790 [ERRO] {123456789, 10000} runtime error +Stack: +1. main.main + /Users/teemo/GolandProjects/gogf/demo/main.go:24 + +``` + +### Log Example + +![](/markdown/d9b17863576dca859b0b13b98041130e.png) + +## Delivery to `Handler` + +If a developer customizes a `Handler` for the log object, each `ctx` context variable passed during log printing will be delivered to the `Handler`. For an introduction to log `Handler`, please refer to the section: [Logging - Handler](日志组件-Handler.md) + +## Trace Support + +The `glog` component supports the OpenTelemetry standard trace feature, which is built-in and requires no setup from the developer. For more information, please refer to the section: [Service Tracing](../../服务可观测性/服务链路跟踪/服务链路跟踪.md) + +![](/markdown/a6ade54c58ba067b6be203a6e17b15e5.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..ce1f7abf39c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/core/glog-flags' +title: 'Logging - Flags' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame, GoFrame Framework, glog, Log Component, Flags Feature, Asynchronous Log Output, Line Number Information, Time Format, Date and Time, Milliseconds] +description: "Flags feature of the log component in the GoFrame framework, explaining how to control additional log output features through different constant combinations, including asynchronous output, call line number information printing, and various time format selections. These features enable developers to achieve more flexible logging and debugging." +--- + +`flags` are used to control the additional feature switches of the log component. These properties are controlled by combining constants, including: + +```go +F_ASYNC = 1 << iota // Enable asynchronous log output +F_FILE_LONG // Print call line number information with a full absolute path, e.g., /a/b/c/d.go:23 +F_FILE_SHORT // Print call line number information, only the file name, e.g., d.go:23, overrides F_FILE_LONG. +F_TIME_DATE // Print the current date, e.g., 2009-01-23 +F_TIME_TIME // Print the current time, e.g., 01:23:23 +F_TIME_MILLI // Print the current time + milliseconds, e.g., 01:23:23.675 +F_TIME_STD = F_TIME_DATE | F_TIME_MILLI // (Default) Print the current date + time + milliseconds, e.g., 2009-01-23 01:23:23.675 +``` + +Usage example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT) + l.Print(ctx, "time and short line number") + l.SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG) + l.Print(ctx, "time with millisecond and long line number") + l.SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG) + l.Print(ctx, "standard time format and long line number") +} + +``` + +After execution, the terminal output is: + +```html +PS C:\hailaz\test> go run .\main.go +16:05:35 main.go:13: time and short line number +16:05:35.108 C:/hailaz/test/main.go:15: time with millisecond and long line number +2022-01-05 16:05:35.109 C:/hailaz/test/main.go:17: standard time format and long line number + +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" new file mode 100644 index 00000000000..135cf3f5194 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" @@ -0,0 +1,300 @@ +--- +slug: '/docs/core/glog-handler' +title: 'Logging - Handler' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, log component, Handler feature, log processing, custom output, Json format, Graylog service, log collection, glog, middleware design] +description: "This article introduces the new customizable log processing Handler feature of the glog component in the GoFrame framework starting from version v2.0. Developers can implement custom log output through the Handler, such as converting logs to Json format or outputting them to third-party services like Graylog. Detailed examples demonstrate how to use Handler for log processing in the GoFrame framework." +--- + +Starting from version `v2.0`, the `glog` component provides a super powerful, customizable log processing `Handler` feature. `Handler` adopts a middleware design approach, allowing developers to register multiple processing `Handlers` for the log object, and also to override the default log component processing logic within the `Handler`. + +## Related Definitions + +### `Handler` Method Definition + +```go +// Handler is function handler for custom logging content outputs. +type Handler func(ctx context.Context, in *HandlerInput) +``` + +As you can see, the second parameter is the log information to be processed by the log and is of pointer type, meaning that any attribute information of this parameter can be modified within the `Handler`, and the modified content will be passed to the next `Handler`. + +### `Handler` Parameter Definition + +```go +// HandlerInput is the input parameter struct for logging Handler. +type HandlerInput struct { + Logger *Logger // Current Logger object. + Buffer *bytes.Buffer // Buffer for logging content outputs. + Time time.Time // Logging time, which is the time that logging triggers. + TimeFormat string // Formatted time string, like "2016-01-09 12:00:00". + Color int // Using color, like COLOR_RED, COLOR_BLUE, etc. Eg: 34 + Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc. Eg: 256 + LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO + CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set. + CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set. + CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured. + TraceId string // Trace id, only available if OpenTelemetry is enabled. + Prefix string // Custom prefix string for logging content. + Content string // Content is the main logging content without error stack string produced by logger. + Values []any // The passed un-formatted values array to logger. + Stack string // Stack string produced by logger, only available if Config.StStatus configured. + IsAsync bool // IsAsync marks it is in asynchronous logging. +} +``` + +Developers have **two ways** to customize log output content through `Handler`: + +- One way is to directly modify the attribute information in `HandlerInput` and then continue to execute `in.Next(ctx)`. The default log output logic will print the attributes in `HandlerInput` as a string output. +- Another way is to write log content into the `Buffer` buffer object. If the default log output logic finds that `Buffer` already contains log content, it will ignore the default log output logic. + +### Registering `Handler` to `Logger` Method + +```go +// SetHandlers sets the logging handlers for current logger. +func (l *Logger) SetHandlers(handlers ...Handler) +``` + +## Usage Example + +Let's look at two examples to better understand how to use `Handler`. + +### Example 1: Convert Log Output to `Json` Format + +In this example, we use a pre-middleware design to modify the log output format to `JSON` format through a custom `Handler`. + +```go +package main + +import ( + "context" + "encoding/json" + "os" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gstr" +) + +// JsonOutputsForLogger is for JSON marshaling in sequence. +type JsonOutputsForLogger struct { + Time string `json:"time"` + Level string `json:"level"` + Content string `json:"content"` +} + +// LoggingJsonHandler is a example handler for logging JSON format content. +var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + jsonForLogger := JsonOutputsForLogger{ + Time: in.TimeFormat, + Level: gstr.Trim(in.LevelFormat, "[]"), + Content: gstr.Trim(in.Content), // For version 2.7 and above, use in.ValuesContent() + } + jsonBytes, err := json.Marshal(jsonForLogger) + if err != nil { + _, _ = os.Stderr.WriteString(err.Error()) + return + } + in.Buffer.Write(jsonBytes) + in.Buffer.WriteString("\n") + in.Next(ctx) +} + +func main() { + g.Log().SetHandlers(LoggingJsonHandler) + ctx := context.TODO() + g.Log().Debug(ctx, "Debugging...") + g.Log().Warning(ctx, "It is warning info") + g.Log().Error(ctx, "Error occurs, please have a check") +} +``` + +As you can see, we can control the output log content through the `Buffer` attribute in the `Handler`. If the `Buffer` content is empty after all pre-middleware `Handlers` process, continuing to execute `Next` will execute the default `Handler` logic of the log middleware. After executing the code of this example, the terminal output: + +```html +{"time":"2021-12-31 11:03:25.438","level":"DEBU","content":"Debugging..."} +{"time":"2021-12-31 11:03:25.438","level":"WARN","content":"It is warning info"} +{"time":"2021-12-31 11:03:25.438","level":"ERRO","content":"Error occurs, please have a check \nStack:\n1. main.main\n C:/hailaz/test/main.go:42"} +``` + +### Example 2: Output Content to a Third-party Log Collection Service + +In this example, we use a post-middleware design to output a copy of the log content to a third-party `graylog` log collection service through a custom `Handler` without affecting the original log output processing. + +> `Graylog` is comparable to `ELK` and is a centralized log management solution that supports data collection, retrieval, and visualization dashboards. In this example, a simple third-party `graylog` client component is used. + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + gelf "github.com/robertkowalski/graylog-golang" +) + +var grayLogClient = gelf.New(gelf.Config{ + GraylogPort: 80, + GraylogHostname: "graylog-host.com", + Connection: "wan", + MaxChunkSizeWan: 42, + MaxChunkSizeLan: 1337, +}) + +// LoggingGrayLogHandler is an example handler for logging content to remote GrayLog service. +var LoggingGrayLogHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + in.Next(ctx) + grayLogClient.Log(in.Buffer.String()) +} + +func main() { + g.Log().SetHandlers(LoggingGrayLogHandler) + ctx := context.TODO() + g.Log().Debug(ctx, "Debugging...") + g.Log().Warning(ctx, "It is warning info") + g.Log().Error(ctx, "Error occurs, please have a check") + glog.Print(ctx, "test log") +} +``` + +## Global Default `Handler` + +By default, the log object does not have any `Handlers` set. From version `v2.1`, the framework provides a feature to set a global default `Handler`. The global default `Handler` will be effective for all log printing functions that use this log component and do not have custom `Handlers`. At the same time, the global default `Handler` will affect the log printing behavior of the log package methods. + +Developers can use the following two methods to set and get the global default `Handler`. + +```go +// SetDefaultHandler sets default handler for package. +func SetDefaultHandler(handler Handler) + +// GetDefaultHandler returns the default handler of package. +func GetDefaultHandler() Handler +``` + +It should be noted that this method of global package configuration is not thread-safe and often needs to be executed at the very top of the project startup logic. + +Usage example, where we use `JSON` format for all project log outputs to ensure log content is structured and each log output is single-line for easy log collection during log collection period: + +```go +package main + +import ( + "context" + "encoding/json" + "os" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gstr" +) + +// JsonOutputsForLogger is for JSON marshaling in sequence. +type JsonOutputsForLogger struct { + Time string `json:"time"` + Level string `json:"level"` + Content string `json:"content"` +} + +// LoggingJsonHandler is an example handler for logging JSON format content. +var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + jsonForLogger := JsonOutputsForLogger{ + Time: in.TimeFormat, + Level: gstr.Trim(in.LevelFormat, "[]"), + Content: gstr.Trim(in.Content), // For version 2.7 and above, use in.ValuesContent() + } + jsonBytes, err := json.Marshal(jsonForLogger) + if err != nil { + _, _ = os.Stderr.WriteString(err.Error()) + return + } + in.Buffer.Write(jsonBytes) + in.Buffer.WriteString("\n") + in.Next(ctx) +} + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(LoggingJsonHandler) + + g.Log().Debug(ctx, "Debugging...") + glog.Warning(ctx, "It is warning info") + glog.Error(ctx, "Error occurs, please have a check") +} +``` + +After execution, the terminal output: + +```html +{"time":"2022-06-20 10:51:50.235","level":"DEBU","content":"Debugging..."} +{"time":"2022-06-20 10:51:50.235","level":"WARN","content":"It is warning info"} +{"time":"2022-06-20 10:51:50.235","level":"ERRO","content":"Error occurs, please have a check"} +``` + +## General Component `Handler` + +The component provides a number of general-purpose log `Handlers` to facilitate developer use and improve development efficiency. + +### `HandlerJson` + +This `Handler` can convert log content to `Json` format for printing. Usage example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(glog.HandlerJson) + + g.Log().Debug(ctx, "Debugging...") + glog.Warning(ctx, "It is warning info") + glog.Error(ctx, "Error occurs, please have a check") +} +``` + +After execution, the terminal output: + +```html +{"Time":"2022-06-20 20:04:04.725","Level":"DEBU","Content":"Debugging..."} +{"Time":"2022-06-20 20:04:04.725","Level":"WARN","Content":"It is warning info"} +{"Time":"2022-06-20 20:04:04.725","Level":"ERRO","Content":"Error occurs, please have a check","Stack":"1. main.main\n /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/main.go:16\n"} +``` + +### `HandlerStructure` + +This `Handler` prints log content in a structured format, primarily to keep the log output content consistent with the `slog` format of the newer versions of `Golang`. It is important to note that the structured log printing feature needs to ensure that all log records use structured output for greater significance. Usage example: + +```go +package main + +import ( + "context" + "net" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(glog.HandlerStructure) + + g.Log().Info(ctx, "caution", "name", "admin") + glog.Error(ctx, "oops", net.ErrClosed, "status", 500) +} +``` + +After execution, the terminal output: + +```html +Time="2023-11-23 21:00:08.671" Level=INFO Content=caution name=admin +Time="2023-11-23 21:00:08.671" Level=ERRO oops="use of closed network connection" status=500 Stack="1. main.main\n /Users/txqiangguo/Workspace/gogf/gf/example/.test/main.go:16\n" +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..1dca122588f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/core/glog-json' +title: 'Logging - JSON Format' +sidebar_position: 7 +hide_title: true +keywords: [Logging Component, JSON Format, GoFrame, GoFrame Framework, glog, Log Analysis, Struct Log, Log Output, map Parameters, gjson] +description: "Using the glog component in the GoFrame framework to output logs in JSON format, suitable for parsing by log analysis tools. You will learn how to implement JSON log format output through map or struct parameters, and achieve more complex JSON content output in conjunction with the gjson.MustEncode method." +--- + +`glog` is very friendly to log analysis tools and supports outputting logs in `JSON` format for easier parsing and analysis of log content later on. + +## Using `map/struct` Parameters + +Supporting JSON data format log output is very simple; just provide a `map` or `struct` type parameter to the printing method. + +Example usage: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Debug(ctx, g.Map{"uid": 100, "name": "john"}) + type User struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + g.Log().Debug(ctx, User{100, "john"}) +} +``` + +After execution, the terminal outputs: + +```html +2019-06-02 15:28:52.653 [DEBU] {"name":"john","uid":100} +2019-06-02 15:28:52.653 [DEBU] {"uid":100,"name":"john"} +``` + +## Combining `gjson.MustEncode` + +Additionally, you can achieve JSON content output in conjunction with `gjson.MustEncode`, for example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + type User struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + g.Log().Debugf(ctx, `user json: %s`, gjson.MustEncode(User{100, "john"})) +} +``` + +After execution, the terminal outputs: + +```html +2022-04-25 18:09:45.029 [DEBU] user json: {"uid":100,"name":"john"} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..d3fdf004cdf --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/glog-rotate' +title: 'Logging - Rotatation' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame,GoFrame framework,glog,log component,log rotation,log rolling,log compression,log configuration,file format,log backup] +description: "Log rolling rotation feature of the glog component in GoFrame framework, including setting log file names to output by date, rolling rotation with RotateSize and RotateExpire, support for log file compression and backup, configuration examples, considerations, and more. Provides detailed configuration options and example code to help developers better manage log files." +--- +:::warning +The rolling rotation is currently an experimental feature; feel free to provide feedback if you encounter issues. +::: +In previous sections, we learned that the `glog` component supports setting log file names to output logs by date. Starting from `GoFrame v1.12`, the `glog` component also supports the rolling rotation feature for log files, which involves the following configuration options in the log object configuration properties: + +```go +RotateSize int64 // Rotate the logging file if its size > 0 in bytes. +RotateExpire time.Duration // Rotate the logging file if its mtime exceeds this duration. +RotateBackupLimit int // Max backup for rotated files, default is 0, means no backups. +RotateBackupExpire time.Duration // Max expire for rotated files, which is 0 in default, means no expiration. +RotateBackupCompress int // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. +RotateCheckInterval time.Duration // Asynchronously checks the backups and expiration at intervals. It's 1 hour by default. +``` + +Brief Explanation: + +1. `RotateSize` is used to specify the size-based rotation trigger. This property is in bytes, and the rotation will only be activated when it is greater than 0. +2. `RotateExpire` sets the duration trigger for rotation based on file modification times. The feature is activated only if this property is greater than 0. +3. `RotateBackupLimit` sets the number of backup files kept after rotation, defaulting to 0, which means no backups. It usually should be set greater than 0. Files exceeding this backup number will be deleted from oldest to newest. +4. `RotateBackupExpire` configures the deletion of rotated files based on their age. +5. `RotateBackupCompress` indicates the compression level for rotated files, default is 0, meaning no compression. The range for compression levels is `1-9`, where `9` denotes the maximum compression level. +6. `RotateCheckInterval` sets the interval for asynchronous backup and expiration checks, with a default of 1 hour, which typically does not need configuration. + +### Feature Activation + +The rolling rotation feature, as indicated by the above configuration options, will only be active if `RotateSize` or `RotateExpire` is configured, and is off by default. + +### File Format + +The `glog` component outputs logs in a fixed file format `*.log`, using `.log` as the log file name suffix. To standardize and manage easily, the format of rotated files is fixed and not customizable by developers. When files are rotated, they are renamed in the format " `*.rotation_time.log`", where `rotation_time` is formatted as: `YYYYMMDDHHMMSSmmmuuu`, for example: + +```html +access.log -> access.20200326101301899002.log +access.20200326.log -> access.20200326.20200326101301899002.log +``` + +### Configuration Examples + +1. Example 1, rotating based on log file size: + +```yaml +logger: + Path: "/var/log" + Level: "all" + Stdout: false + RotateSize: "100M" + RotateBackupLimit: 2 + RotateBackupExpire: "7d" + RotateBackupCompress: 9 +``` + +Brief Explanation: + + - The `RotateSize` option can be set as a string in the config file, e.g., `100M`, `200MB`, `500KB`, `1Gib`, etc. In this example, we rotate when the log file size exceeds `100M`. + - Similarly, `RotateBackupExpire` can be string-configured with values like `1h`, `30m`, `1d6h`, `7d`, etc. Here, set to `7d` to automatically delete rotated files after seven days. + - Setting `RotateBackupCompress` to `9` indicates using the highest compression level for minimized storage of rotated files. Note that rotation and compression are separate actions; file compression is asynchronous, and after auto-rotation, the scheduler periodically compresses the files to `*.gz`. Higher compression levels will consume more CPU resources. + +2. Example 2, rotating based on file modification expiration: + +```yaml +logger: + Path: "/var/log" + Level: "all" + Stdout: false + RotateExpire: "1d" + RotateBackupLimit: 1 + RotateBackupExpire: "7d" + RotateBackupCompress: 9 +``` + +Here, `RotateExpire` is set to `1d` meaning if a log file hasn't been modified or written to within a day, the `glog` module will automatically rotate it, compressing it for storage. + +### Considerations + +Due to differing rolling rotation configurations among log objects, if multiple log objects share the same directory and have rotation enabled, there might be conflicts leading to unexpected outcomes. Thus, we recommend you consider two strategies: + +1. Use the global default logging singleton object ( `g.Log()`), specifying different directories or file names using the `Cat` or `File` methods. +2. Set different output directories ( `Path` configuration) for different log objects ( `g.Log(name)`) ensuring no hierarchical overlap among them. + +For example, with two types of business log files `order` and `promo` for order and promotion business respectively, within the same service program: + +If the log path is `/var/log`, you could: + +1. Output order logs via `g.Log().Cat("order").Print(xxx)`, resulting in log files like: `/var/log/order/2020-03-26.log`. +2. Output promotion logs via `g.Log().Cat("promo").Print(xxx)`, resulting in log files like: `/var/log/promo/2020-03-26.log`. + +Alternatively: + +1. Use `g.Log("order").Print(xxx)` for order logs, creating files like: `/var/log/order.log`. +2. Use `g.Log("promo").Print(xxx)` for promotion logs, creating files like: `/var/log/promo.log`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..04a8a8d77c1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/core/glog-writer' +title: 'Logging - Writer Interface' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Log Component, Writer Interface, Custom Log Output, Custom Writer, glog Module, Log HOOK, Graylog, Centralized Log Management] +description: "Use the Writer interface of the glog module in the GoFrame framework to customize log output. By implementing a custom Writer object, logs can be flexibly output to different targets such as files, standard output, and Graylog. Additionally, sample code is provided to demonstrate how to implement log HOOK functionality to promptly notify monitoring services of serious errors." +--- +:::tip +The `Writer` interface is the lowest-level `IO` writing interface. If your business needs to customize the log content printing, it is recommended to use the `Handler` feature. Refer to the section: [Logging - Handler](日志组件-Handler.md) +::: +## Custom `Writer` Interface + +The `glog` module implements log content printing for both standard output and file output. Of course, developers can also implement custom log content output by customizing the `io.Writer` interface. `io.Writer` is a content output interface provided by the standard library, defined as follows: + +```go +type Writer interface { + Write(p []byte) (n int, err error) +} +``` + +We can implement custom `Writer` output using the `SetWriter` method or the chaining method `To`. Developers can define operations in this `Writer`, and they can also integrate other module functions within it. + +Additionally, the `glog.Logger` object has already implemented the `io.Writer` interface, making it very convenient for developers to integrate `glog` into other modules. + +## Example 1: Implementing Log `HOOK` + +In this example, we implement a custom `Writer` object `MyWriter`. In the `Writer` interface implementation of this object, we evaluate the log content. If a `PANI` or `FATA` error occurs, it indicates a severe error, and the interface will first notify the `Monitor` monitoring service through an `HTTP` interface. Then, the log content is written to files and standard output using the `glog` module according to the configuration. + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gregex" +) + +type MyWriter struct { + logger *glog.Logger +} + +func (w *MyWriter) Write(p []byte) (n int, err error) { + var ( + s = string(p) + ctx = context.Background() + ) + if gregex.IsMatchString(`PANI|FATA`, s) { + fmt.Println("SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time!") + g.Client().PostContent(ctx, "http://monitor.mydomain.com", s) + } + return w.logger.Write(p) +} + +func main() { + var ctx = context.Background() + glog.SetWriter(&MyWriter{ + logger: glog.New(), + }) + glog.Fatal(ctx, "FATAL ERROR") +} +``` + +After execution, the output result is: + +```html +SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time! +2019-05-23 20:14:49.374 [FATA] FATAL ERROR +Stack: +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_writer_hook.go:27 +``` + +## Example 2: Integrating with `Graylog` + +Suppose we need to output logs to both files and standard output, and simultaneously output logs to `Graylog`. Clearly, this can only be achieved by customizing the `Writer`. Similarly, we can customize the output to other log collection components or databases. + +> `Graylog` is a centralized log management solution comparable to `ELK`, supporting data collection, retrieval, and visualized Dashboards. + +Example code: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" + "github.com/robertkowalski/graylog-golang" +) + +type MyGrayLogWriter struct { + gelf *gelf.Gelf + logger *glog.Logger +} + +func (w *MyGrayLogWriter) Write(p []byte) (n int, err error) { + w.gelf.Send(p) + return w.logger.Write(p) +} + +func main() { + var ctx = context.Background() + glog.SetWriter(&MyGrayLogWriter{ + logger : glog.New(), + gelf : gelf.New(gelf.Config{ + GraylogPort : 80, + GraylogHostname : "graylog-host.com", + Connection : "wan", + MaxChunkSizeWan : 42, + MaxChunkSizeLan : 1337, + }), + }) + glog.Println(ctx, "test log") +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..e080267e875 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" @@ -0,0 +1,146 @@ +--- +slug: '/docs/core/glog-stack' +title: 'Logging - Stack Printing' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame framework,log component,stack printing,glog,GetStack,PrintStack,gerror,error log,debugging] +description: "The stack printing feature in the GoFrame framework log component allows developers to automatically print the stack information of logging method invocations. This feature can be triggered by various error log output methods like Notice*/Warning*/Error*/Critical*/Panic*/Fatal*, or obtained/printed via GetStack/PrintStack. This stack information is very useful for debugging error log information, especially in handling complex applications. This article helps developers better understand and apply the stack printing features of the log component through code examples." +--- + +Error log information supports the `Stack` feature, which can automatically print the stack information of the current invocation of log component methods. This stack information can be triggered by error log output methods like `Notice*/Warning*/Error*/Critical*/Panic*/Fatal*`, or obtained/printed via `GetStack/PrintStack`. The `stack` information of error messages is quite useful for debugging. + +### Example 1, Triggered by `Error` Method + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func Test(ctx context.Context) { + glog.Error(ctx, "This is error!") +} + +func main() { + ctx := context.TODO() + Test(ctx) +} + +``` + +The printed result is as follows: + +```html +2022-01-05 15:08:54.998 [ERRO] This is error! +Stack: +1. main.Test + C:/hailaz/test/main.go:10 +2. main.main + C:/hailaz/test/main.go:15 +``` + +### Example 2, Print via `Stack` Method + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.PrintStack(ctx) + glog.New().PrintStack(ctx) + + fmt.Println(glog.GetStack()) + fmt.Println(glog.New().GetStack()) +} + +``` + +After execution, the output is: + +```html +2019-07-12 22:20:28.070 Stack: +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:11 + +2019-07-12 22:20:28.070 Stack: +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:12 + +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:14 + +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:15 +``` + +### Example 3, Print `gerror.Error` + +The `glog` logging module supports stack printing for both standard errors and `gerror` errors. + +```go +package main + +import ( + "context" + "errors" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/glog" +) + +func MakeError() error { + return errors.New("connection closed with normal error") +} + +func MakeGError() error { + return gerror.New("connection closed with gerror") +} + +func TestGError(ctx context.Context) { + err1 := MakeError() + err2 := MakeGError() + glog.Error(ctx, err1) + glog.Errorf(ctx, "%+v", err2) +} + +func main() { + ctx := context.TODO() + TestGError(ctx) +} + +``` + +After execution, the terminal output is: + +```html +2019-07-12 22:23:11.467 [ERRO] connection closed with normal error +Stack: +1. main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:20 +2. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 + +2019-07-12 22:23:11.467 [ERRO] connection closed with gerror +1. connection closed with gerror + 1). main.MakeGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:14 + 2). main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:19 + 3). main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 +Stack: +1. main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:21 +2. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..e60c454f853 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/glog-faq' +title: 'Logging - FAQ' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Log Component,FAQ,error variable,Error Stack,Log Method,Print Error,g.Log,GoFrame Log] +description: "When using the GoFrame framework for logging, print the stack information of the error variable instead of the stack when the log method is called. Provides specific Go code examples to help developers record and debug error logs more accurately." +--- + +## How to print the stack of an `error` variable rather than the stack at the time the log method is called + +Using the following method will only print the string description of the `error`, and the stack is just the stack at the time the log method `Error` is called: + +```go +g.Log().Error(ctx, err) +``` + +If you want to print the stack of the `error` variable, and not the stack when the `Error` method is called, you can do it as follows: + +```go +g.Log().Printf(ctx, "%+v", err) +``` + +Reference links: + +- [https://github.com/gogf/gf/issues/1640](https://github.com/gogf/gf/issues/1640) +- [Error Handling - Other Features](../错误处理/错误处理-其他特性.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" new file mode 100644 index 00000000000..95f697a6f32 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/core/glog-async' +title: 'Logging - Asynchronous Output' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, Log Component, Asynchronous Output, glog, goroutine, SetAsync, SetFlags, Chaining, Log Optimization, Resource Usage] +description: "Use the GoFrame framework for asynchronous log output to improve logging efficiency. You can set asynchronous output using SetAsync or chaining methods. Asynchronous output can reduce resource usage, but be aware of potential log disorder issues." +--- + +For log output with low immediacy requirements, you can output logs asynchronously. Asynchronous output allows logging calls to return immediately, thus being more efficient. `glog` certainly supports asynchronous output features and uses a `goroutine` pool internally to manage asynchronous log printing tasks, which can significantly reduce resource usage. + +Asynchronous output can be implemented through the `SetAsync`/ `SetFlags` methods of the log object or through the chaining operation `Async` method. However, note that if you set asynchronous output through the object setting methods, all subsequent log outputs will be asynchronous; if you output asynchronously through chaining, only the current log output will be asynchronous. +:::warning +If both synchronous and asynchronous printing are used for the same file log output, please note the log file content may become disordered, which should be avoided as much as possible. +::: +### `SetAsync` + +Let's look at an example of implementing asynchronous printing using the `SetAsync` method. + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().SetAsync(true) + for i := 0; i < 10; i++ { + g.Log().Print(ctx, "async log", i) + } +} +``` + +After execution, you can find nothing is output to the terminal because the log output is asynchronous, and the example exits before the log content is output. Therefore, we can make a slight improvement as follows: + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().SetAsync(true) + for i := 0; i < 10; i++ { + g.Log().Print(ctx, "async log", i) + } + time.Sleep(time.Second) +} +``` + +After execution, the terminal output is: + +```html +2019-06-02 15:44:21.399 async log 0 +2019-06-02 15:44:21.399 async log 1 +2019-06-02 15:44:21.399 async log 2 +2019-06-02 15:44:21.399 async log 3 +2019-06-02 15:44:21.399 async log 4 +2019-06-02 15:44:21.399 async log 5 +2019-06-02 15:44:21.399 async log 6 +2019-06-02 15:44:21.399 async log 7 +2019-06-02 15:44:21.399 async log 8 +2019-06-02 15:44:21.399 async log 9 +``` + +### `Async` Chaining Operation + +The chaining operation is relatively simple. + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + for i := 0; i < 10; i++ { + g.Log().Async().Print(ctx, "async log", i) + } + g.Log().Print(ctx, "normal log") + g.Log().Print(ctx, "normal log") + g.Log().Print(ctx, "normal log") + time.Sleep(time.Second) +} +``` + +After execution, the terminal output is: + +```html +2022-01-05 15:00:44.101 normal log +2022-01-05 15:00:44.101 async log 0 +2022-01-05 15:00:44.101 async log 1 +2022-01-05 15:00:44.101 async log 2 +2022-01-05 15:00:44.101 async log 3 +2022-01-05 15:00:44.101 async log 4 +2022-01-05 15:00:44.101 async log 5 +2022-01-05 15:00:44.101 async log 6 +2022-01-05 15:00:44.101 async log 7 +2022-01-05 15:00:44.101 async log 8 +2022-01-05 15:00:44.101 async log 9 +2022-01-05 15:00:44.101 normal log +2022-01-05 15:00:44.103 normal log +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" new file mode 100644 index 00000000000..8fee9822eab --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" @@ -0,0 +1,101 @@ +--- +slug: '/docs/core/glog-file-folder' +title: 'Logging - File Directory' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,Log Component,File Directory,Log File,SetFile Method,gtime Time Format,Chained Operation,SetPath Method,gfpool File Pointer Pool,Log Directory] +description: "Use the logging component in the GoFrame framework to set the name and directory path of log files. Through the SetFile method, users can customize the format of log files and support gtime time format. Through the SetPath method, users can write log content to a specified directory and use gfpool to optimize file writing efficiency." +--- + +## Log Files + +By default, log file names are named with the current date and time, formatted as `YYYY-MM-DD.log`. We can use the `SetFile` method to set the format of the file name, and the file name format supports [Time](../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) time format. Simple example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +// Set log level +func main() { + ctx := context.TODO() + path := "./glog" + glog.SetPath(path) + glog.SetStdoutPrint(false) + // Use the default file name format + glog.Print(ctx, "Standard file name format, using the current date and time") + // Set file name format via SetFile + glog.SetFile("stdout.log") + glog.Print(ctx, "Set the log output file name to a single file") + // Chained operation to set file name format + glog.File("stderr.log").Print(ctx, "Supports chained operations") + glog.File("error-{Ymd}.log").Print(ctx, "File name supports gtime date format") + glog.File("access-{Ymd}.log").Print(ctx, "File name supports gtime date format") + + list, err := gfile.ScanDir(path, "*") + g.Dump(err) + g.Dump(list) +} + +``` + +After execution, the output is: + +```html + +[ + "C:\hailaz\test\glog\2021-12-31.log", + "C:\hailaz\test\glog\access-20211231.log", + "C:\hailaz\test\glog\error-20211231.log", + "C:\hailaz\test\glog\stderr.log", + "C:\hailaz\test\glog\stdout.log", +] +``` + +As you can see, if you need to use the `gtime` time format in the file name format, you need to include the format content in `{xxx}`. This example also uses the feature of `chained operations`. For details, see the subsequent explanation. + +## Log Directory + +By default, `glog` will output log content to the standard output. We can set the directory path for log output through the `SetPath` method, so that log content will be written to the log file. Thanks to the internal use of the `gfpool` file pointer pool, the efficiency of file writing is quite excellent. Simple example: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +// Set log level +func main() { + ctx := context.TODO() + path := "./glog" + glog.SetPath(path) + glog.Print(ctx, "Log content") + list, err := gfile.ScanDir(path, "*") + g.Dump(err) + g.Dump(list) +} + +``` + +After execution, the output is: + +```html +2021-12-31 11:32:16.742 Log content + +[ + "C:\hailaz\test\glog\2021-12-31.log", +] +``` + +When setting the output directory of the log through `SetPath`, if the directory does not exist, it will recursively create the directory path. As you can see, after executing `Println`, the log directory `glog` was created under `/tmp`, and a log file was generated under it. Also, we can see that the log content is output not only to the file but by default also to the terminal. We can disable terminal log output using the `SetStdoutPrint(false)` method, so the log content will only be output to the log file. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" new file mode 100644 index 00000000000..abf08559f5e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" @@ -0,0 +1,196 @@ +--- +slug: '/docs/core/glog-level' +title: 'Logging - Log Level' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Log Component, Log Level, GoFrame Framework, SetLevel, SetLevelStr, SetLevelPrint, Level Name, glog, Log Output] +description: "Using the log component in the GoFrame framework to manage and set log levels, including specific usage examples of SetLevel, SetLevelStr, and SetLevelPrint methods. We also discuss how to filter and display log content through different log level names for flexible log management." +--- + +## Log Level + +Log levels are used to manage the output of logs, allowing us to enable or disable specific log content by setting particular levels. The log level can be set using two methods: + +```go +func (l *Logger) SetLevel(level int) +func (l *Logger) SetLevelStr(levelStr string) error +func (l *Logger) SetLevelPrint(enabled bool) +``` + +### `SetLevel` Method + +The `SetLevel` method allows you to set the log level, and the `glog` module supports the following log level constants: + +```html +LEVEL_ALL +LEVEL_DEV +LEVEL_PROD +LEVEL_DEBU +LEVEL_INFO +LEVEL_NOTI +LEVEL_WARN +LEVEL_ERRO +``` + +You can combine these levels using `bitwise operations`, for example, `LEVEL_ALL` is equivalent to `LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT`. You can also filter out `LEVEL_DEBU/LEVEL_INFO/LEVEL_NOTI` log content with `LEVEL_ALL & ^LEVEL_DEBU & ^LEVEL_INFO & ^LEVEL_NOTI`. +:::warning +There are other levels in the log module, such as `CRIT/PANI/FATA`, but these levels represent very serious errors and cannot be masked by the developer in the log level. For example, during serious errors, `PANI/FATA` error levels will trigger additional system actions: `panic`/`exit`. +::: +Example of use: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) + l.Info(ctx, "info2") +} + +``` + +After execution, the output result is: + +```html +2021-12-31 11:16:57.272 [INFO] info1 +``` + +### `SetLevelStr` Method + +In most scenarios, you can set the log level using strings with the `SetLevelStr` method. The `level` configuration item in the configuration file is also set using strings. The supported log level strings, which are case-insensitive, are as follows: + +```html +"ALL": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEV": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEVELOP": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"PROD": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"PRODUCT": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEBU": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEBUG": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"INFO": LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"NOTI": LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"NOTICE": LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"WARN": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"WARNING": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"ERRO": LEVEL_ERRO | LEVEL_CRIT, +"ERROR": LEVEL_ERRO | LEVEL_CRIT, +"CRIT": LEVEL_CRIT, +"CRITICAL": LEVEL_CRIT, +``` + +You can see that the log levels set by the level names are filtered according to their severity: `DEBU < INFO < NOTI < WARN < ERRO < CRIT`. It also supports common deployment mode configuration names such as `ALL`, `DEV`, `PROD`. + +Example of use: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevelStr("notice") + l.Info(ctx, "info2") +} + +``` + +After execution, the output result is: + +```html +2021-12-31 11:20:15.019 [INFO] info1 +``` + +### `SetLevelPrint` Method + +This method controls whether the default log output prints the log level indicator. By default, it prints the log level indicator. + +Example of use: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevelPrint(false) + l.Info(ctx, "info2") +} + +``` + +After execution, the output result is: + +```html +2023-03-14 10:28:18.598 [INFO] info1 +2023-03-14 10:28:18.631 info1 +``` + +## Level Name + +In logs, you will see content with various log levels printed in front, each with different level names. The default log level names are: + +```html +LEVEL_DEBU: "DEBU", +LEVEL_INFO: "INFO", +LEVEL_NOTI: "NOTI", +LEVEL_WARN: "WARN", +LEVEL_ERRO: "ERRO", +LEVEL_CRIT: "CRIT", +LEVEL_PANI: "PANI", +LEVEL_FATA: "FATA", +``` + +To maintain a unified log format and ensure an elegant layout, the names of log levels use the first four characters of the English words. If you have special requirements to change the log level names, you can set them using the following methods: + +```go +func (l *Logger) SetLevelPrefix(level int, prefix string) +func (l *Logger) SetLevelPrefixes(prefixes map[int]string) +``` + +Example of use: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.SetLevelPrefix(glog.LEVEL_DEBU, "debug") + l.Debug(ctx, "test") +} + +``` + +After execution, the console output is: + +```html +2021-12-31 11:21:45.754 [debug] test +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..e0f143217e9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/core/glog-debug' +title: 'Logging - Debug Info' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, log component, debug information, Debug method, test environment, SetDebug, example code, log output, command line parameters, environment variables] +description: "The way to record debug information using Debug/Debugf methods in GoFrame framework, suitable for development and testing environments. Demonstrates how to control the output of debug information using SetDebug method with code examples, as well as how to disable debug logs via command line parameters and environment variables." +--- + +`Debug/Debugf` are very useful methods for recording debug information, commonly used in development/testing environments. When the application goes live, you can easily enable/disable them using `SetDebug` or configuration files. + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + ctx := context.TODO() + gtimer.SetTimeout(ctx, 3*time.Second, func(ctx context.Context) { + g.Log().SetDebug(false) + }) + for { + g.Log().Debug(ctx, gtime.Datetime()) + g.Log().Info(ctx, gtime.Datetime()) + time.Sleep(time.Second) + } +} +``` + +In this example, the `glog.Debug` method is used to output debug information. After 3 seconds, the output of debug information is turned off. After execution, the output result shows only 3 log entries. Subsequent debug log information is not output because it is turned off using the `SetDebug` method. + +```html +2022-01-05 15:59:05.674 [DEBU] 2022-01-05 15:59:05 +2022-01-05 15:59:05.675 [INFO] 2022-01-05 15:59:05 +2022-01-05 15:59:06.684 [DEBU] 2022-01-05 15:59:06 +2022-01-05 15:59:06.684 [INFO] 2022-01-05 15:59:06 +2022-01-05 15:59:07.692 [DEBU] 2022-01-05 15:59:07 +2022-01-05 15:59:07.692 [INFO] 2022-01-05 15:59:07 +2022-01-05 15:59:08.708 [INFO] 2022-01-05 15:59:08 +2022-01-05 15:59:09.717 [INFO] 2022-01-05 15:59:09 +2022-01-05 15:59:10.728 [INFO] 2022-01-05 15:59:10 +2022-01-05 15:59:11.733 [INFO] 2022-01-05 15:59:11 +``` + +> We can also disable debug information via command line parameters or system environment variables. +> +> 1. Modify the command line startup parameter - `gf.glog.debug=false`; +> 2. Modify the specified environment variable - `GF_GLOG_DEBUG=false`; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..3e3844c7d70 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/core/glog-config' +title: 'Logging - Configuration' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, log component, configuration management, log output, log levels, configuration files, Logger, modular, log splitting, log format] +description: "Configuration management functionality of the log component in the GoFrame framework, including how to manage Logger objects through configuration files and configuration methods. The log component supports multiple configuration formats, and its modular design allows independent log output configuration. Configuration items cover log paths, output levels, and terminal displays, with log levels supporting multiple modes to ensure flexible recording of information at each level." +--- + +The log component is one of the core components of the `GoFrame` framework, supporting convenient configuration management capabilities. + +## Configuration File (Recommended) +:::tip +The log configuration uses the unified configuration component of the framework, supporting multiple file formats, as well as configuration centers and interface-based extensions. For more details, please refer to the section: [Configuration](../配置管理/配置管理.md) +::: +The log component supports configuration files. When using `g.Log(instance name)` to obtain the `Logger` instance object, it will automatically obtain the corresponding `Logger` configuration through the default configuration management object. By default, it will read the `logger.instance name` configuration item. If this configuration item does not exist, it will read the default `logger` configuration item. Please refer to the configuration object structure definition for configuration items: [https://pkg.go.dev/github.com/gogf/gf/v2/os/glog#Config](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog#Config) + +The complete configuration file configuration items and descriptions are as follows, with configuration item names being case-insensitive: + +```yaml +logger: + path: "/var/log/" # Log file path. Default is empty, indicating closed, only output to terminal + file: "{Y-m-d}.log" # Log file format. Default is "{Y-m-d}.log" + prefix: "" # Prefix for log content output. Default is empty + level: "all" # Log output level + timeFormat: "2006-01-02T15:04:05" # Custom log output time format, configured using Golang's standard time format + ctxKeys: [] # Custom Context variable names, automatically print Context variables to the log. Default is empty + header: true # Whether to print the header information of the log. Default is true + stdout: true # Whether the log is also output to the terminal. Default is true + rotateSize: 0 # Rotate files based on log file size. Default is 0, indicating that the rotation feature is turned off + rotateExpire: 0 # Rotate files based on log file time interval. Default is 0, indicating that the rotation feature is turned off + rotateBackupLimit: 0 # Clean up split files according to the number of split files, effective when the rotation feature is turned on. Default is 0, meaning no backup, delete when split + rotateBackupExpire: 0 # Clean up split files according to the validity period of split files, effective when the rotation feature is turned on. Default is 0, meaning no backup, delete when split + rotateBackupCompress: 0 # Compression ratio of rotated split files (0-9). Default is 0, meaning no compression + rotateCheckInterval: "1h" # Time detection interval for rotation splitting, generally does not need to be set. Default is 1 hour + stdoutColorDisabled: false # Disable color printing on the terminal. Default is enabled + writerColorEnable: false # Whether the log file is colored. Default is false, indicating no color +``` + +Among them, the `level` configuration item is configured using a string and supports the following configurations according to log levels: `DEBU` < `INFO` < `NOTI` < `WARN` < `ERRO` < `CRIT`, and also supports common deployment mode configuration names such as `ALL`, `DEV`, `PROD`. The `level` configuration item string is not case-sensitive. For a detailed introduction to log levels, please refer to the section [Logging - Log Level](日志组件-日志级别.md). + +### Example 1, Default Configuration Items + +```yaml +logger: + path: "/var/log" + level: "all" + stdout: false +``` + +Then you can use `g.Log()` to automatically obtain and set this configuration when getting the default instance object. + +### Example 2, Multiple Configuration Items + +An example of configuration for multiple `Logger`: + +```yaml +logger: + path: "/var/log" + level: "all" + stdout: false + logger1: + path: "/var/log/logger1" + level: "dev" + stdout: false + logger2: + path: "/var/log/logger2" + level: "prod" + stdout: true +``` + +We can obtain the corresponding configured `Logger` instance objects by the instance object name: + +```go +// Corresponding to logger.logger1 configuration item +l1 := g.Log("logger1") +// Corresponding to logger.logger2 configuration item +l2 := g.Log("logger2") +// Corresponding to default configuration item logger +l3 := g.Log("none") +// Corresponding to default configuration item logger +l4 := g.Log() +``` + +## Configuration Method (Advanced) + +The configuration method is used for developers to perform configuration management themselves when using `glog` modularly. + +List of methods: + +Brief explanation: + +1. You can set using `SetConfig` and `SetConfigWithMap`. +2. You can also use the `Set*` methods of the `Logger` object for specific configurations. +3. It should be noted that configuration items should be set before the `Logger` object executes log output to avoid concurrent safety issues. + +We can use the `SetConfigWithMap` method to set/modify specific configurations of the `Logger` through `Key-Value` pairs, while other configurations use the default configuration. The name of the `Key` is the attribute name in the `Config` `struct`, and it is case-insensitive, supporting `-`/ `_`/ space symbols between words. For details, please refer to the conversion rules in the section [Type Conversion - Struct](../类型转换/类型转换-Struct转换.md). + +Simple example: + +```go +logger := glog.New() +logger.SetConfigWithMap(g.Map{ + "path": "/var/log", + "level": "all", + "stdout": false, + "StStatus": 0, +}) +logger.Print("test") +``` + +Here `StStatus` indicates whether stack printing is enabled, set to `0` to disable. The key name can also be `stStatus`, `st-status`, `st_status`, `St Status`, and other configuration attributes are analogous. + +## Precautions + +Common issues: such as why the log component configuration does not take effect for logs printed by `HTTP Server`, `GRPC Server`, `ORM` components. + +The `GoFrame` framework adopts a modular design, and the log component is an independent component of the framework. The configurations introduced in this chapter only take effect for independently using the log component, such as using `g.Log()` or `glog.New()`. The log configuration of other components has their own configuration items or log object setting methods to achieve log configuration. Please refer specifically to the corresponding component documentation and `API`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..891dca980f6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/glog-chaining' +title: 'Logging - Chaining Operations' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Log Component, Chaining Operations, glog, Log Output, Log Level, File Backtrack, Terminal Output, Asynchronous Log] +description: "The glog module in the GoFrame framework supports chaining operations for logging functionality. This includes setting log output paths, log file categorization, log levels, enabling trace printing, and more. Additionally, it provides examples of setting file backtrack values and implementing asynchronous log output, optimizing the logging experience comprehensively." +--- + +For a complete method list, refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/glog](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog) + +The `glog` module supports a very convenient `chaining operation` method, with the main chaining methods as follows: + +```go +// Redirect log output interface +func To(writer io.Writer) *Logger +// Log content output to directory +func Path(path string) *Logger +// Set log file category +func Cat(category string) *Logger +// Set log file format +func File(file string) *Logger +// Set log print level +func Level(level int) *Logger +// Set log print level (string) +func LevelStr(levelStr string) *Logger +// Set file backtrack value +func Skip(skip int) *Logger +// Enable trace printing +func Stack(enabled bool) *Logger +// Enable trace printing with filter string +func StackWithFilter(filter string) *Logger +// Enable terminal output +func Stdout(enabled...bool) *Logger +// Enable log header information +func Header(enabled...bool) *Logger +// Output log line number information +func Line(long...bool) *Logger +// Asynchronous log output +func Async(enabled...bool) *Logger +``` + +## Example 1, Basic Usage + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + ctx := context.TODO() + path := "/tmp/glog-cat" + g.Log().SetPath(path) + g.Log().Stdout(false).Cat("cat1").Cat("cat2").Print(ctx, "test") + list, err := gfile.ScanDir(path, "*", true) + g.Dump(err) + g.Dump(list) +} +``` + +After execution, the output is: + +```javascript +[ + "/tmp/glog-cat/cat1", + "/tmp/glog-cat/cat1/cat2", + "/tmp/glog-cat/cat1/cat2/2018-10-10.log", +] +``` + +## Example 2, Print Call Line Number + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Line().Print(ctx, "this is the short file name with its line number") + g.Log().Line(true).Print(ctx, "lone file name with line number") +} +``` + +After execution, the terminal output is: + +```html +2019-05-23 09:22:58.141 glog_line.go:8: this is the short file name with its line number +2019-05-23 09:22:58.142 /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/os/glog/glog_line.go:9: lone file name with line number +``` + +## Example 3, File Backtrack `Skip` + +Sometimes we encapsulate the `glog` module using some modules to print logs, such as encapsulating a `logger` package to print logs via `logger.Print`. In this case, the printed call line number is always the same location because, for `glog`, its caller is always the `logger.Print` method. At this time, we can set the backtrack value to skip the backtracked file count, using `SetStackSkip` or the chaining method `Skip`. + +> The setting of the file backtrack value also affects the `Stack` call backtrack print result. + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func PrintLog(ctx context.Context, content string) { + g.Log().Skip(1).Line().Print(ctx, "line number with skip:", content) + g.Log().Line().Print(ctx, "line number without skip:", content) +} + +func main() { + ctx := context.TODO() + PrintLog(ctx, "just test") +} +``` + +After execution, the terminal output is: + +```html +2019-05-23 19:30:10.984 glog_line2.go:13: line number with skip: just test +2019-05-23 19:30:10.984 glog_line2.go:9: line number without skip: just test +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..83bf4adc42e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/core/glog-color' +title: 'Logging - Color Printing' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame framework, log component, color printing, log levels, log configuration, log visibility, terminal color output, file log, log debugging] +description: "Using the log component's color printing feature in the GoFrame framework to enhance log visibility. By adding font colors to highlight different log levels, including Debug, Info, Notice, Warning, Error, etc. Additionally, this article provides examples of enabling log color printing in configuration files and code, and explains the default color settings for different log levels." +--- + +## Color Printing + +It can enhance the visibility of logs. When printing logs, the error level text is highlighted by adding font colors. + +## Effect Example + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Debug(ctx, "Debug") + g.Log().Info(ctx, "Info") + g.Log().Notice(ctx, "Notice") + g.Log().Warning(ctx, "Warning") + g.Log().Error(ctx, "Error") +} +``` + +![](/markdown/14e84e84c66a71247cfb1d19dd4bc07d.png) + +## Using Configuration + +The console will naturally have color output, and file logs do not have colors by default. + +If you need logs in files to also have colors, you can add configuration in the configuration file. + +```yaml +logger: + stdoutColorDisabled: false # Whether to disable terminal color printing. Default is no, which means terminal color output is enabled. + writerColorEnable: false # Whether to enable Writer color printing. Default is no, which means no color output to custom Writers or files. +``` + +You can also add it in the code + +```go +g.Log().SetWriterColorEnable(true) +``` + +The effect is as follows (the red box shows the effect after adding file log color, while the other part shows the effect when it is not enabled) + +![](/markdown/034442032ae97084b092395d8c9daa93.png) + +## Default Color Correspondence Table + +By default, different log levels correspond to the following colors: + +```go +// \v2\os\glog\glog_logger_color.go +var defaultLevelColor = map[int]int{ + LEVEL_DEBU: COLOR_YELLOW, + LEVEL_INFO: COLOR_GREEN, + LEVEL_NOTI: COLOR_CYAN, + LEVEL_WARN: COLOR_MAGENTA, + LEVEL_ERRO: COLOR_RED, + LEVEL_CRIT: COLOR_HI_RED, + LEVEL_PANI: COLOR_HI_RED, + LEVEL_FATA: COLOR_HI_RED, +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..bf70ee33cf7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" @@ -0,0 +1,78 @@ +--- +slug: '/docs/core/glog' +title: 'Logging' +sidebar_position: 4 +hide_title: true +keywords: [glog, log management, high performance, GoFrame, log component, debug management, log level, chaining operations, rotation splitting, singleton pattern] +description: "glog is the core log management module of the GoFrame framework, providing high-performance log management features. It supports file output, log levels, log categorization, and rich debugging and call-tracing features. Create custom Logger objects through glog.New or use the default methods to print logs. Notable features of the glog component include simplicity, support for color printing, asynchronous output, chaining operations, and custom log handling." +--- + +`glog` is a universal high-performance log management module that provides powerful and easy-to-use log management functions and is one of the core components of the `GoFrame` development framework. + +## Introduction + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/glog" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/glog](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog) + +**Brief Description:** + +1. The `glog` module has a fixed log file name format `*.log`, using `.log` as the fixed log file name suffix. +2. `glog` supports file output, log levels, log categorization, debug management, call tracing, chaining operations, rotation splitting, and many more rich features. +3. You can use the `glog.New` method to create `glog.Logger` objects for custom log printing, but it is recommended to use the default package methods provided by `glog` to print logs. +4. When using package methods to modify module configuration, note that any `glog.Set*` setting methods will take effect **globally**. +5. The default time format for log content is `time [level] content newline`, where `time` is accurate to the millisecond level, `level` is optionally output, `content` is the parameter input of the calling side, and `newline` is optionally output (some methods automatically add a newline character to the log content). Example log content: `2018-10-10 12:00:01.568 [ERRO] An error occurred`. +6. `Print*/Debug*/Info*` methods output log content to the standard output (`stdout`), and to prevent the mixing of logs, `Notice*/Warning*/Error*/Critical*/Panic*/Fatal*` methods also output log content to the standard output (`stdout`). +7. The `Panic*` methods will trigger a `panic` error method after outputting log information. +8. The `Fatal*` methods will stop the process running after outputting log information and return a process status code of `1` (the normal program exit status code is `0`). + +## Features + +The `glog` component has the following notable features: + +- Easy to use, powerful functionality +- Supports configuration management with a unified configuration component +- Supports log levels +- Supports color printing +- Supports chaining operations +- Supports specifying output log files/directories +- Supports tracing +- Supports asynchronous output +- Supports stack printing +- Supports debug information switch +- Supports custom `Writer` output interface +- Supports custom log `Handler` processing +- Supports custom log `CtxKeys` keys +- Supports `JSON` format printing +- Supports `Flags` feature +- Supports `Rotate` rotation splitting feature + +## Singleton Object + +The log component supports the singleton pattern, using `g.Log(singleton name)` to obtain different singleton log management objects. The purpose of providing singleton objects is to allow different log management objects to be used for different business scenarios. It is recommended to use the `g.Log()` method to obtain singleton objects for logging operations. This method will automatically read the configuration file and initialize the singleton object, and this initialization operation will only be executed once. + +## Differences between `glog.Print` and `g.Log().Print` + +- `glog` is the package name of the log component, and `g.Log()` is a singleton object of a log component. +- The `g.Log()` singleton object is maintained through the object management component `g` package. During object initialization, it will automatically read the log configuration. It is easy to use, and in most cases, it is recommended to use this method to use the log component. +- `glog` is used in the form of an independent component, and by default, it directly outputs logs to the terminal. You can also set global configuration through configuration management methods or create independent log objects using `New`. + +:::tip +**User**: Why are there two ways to print logs? Which method should I use? + +**Answer**: + +Since each component of the framework is **decoupled**, the framework **can be used as an independent tool library**. For example, if the project only needs to use the log component, you can directly use the methods of the `glog` package, without introducing other components. However, in project engineering use, the use of the tool library may be more cumbersome, often involving configuration and component initialization (in most cases, this will lead to secondary encapsulation). Therefore, the framework also provides a **coupled package** called the `g` package. This package loads some commonly used components by default. `g.Log()` is one of the log components, which will automatically read and initialize the configuration object according to engineering specifications, quickly initialize the log object and achieve singleton management, greatly simplifying the use of logs under engineering. For more introductions, please refer to: [Objects](../对象管理.md) +::: + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..c92befa616d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/core' +title: 'Core Components 🔥' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, core components, GoFrame framework, framework composition, software development, technical framework, development tools, programming fundamentals, application development, system architecture] +description: "Core components are an essential part of the GoFrame framework, providing fundamental development tools and system architecture support, laying a solid and reliable foundation for application development. This document introduces the core components and their application within the GoFrame framework, helping developers better understand and utilize these components." +--- + +Core components are the core part of the `GoFrame` framework. + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..9a920682e28 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gview-xss' +title: 'Template Engine - XSS' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, XSS Handling, HTML Encoding, AutoEncode, SetAutoEncode, Variable Output, Security, Framework Configuration] +description: "XSS handling when using the GoFrame framework's template engine. By default, output variables are not HTML encoded, which may lead to XSS vulnerabilities. However, the GoFrame framework provides flexible configuration options to control encoding effects through the AutoEncode or SetAutoEncode methods to enhance output security." +--- + +By default, the template engine does not use `HTML` encoding for all variable outputs, which means that if not handled properly by developers, there might be XSS vulnerabilities. + +No worries, the `GoFrame` framework has taken this into full consideration and provides developers with flexible configuration parameters to control whether to encode `HTML` content of variable outputs by default. This feature can be enabled/disabled via the `AutoEncode` configuration item or the `SetAutoEncode` method. +:::tip +It is important to note that this feature does not affect the built-in function of `include` templates. +::: +Usage example: + +1. Configuration file + +```toml +[viewer] + delimiters = ["${", "}"] + autoencode = true +``` + +2. Sample code + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + result, _ := g.View().ParseContent(context.TODO(), "Name: ${.name}", g.Map{ + "name": "", + }) + fmt.Println(result) +} +``` + +3. Execution output + +```html +Name: <script>alert('john');</script> +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..91a4dd46c96 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" @@ -0,0 +1,214 @@ +--- +slug: '/docs/core/gview-more' +title: 'Template Engine - Others' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, I18N Support, HTTP Object View, Controller View Management, GoFrame Framework, gview, Template Parsing, WebServer] +description: "Using different features of the template engine in the GoFrame framework, including support for I18N features and implementation in Http objects and controllers. Through example code, we demonstrate template parsing syntax and data isolation management in multi-threaded environments. Also includes usage notes on non-recommended controller registration methods." +--- + +## `I18N` Support + +The template engine supports `i18n` features, allowing you to inject specific `i18n` languages into the context to render different requests/pages in different `i18n` languages. For example: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + ctxCN = gi18n.WithLanguage(context.TODO(), "zh-CN") + ctxJa = gi18n.WithLanguage(context.TODO(), "ja") + content = `{{.name}} says "{#hello}{#world}!"` + ) + + result1, _ := g.View().ParseContent(ctxCN, content, g.Map{ + "name": "john", + }) + fmt.Println(result1) + + result2, _ := g.View().ParseContent(ctxJa, content, g.Map{ + "name": "john", + }) + fmt.Println(result2) +} +``` + +After executing, the output result is: (ensure the current running directory contains `i18n` translation configuration files) + +```html +john says "你好世界!" +john says "こんにちは世界!" +``` + +## HTTP Object View + +The `WebServer` return object in the `goframe` framework provides basic template parsing methods as follows: + +```go +func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap ...map[string]interface{}) error +func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcMap ...map[string]interface{}) error +``` + +Among them, `WriteTpl` is used to output template files, and `WriteTplContent` is used to directly parse and output template content. + +Example of use: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Cookie.Set("theme", "default") + r.Session.Set("name", "john") + r.Response.WriteTplContent(`Cookie:{{.Cookie.theme}}, Session:{{.Session.name}}`, nil) + }) + s.SetPort(8199) + s.Run() +} +``` + +After executing, the output result is: + +``` +Cookie:default, Session:john +``` + +## Controller View Management + +The `goframe` framework provides good template engine support for the routing controller registration method, managed by the `gmvc.View` view object, providing good data `isolation`. The controller view is designed for concurrency safety, allowing asynchronous operations in multi-threading. +:::warning +The controller registration method is similar to the PHP execution process and relatively inefficient, so it is not recommended for future use. +::: +```go +func (view *View) Assign(key string, value interface{}) +func (view *View) Assigns(data gview.Params) + +func (view *View) Parse(file string) ([]byte, error) +func (view *View) ParseContent(content string) ([]byte, error) + +func (view *View) Display(files ...string) error +func (view *View) DisplayContent(content string) error + +func (view *View) LockFunc(f func(vars map[string]interface{})) +func (view *View) RLockFunc(f func(vars map[string]interface{})) +``` + +Usage example 1: + +```go +package main + +import ( + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/frame/gmvc" +) + +type ControllerTemplate struct { + gmvc.Controller +} + +func (c *ControllerTemplate) Info() { + c.View.Assign("name", "john") + c.View.Assigns(map[string]interface{}{ + "age" : 18, + "score" : 100, + }) + c.View.Display("index.tpl") +} + +func main() { + s := ghttp.GetServer() + s.BindController("/template", new(ControllerTemplate)) + s.SetPort(8199) + s.Run() +} +``` + +The template content of `index.tpl` is as follows: + +``` + + + gf template engine + + +

Name: {{.name}}

+

Age: {{.age}}

+

Score:{{.score}}

+ + +``` + +After executing, visiting [http://127.0.0.1:8199/template/info](http://127.0.0.1:8199/template/info) shows the parsed template on the page. If the page reports an error that it cannot find the template file, don't worry, because no template directory setting is made, the default is the current executable directory (`/tmp` for `Linux&Mac`, `C:\Documents and Settings\Username\Local Settings\Temp` for `Windows`). + +The given template file parameter file requires a full filename suffix, such as `index.tpl`, `index.html`, etc. The template engine does not mandate the file extension, allowing full customization by the user. Additionally, the template file parameter also supports the absolute path of the file (complete file path). + +Of course, we can also directly parse the template content, for example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/frame/gmvc" +) + +type ControllerTemplate struct { + gmvc.Controller +} + +func (c *ControllerTemplate) Info() { + c.View.Assign("name", "john") + c.View.Assigns(map[string]interface{}{ + "age" : 18, + "score" : 100, + }) + c.View.DisplayContent(` + + + gf template engine + + +

Name: {{.name}}

+

Age: {{.age}}

+

Score:{{.score}}

+ + + `) +} + +func main() { + s := ghttp.GetServer() + s.BindController("/template", new(ControllerTemplate{})) + s.SetPort(8199) + s.Run() +} +``` + +After executing, visiting [http://127.0.0.1:8199/template/info](http://127.0.0.1:8199/template/info) shows the parsed content as follows: + +``` + + + gf template engine + + +

Name: john

+

Age: 18

+

Score:100

+ + +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..2c08bf4d5c3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" @@ -0,0 +1,304 @@ +--- +slug: '/docs/core/gview-funcs-builtin' +title: 'Template Funcs - Built-In' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, template functions, built-in functions, plus function, minus function, times function, divide function, string operations, date formatting, URL escaping] +description: "Introduction to the built-in functions used in templates within the GoFrame framework. These functions can be used for mathematical calculations, HTML and URL escaping, string processing, date formatting, and data format conversion, including functions like plus, minus, times, divide, enhancing development efficiency." +--- + +## `plus/minus/times/divide` + +| Function | Description | Format | Example | Remarks | +| --- | --- | --- | --- | --- | +| `plus` | **Add** | `{{.value1 \| plus .value2}}` | `{{2 \| plus 3}} => 5` | `3+2` | +| `minus` | **Subtract** | `{{.value1 \| minus .value2}}` | `{{2 \| minus 3}} => 1` | `3-2` | +| `times` | **Multiply** | `{{.value1 \| times .value2}}` | `{{2 \| times 3}} => 6` | `3*2` | +| `divide` | **Divide** | `{{.value1 \| divide .value2}}` | `{{2 \| divide 3}} => 1.5` | `3/2` | + +## `text` + +``` +{{.value | text}} +``` + +Removes HTML tags from the `value` variable and displays only the text content (and removes `script` tags). Example: + +``` +{{"
Test
"|text}} +// Output: Test +``` + +## `htmlencode/encode/html` + +``` +{{.value | htmlencode}} +{{.value | encode}} +{{.value | html}} +``` + +Escapes the `value` variable with HTML encoding. Example: + +``` +{{"
Test
"|html}} +// Output: <div>Test</div> +``` + +## `htmldecode/decode` + +``` +{{.value | htmldecode}} +{{.value | decode}} +``` + +Decodes the HTML encoded `value` variable. Example: + +``` +{{"<div>Test</div>" | htmldecode}} +// Output:
Test
+``` + +## `urlencode/url` + +``` +{{.url | url}} +``` + +Escapes the `url` variable in `URL` encoding. Example: + +``` +{{"https://goframe.org" | url}} +// Output: https%3A%2F%2Fgoframe.org +``` + +## `urldecode` + +``` +{{.url | urldecode}} +``` + +Decodes the `URL` encoded `url` variable. Example: + +``` +{{"https%3A%2F%2Fgoframe.org"|urldecode}} +// Output: https://goframe.org +``` + +## `date` + +``` +{{.timestamp | date .format}} +{{date .format .timestamp}} +{{date .format}} +``` + +Formats the `timestamp` variable into a date and time, similar to PHP's `date` method. The `format` parameter supports [PHP date](http://php.net/manual/en/function.date.php) format. Additionally, refer to [Time](../../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md). + +When `timestamp` is `null` (or `0`), the current time is used as the timestamp parameter. + +Example: + +``` +{{1540822968 | date "Y-m-d"}} +{{"1540822968" | date "Y-m-d H:i:s"}} +{{date "Y-m-d H:i:s"}} +// Output: +// 2018-10-29 +// 2018-10-29 22:22:48 +// 2018-12-05 10:22:00 +``` + +## `compare` + +``` +{{compare .str1 .str2}} +{{.str2 | compare .str1}} +``` + +Compares the strings `str1` and `str2`. Return values: \- 0 : `str1` == `str2` \- 1 : `str1` \> `str2` \- -1 : `str1` < `str2` + +Example: + +``` +{{compare "A" "B"}} +{{compare "1" "2"}} +{{compare 2 1}} +{{compare 1 1}} +// Output: +// -1 +// -1 +// 1 +// 0 +``` + +## `replace` + +``` +{{.str | replace .search .replace}} +{{replace .search .replace .str}} +``` + +Replaces `search` with `replace` in `str`. Example: + +``` +{{"I'm中国人" | replace "I'm" "我是"}} +// Output: +// 我是中国人 +``` + +## `substr` + +``` +{{.str | substr .start .length}} +{{substr .start .length .str}} +``` + +Extracts a substring from `str` starting at index `start` (index starts from 0) for `length` characters, supporting Chinese characters, similar to PHP's `substr` function. Example: + +``` +{{"我是中国人" | substr 2 -1}} +{{"我是中国人" | substr 2 2}} +// Output: +// 中国人 +// 中国 +``` + +## `strlimit` + +``` +{{.str | strlimit .length .suffix}} +``` + +Limits the `str` string to `length` characters, supporting Chinese characters, and appends the `suffix` string if it exceeds the length. Example: + +``` +{{"我是中国人" | strlimit 2 "..."}} +// Output: +// 我是... +``` + +## `concat` + +``` +{{concat .str1 .str2 .str3...}} +``` + +Concatenates strings. Example: + +``` +{{concat "我" "是" "中" "国" "人"}} +// Output: +// 我是中国人 +``` + +## `hidestr` + +``` +{{.str | hidestr .percent .hide}} +``` + +Hides characters in `str` according to a `percent` percentage, hiding towards the center of the string using the `hide` variable, commonly used for names, phone numbers, email addresses, and ID numbers. It supports Chinese characters and email formats. Example: + +``` +{{"热爱GF热爱生活" | hidestr 20 "*"}} +{{"热爱GF热爱生活" | hidestr 50 "*"}} +// Output: +// 热爱GF*爱生活 +// 热爱****生活 +``` + +## `highlight` + +``` +{{.str | highlight .key .color}} +``` + +Highlights the keyword `key` in `str` with the specified color `color`. Example: + +``` +{{"热爱GF热爱生活" | highlight "GF" "red"}} +// Output: +// 热爱GF热爱生活 +``` + +## `toupper/tolower` + +``` +{{.str | toupper}} +{{.str | tolower}} +``` + +Converts the case of `str`. Example: + +``` +{{"gf" | toupper}} +{{"GF" | tolower}} +// Output: +// GF +// gf +``` + +## `nl2br` + +``` +{{.str | nl2br}} +``` + +Replaces `\n/\r` in `str` with HTML `
` tags. Example: + +``` +{{"Go\nFrame" | nl2br}} +// Output: +// Go
Frame +``` + +## `dump` + +``` +{{dump .var}} +``` + +Formats and prints the variable, similar to the `g.Dump` function, often used for development and debugging. Example: + +``` +gview.Assign("var", g.Map{ + "name" : "john", +}) +``` + +``` +{{dump .var}} +// Output: +// +``` + +## `map` + +``` +{{map .var}} +``` + +Converts a template variable to `map[string]interface{}` type, commonly used for `range...end` iteration. + +## `maps` + +``` +{{maps .var}} +``` + +Converts a template variable to `[]map[string]interface{}` type, commonly used for `range...end` iteration. + +## `json/xml/ini/yaml/yamli/toml` + +| Function | Description | Format | +| --- | --- | --- | +| `json` | Converts a template variable to a `JSON` formatted string. | `{{json .var}}` | +| `xml` | Converts a template variable to an `XML` formatted string. | `{{xml .var}}` | +| `ini` | Converts a template variable to an `INI` formatted string. | `{{ini .var}}` | +| `yaml` | Converts a template variable to a `YAML` formatted string. | `{{yaml .var}}` | +| `yamli` | Converts a template variable to a `YAML` formatted string with custom indentation. | `{{yamli .var .indent}}` | +| `toml` | Converts a template variable to a `TOML` formatted string. | `{{toml .var}}` | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..7c45e7f414c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" @@ -0,0 +1,252 @@ +--- +slug: '/docs/core/gview-funcs-index' +title: 'Template Funcs - Intro' +sidebar_position: 0 +hide_title: true +keywords: [Golang, GoFrame, template engine, basic functions, logical operations, data comparison, parameter passing, function call, standard library, template rendering] +description: "Basic functions from the Golang standard library and their improved usage in the GoFrame framework. Practical examples demonstrate how to pass parameters between different template functions and how to flexibly call functions. The usage of and, or, not, call, index functions, as well as the improved automatic type conversion for eq/ne/lt/le/gt/ge comparison functions in the GoFrame framework, are detailed." +--- +:::tip +Below are some basic syntax and basic functions from the Golang standard library. The GoFrame framework has made necessary improvements to some basic functions. +::: +Variables can be passed between functions using the `|` symbol + +``` +{{.value | Func1 | Func2}} +``` + +Use parentheses + +``` +{{printf "nums is %s %d" (printf "%d %d" 1 2) 3}} +``` + +## `and` + +``` +{{and .X .Y .Z}} +``` + +`and` will evaluate each argument and return the first empty argument, otherwise, it returns the last non-empty argument. + +## `call` + +``` +{{call .Field.Func .Arg1 .Arg2}} +``` + +`call` can invoke a function and pass in parameters. + +The called function needs to return 1 or 2 values. When returning two values, the second value is used to return an error of type `error`. Execution will terminate if the returned error is not `nil`. + +## `index` + +`index` supports `map`, `slice`, `array`, `string`, and reads the value of the specified index for these types. + +``` +{{index .Maps "name"}} +``` + +## `len` + +``` +{{printf "The content length is %d" (.Content|len)}} +``` + +Returns the length of the corresponding type, supporting types: `map`, `slice`, `array`, `string`, `chan`. + +## `not` + +`not` returns the negation of the input argument. + +For example, to determine if a variable is empty: + +``` +{{if not .Var}} +// Executes when empty (.Var is empty, like: nil, 0, "", slice/map of length 0) +{{else}} +// Executes when not empty (.Var is not empty) +{{end}} +``` + +## `or` + +``` +{{or .X .Y .Z}} +``` + +`or` will evaluate each argument and return the first non-empty argument, otherwise, it returns the last argument. + +## `print` + +Same as `fmt.Sprint`. + +## `printf` + +Same as `fmt.Sprintf`. + +## `println` + +Same as `fmt.Sprintln`. + +## `urlquery` + +``` +{{urlquery "http://johng.cn"}} +``` + +Will return + +``` +http%3A%2F%2Fjohng.cn +``` + +## `eq / ne / lt / le / gt / ge` + +These functions are generally used within an `if` statement + +``` +`eq`: arg1 == arg2 +`ne`: arg1 != arg2 +`lt`: arg1 < arg2 +`le`: arg1 <= arg2 +`gt`: arg1 > arg2 +`ge`: arg1 >= arg2 +``` + +The `eq` function is different from the others in that it supports multiple arguments. + +``` +{{eq arg1 arg2 arg3 arg4}} +``` + +Is the same as the following logical evaluation: + +``` +arg1==arg2 || arg1==arg3 || arg1==arg4 ... +``` + +Used with `if` + +``` +{{if eq true .Var1 .Var2 .Var3}}...{{end}} +``` + +``` +{{if lt 100 200}}...{{end}} +``` + +For example, executing when the variable is not empty: + +``` +{{if .Var}} +// Executes when not empty (.Var is not empty) +{{else}} +// Executes when empty (.Var is empty, like: nil, 0, "", slice/map of length 0) +{{end}} +``` + +### Comparison Function Improvements + +The GoFrame framework's template engine has made necessary improvements to the standard library's comparison template functions `eq/ne/lt/le/gt/ge`, to support comparison of any data type. For instance, the following comparison in the standard library template: + +``` +{{eq 1 "1"}} +``` + +Will cause an error: + +``` +panic: template: at : error calling eq: incompatible types for comparison +``` + +Because the two parameters being compared are not of the same data type, a `panic` error is triggered. + +In the GoFrame framework's template engine, the two parameters will be automatically converted to the same data type before comparison, providing a better development experience and greater flexibility. If both parameters are integer variables (or integer strings), they will be converted to integers for comparison; otherwise, they will be converted to string variables for comparison (case-sensitive). + +### Improved Execution Example + +Let's look at an example of running comparison template functions in the GoFrame framework's template engine. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + tplContent := ` +eq: +eq "a" "a": {{eq "a" "a"}} +eq "1" "1": {{eq "1" "1"}} +eq 1 "1": {{eq 1 "1"}} + +ne: +ne 1 "1": {{ne 1 "1"}} +ne "a" "a": {{ne "a" "a"}} +ne "a" "b": {{ne "a" "b"}} + +lt: +lt 1 "2": {{lt 1 "2"}} +lt 2 2 : {{lt 2 2 }} +lt "a" "b": {{lt "a" "b"}} + +le: +le 1 "2": {{le 1 "2"}} +le 2 1 : {{le 2 1 }} +le "a" "a": {{le "a" "a"}} + +gt: +gt 1 "2": {{gt 1 "2"}} +gt 2 1 : {{gt 2 1 }} +gt "a" "a": {{gt "a" "a"}} + +ge: +ge 1 "2": {{ge 1 "2"}} +ge 2 1 : {{ge 2 1 }} +ge "a" "a": {{ge "a" "a"}} +` + content, err := g.View().ParseContent(context.TODO(), tplContent, nil) + if err != nil { + panic(err) + } + fmt.Println(content) +} +``` + +After running, the output result is: + +``` +eq: +eq "a" "a": true +eq "1" "1": true +eq 1 "1": true + +ne: +ne 1 "1": false +ne "a" "a": false +ne "a" "b": true + +lt: +lt 1 "2": true +lt 2 2 : false +lt "a" "b": true + +le: +le 1 "2": true +le 2 1 : false +le "a" "a": true + +gt: +gt 1 "2": false +gt 2 1 : true +gt "a" "a": false + +ge: +ge 1 "2": false +ge 2 1 : true +ge "a" "a": true +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..d650e0970f3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gview-funcs-custom' +title: 'Template Funcs - Custom' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, template functions, custom functions, view object, global binding, object assignment, method invocation, pipeline argument passing, template parsing] +description: "In the GoFrame framework, developers can define custom template functions and globally bind them to specified view objects. Additionally, custom objects can be assigned to templates, allowing method calls on these objects. The sample code demonstrates how to define and bind template functions, and how to pass parameters for template parsing in both regular and pipeline ways." +--- + +## Introduction + +Developers can define custom template functions and globally bind them to specified view objects. +:::tip +Custom objects can also be assigned to templates, allowing method calls on those objects. +::: +## Example + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +// Test built-in function with parameters +func funcHello(name string) string { + return fmt.Sprintf(`Hello %s`, name) +} + +func main() { + // Bind global template function + g.View().BindFunc("hello", funcHello) + + // Pass parameters in a regular way + parsed1, err := g.View().ParseContent(context.TODO(), `{{hello "GoFrame"}}`, nil) + if err != nil { + panic(err) + } + fmt.Println(string(parsed1)) + + // Pass parameters through a pipeline + parsed2, err := g.View().ParseContent(context.TODO(), `{{"GoFrame" | hello}}`, nil) + if err != nil { + panic(err) + } + fmt.Println(string(parsed2)) +} +``` + +After execution, the output is: + +``` +Hello GoFrame +Hello GoFrame +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..555653b5a8c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/core/gview-funcs' +title: 'Template Engine - Funcs' +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, Template Functions, Documentation Page, Front-end Development, Dynamic Templates, View Rendering, Function Extension, Programming Guide] +description: "Use the template engine and template functions in the GoFrame framework to implement dynamic templates and view rendering in web applications. By customizing and extending template functions, developers can perform front-end development more efficiently and enhance the flexibility of applications." +--- +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..e3d1a9d2e18 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gview-variable' +title: 'Template Engine - Variables' +sidebar_position: 3 +hide_title: true +keywords: [Template Engine, Template Variables, Custom Objects, Object Properties, Object Methods, GoFrame, GoFrame Framework, Template Parsing, WebServer Built-in Variables, GoFrame Tutorial] +description: "How to use custom objects as template variables in the template engine, and access object properties and call methods within the template. This is explained in detail through examples, illustrating how to implement template content parsing in the GoFrame framework, as well as the differences between using object pointers and object variables, and the rules for method calls, helping developers better master the templating technology in the GoFrame framework." +--- + +## Built-in Variables + +Please refer to the [Response - Template Parsing](../../WEB服务开发/数据返回/数据返回-模板解析.md) section for `WebServer` built-in variables. + +## Variable Objects + +We can use custom objects in templates and access the properties of objects and invoke their methods within the template. + +Example: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" +) + +type T struct { + Name string +} + +func (t *T) Hello(name string) string { + return "Hello " + name +} + +func (t *T) Test() string { + return "This is test" +} + +func main() { + t := &T{"John"} + v := g.View() + content := `{{.t.Hello "there"}}, my name's {{.t.Name}}. {{.t.Test}}.` + if r, err := v.ParseContent(context.TODO(), content, g.Map{"t": t}); err != nil { + g.Dump(err) + } else { + g.Dump(r) + } +} +``` + +Here, the variables assigned to the template can be either `object pointers` or `object variables`. However, note the defined object methods: if it is an object pointer, you can only call methods where the receiver is an object pointer; if it is an object variable, you can only call methods where the receiver is an object. + +After execution, the output is: + +``` +Hello there, my name's John. This is test. +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" new file mode 100644 index 00000000000..81c2b1b24a2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" @@ -0,0 +1,171 @@ +--- +slug: '/docs/core/gview-layout' +title: 'Template Engine - Layout' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gview,Template Engine,layout,define,template,include,Template Layout,Template Variables] +description: "The layout template layout method of the gview template engine in the GoFrame framework. gview supports two layout methods: content block management through define and template tags, and template embedding through include tags. Both methods support the transfer of template variables. The sample code demonstrates how to use these template engine techniques in the GoFrame framework." +--- + +The `gview` template engine supports two types of `layout` template layouts: + +1. `define` + `template` method +2. `include` template embedding method + +Both methods support passing template variables. + +### `define` + `template` + +Since `gview` uses `ParseFiles` in the underlying layer to parse template files in bulk, you can use the `define` tag to define template content blocks and use the `template` tag to introduce specified template content blocks in any other template files. The `template` tag supports cross-template referencing, meaning that the template content block defined by the `define` tag may be in other template files, and the `template` can be freely introduced as well. + +:::warning +Note: +- When passing template variables to a nested child template, use the syntax: `{{template "xxx" .}}`. +- The file extension of the template file should be consistent with the `define template` file extension. +::: + +Example Usage: + +![](/markdown/5c50dcf4b78634b414c3857035097292.png) + +1. `layout.html` +```html + + + + GoFrame Layout + {{template "header" .}} + + +
+ {{template "container" .}} +
+ + + +``` + +2. `header.html` +```html +{{define "header"}} +

{{.header}}

+{{end}} +``` + +3. `container.html` +```html +{{define "container"}} +

{{.container}}

+{{end}} +``` + +4. `footer.html` +```html +{{define "footer"}} +

{{.footer}}

+{{end}} +``` + +5. `main.go` +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "header": "This is header", + "container": "This is container", + "footer": "This is footer", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, visit [http://127.0.0.1:8199](http://127.0.0.1:8199/) and the result is as follows: + +![](/markdown/8826a2512ed91b4c9e7b77eabad4ae2c.png) + +### `include` Template Embedding + +Of course, we can also use the `include` tag to achieve page layout. + +:::warning +Note: When passing template variables to a nested child template, use the syntax: `{{include "xxx" .}}`. +::: + +Example Usage: + +![](/markdown/21b9ad277927db37879d5513766557c2.png) + +1. `layout.html` +```html +{{include "header.html" .}} +{{include .mainTpl .}} +{{include "footer.html" .}} +``` + +2. `header.html` +```html +

HEADER

+``` + +3. `footer.html` +```html +

FOOTER

+``` + +4. `main1.html` +```html +

MAIN1

+``` + +5. `main2.html` +```html +

MAIN2

+``` + +6. `main.go` +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/main1", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "mainTpl": "main/main1.html", + }) + }) + s.BindHandler("/main2", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "mainTpl": "main/main2.html", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, visiting different route addresses will show different results: + +1. [http://127.0.0.1:8199/main1](http://127.0.0.1:8199/main1) + +![](/markdown/88d87dc79a4aa226d1c20312c3aeaff0.png) + +2. [http://127.0.0.1:8199/main2](http://127.0.0.1:8199/main2) + +![](/markdown/8db789d456e0422ca3796242b89b8b44.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" new file mode 100644 index 00000000000..32666f22d86 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" @@ -0,0 +1,202 @@ +--- +slug: '/docs/core/gview-tags' +title: 'Template Engine - Tags' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, Template Tags, gview.SetDelimiters, pipeline, if...else, range, define, template, include] +description: "The basic usage of template engines and template tags in the GoFrame framework, including how to customize template delimiters, use pipelines to pass data in templates, usage of conditional statements if...else, range to iterate types like slice, map, using define to define template blocks, template tag combined with define for template nesting and reuse, and the difference in usage of the include tag in the GoFrame framework." +--- + +The template engine uses `{{` and `}}` as the default left and right delimiters. Developers can set custom template delimiters using the `gview.SetDelimiters` method. + +Use `.` to access the value of the current object (template local variables). + +Use `$` to reference the root context of the current template (template global variables). + +Use `$var` to access specific template variables. + +**Supported `go` language symbols in the template** + +``` +{{"string"}} // General string +{{`raw string`}} // Raw string +{{'c'}} // Byte +{{print nil}} // nil is also supported +``` + +**Pipeline in the template** + +It can be output from context variables or the return values passed through the pipeline from functions. + +``` +{{. | FuncA | FuncB | FuncC}} +``` + +When the value of the `pipeline` is: + +- `false` or `0` +- `nil pointer` or `interface` +- Length of `0` for `array`, `slice`, `map`, `string` + +Then this `pipeline` is considered empty. +:::warning +Note: In the `gf` template engine, when a specified variable shown in the template does not exist, it will appear empty (the standard library template engine would show ``). +::: +## `if … else … end` + +``` +{{if pipeline}}...{{end}} +``` + +When `if` evaluates, if the `pipeline` is empty, it is equivalent to evaluating to `false`. + +Nested loops are supported. + +``` +{{if .condition}} + ... +{{else}} + {{if .condition2}} + ... + {{end}} +{{end}} +``` + +You can also use `else if` + +``` +{{if .condition}} + ... +{{else if .condition2}} + ... +{{else}} + ... +{{end}} +``` + +## `range … end` + +``` +{{range pipeline}} {{.}} {{end}} +``` + +The types supported by the `pipeline` are `slice`, `map`, `channel`. + +Note: Inside the `range` loop, the `.` symbol will be overridden to the sub-elements of the above types (local variables). To access external variables (global variables) within the loop, add the `$` symbol, such as `{{$.Session.Name}}`. + +Additionally, when the corresponding value length is `0`, `range` will not execute, and `.` will not change. + +For example, to iterate a `map`: + +``` +{{range $key, $value := .MapContent}} + {{$key}}:{{$value}} +{{end}} +``` + +For example, to iterate a `slice`: + +``` +{{range $index, $elem := .SliceContent}} + {{range $key, $value := $elem}} + {{$key}}:{{$value}} + {{end}} +{{end}} +``` + +## `with … end` + +``` +{{with pipeline}}...{{end}} +``` + +`with` is used to redirect the `pipeline` + +``` +{{with .Field.NestField.SubField}} + {{.Var}} +{{end}} +``` + +## `define` + +`define` can be used to **customize template content blocks** (define a template name for a section of template content). It can be used for module definition and template nesting (used in the `template` tag). + +``` +{{define "loop"}} +
  • {{.Name}}
  • +{{end}} +``` + +Where `loop` is the name of the template content block, which can subsequently be called using the `template` tag: + +``` +
      + {{range .Items}} + {{template "loop" .}} + {{end}} +
    +``` +:::warning +The `define` tag needs to be used in conjunction with the `template` tag and supports cross-template usage (effective within the same template directory/subdirectory, achieved by using the `ParseFiles` method to parse template files). +::: +## `template` + +``` +{{template "templateName" pipeline}} +``` + +The corresponding context `pipeline` is passed to the template for it to be called within the template. + +Note: The parameter for the `template` tag is `templateName`, not the template file path. The `template` tag does not support template file paths. +:::warning +The `template` tag needs to be used in conjunction with the `define` tag and supports cross-template usage (effective within the same template directory/subdirectory, achieved by using the `ParseFiles` method to parse template files). +::: +## `include` + +**This is a new tag added for `gf` framework template engines** + +``` +{{include "templateFileName(with full file suffix)" pipeline}} +``` + +The `include` tag can be used in the template to load other templates (any path). The template file name supports **relative path** as well as file system **absolute path**. To pass the template variables of the current template to the sub-template (nested template), you can do as follows: + +``` +{{include "templateFileName(with full file suffix)" .}} +``` + +The difference with the `template` tag is that `include` only supports **file paths** and does not support **template names**; whereas, the `template` tag only supports **template names** and does not support **file paths**. + +## Comments + +Multiline text comments are allowed but cannot be nested. + +``` +{{/* +comment content +support new line +*/}} +``` + +## Remove Whitespace + +Remove leading and trailing whitespace. + +``` +# Use {{- syntax to remove all whitespace on the left of the template content, and use -}} to remove all whitespace on the right of the template content. + +# Note: - should be adjacent to {{ and }}, and there should be a space between the syntax and the template value. + +{{- .Name -}} + +{{- range $key, $value := .list}} + "{{$value}}" +{{- end}} +``` + +## More Usage Instructions + +[template package - html/template - Go Packages](https://pkg.go.dev/html/template) + +[template package - text/template - Go Packages](https://pkg.go.dev/text/template) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..3ed7df819a3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" @@ -0,0 +1,131 @@ +--- +slug: '/docs/core/gview-config' +title: 'Template Engine - Configuration' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, Configuration Management, View Component, Template Configuration, gview, Template Parsing, XSS Encoding, Singleton Object] +description: "The view component of the GoFrame framework is one of its cores, supporting convenient configuration management. This article provides a detailed introduction on managing view components through configuration files, including the definitions and examples of configuration items. It also explains how to use the SetConfigWithMap method for specific configuration settings to ensure proper template parsing." +--- + +The view component is one of the core components of the `GoFrame` framework, and it also supports very convenient configuration management. + +## Configuration Object + +Configuration object definition: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gview#Config](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview#Config) + +## Configuration File + +The view component supports configuration files. When using `g.View(singleton name)` to get the `View` singleton object, it automatically retrieves the corresponding `View` configuration through the default configuration management object. By default, it reads the `viewer.singleton name` configuration item. If this configuration item does not exist, it reads the `viewer` configuration item. + +The complete configuration file items and descriptions are as follows, where configuration item names are case-insensitive: + +``` +[viewer] + Paths = ["/var/www/template"] # Template file search directory path, it is recommended to use absolute paths. Default is the current program's working path + DefaultFile = "index.html" # Default template engine file for parsing. Default is "index.html" + Delimiters = ["${", "}"] # Template engine variable delimiters. Default is ["{{", "}}"] + AutoEncode = false # Whether to perform XSS encoding on variable content by default. Default is false + [viewer.Data] # Custom global Key-Value pairs, which can be directly used in template parsing + Key1 = "Value1" + Key2 = "Value2" +``` + +### Example 1, Default Configuration Items + +``` +[viewer] + paths = ["template", "/var/www/template"] + defaultFile = "index.html" + delimiters = ["${", "}"] + [viewer.data] + name = "gf" + version = "1.10.0" +``` + +Then, you can use `g.View()` to automatically get and set this configuration when obtaining the default singleton object. + +### Example 2, Multiple Configuration Items + +Example of configuration for multiple `View` objects: + +``` +[viewer] + paths = ["template", "/var/www/template"] + defaultFile = "index.html" + delimiters = ["${", "}"] + [viewer.data] + name = "gf" + version = "1.10.0" + [viewer.view1] + defaultFile = "layout.html" + delimiters = ["${", "}"] + [viewer.view2] + defaultFile = "main.html" + delimiters = ["#{", "}"] +``` + +We can obtain the corresponding `View` singleton object configuration through the singleton object name: + +```go +// Corresponds to viewer.view1 configuration item +v1 := g.View("view1") +// Corresponds to viewer.view2 configuration item +v2 := g.View("view2") +// Corresponds to the default configuration item viewer +v3 := g.View("none") +// Corresponds to the default configuration item viewer +v4 := g.View() +``` + +### Configuration Methods + +Method list: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gview](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview) + +Brief introduction: + +1. You can set using `SetConfig` and `SetConfigWithMap`. +2. You can also use the `Set*` method of the `View` object for specific configuration settings. +3. It is crucial to set the configuration items before the `View` object executes template parsing to avoid concurrency safety issues. + +### `SetConfigWithMap` Method + +We can use the `SetConfigWithMap` method to set/modify specific configurations of the `View` using `Key-Value` pairs, while the remaining configurations use the default configuration. The `Key` name is the attribute name in the `Config` struct, which is case-insensitive, and it supports using `-`/ `_`/ `space` symbols between words. Refer to the [Type Conversion - Struct](../类型转换/类型转换-Struct转换.md) section for conversion rules. + +Simple example: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + view := gview.New() + view.SetConfigWithMap(g.Map{ + "Paths": []string{"template"}, + "DefaultFile": "index.html", + "Delimiters": []string{"${", "}"}, + "Data": g.Map{ + "name": "gf", + "version": "1.10.0", + }, + }) + result, err := view.ParseContent(context.TODO(), "hello ${.name}, version: ${.version}") + if err != nil { + panic(err) + } + fmt.Println(result) +} +``` + +Here, `DefaultFile` represents the default template file for parsing. The key name can also be `defaultFile`, `default-File`, `default_file`, `default file`, and other configuration properties can be deduced similarly. + +## Notes + +It's common for users to ask, why isn't my template parsing effective? Why does the page directly display the tags I wrote as is? + +At this time, please check whether your configuration file has set the template tags. A common scenario is setting `delimiters` to `["${", "}"]`, but using `["{{", "}}"]` in the template. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" new file mode 100644 index 00000000000..58b07d5a285 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" @@ -0,0 +1,198 @@ +--- +slug: '/docs/core/gview' +title: 'Template Engine' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Template Engine,gview,Template Management,Cache Mechanism,Auto-detect Updates,Template Directory,Template Variables,Template Functions] +description: "GoFrame framework's template engine offers simple, easy-to-use, and powerful features, supporting multiple template directory searches, layout template design, and automatic updates of template files, among others. Through the gview module, general view management is implemented, supporting singleton pattern for template objects and the use of view object managers, facilitating configuration of template directories and rendering of template variables and functions." +--- + +## Features of the Template Engine + +1. Simple, easy to use, powerful; +2. Supports multiple template directory searches; +3. Supports `layout` template design; +4. Supports singleton mode for template view objects; +5. Natively integrated with the configuration management module, easy to use; +6. Utilizes a two-level cache design at the core, offering high performance; +7. Adds new template tags and numerous built-in template variables and functions; +8. Supports automatic cache update mechanism upon template file modification, which is more developer-friendly; +9. `define`/`template` tags support cross-template invocation (including template files under the same template path with subdirectories); +10. `include` tag supports importing template files from any path; + +## General View Management + +General view management involves using the native template engine `gview` module to implement template management, including template reading and display, template variable rendering, etc. You can use the method `gview.Instance` to obtain a view singleton object and retrieve it according to the singleton object name. You can also obtain the default singleton `gview` object through the object manager `g.View()`. + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gview](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview) + +Brief Explanation: + +1. `gview.Get` is used to obtain the corresponding singleton template engine object based on a given template directory path; +2. `gview.New` can also create a template engine object based on the given template directory path but without singleton management; +3. `SetPath/AddPath` is used to set/add the template directory path of the current template engine object, where `SetPath` will overwrite all template directory settings, and `AddPath` is recommended; +4. `Assign/Assigns` is used to set template variables, and all templates parsed by the template engine can use these template variables; +5. `BindFunc` is used to bind template functions; for detailed usage, refer to subsequent examples; +6. `Parse/ParseContent` is used to parse template files/content, allowing for temporary template variables and template functions to be given during parsing; +7. `SetDelimiters` is used to set the template parsing delimiters of the template engine object, defaulting to `{{ }}` (conflicts with the `vuejs` front-end framework); + +:::warning +Note: Starting with `goframe v1.16`, all template parsing methods have an additional first input parameter which is the `Context`. +::: + +### Example 1: Parsing a Template File + +`index.tpl` + +``` +id:{{.id}}, name:{{.name}} +``` + +`main.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/template", func(r *ghttp.Request) { + r.Response.WriteTpl("index.tpl", g.Map{ + "id": 123, + "name": "john", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, visiting [http://127.0.0.1:8199/template](http://127.0.0.1:8199/template) will show the parsed content as: `id:123, name:john` + +### Example 2: Parsing Template Content + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/template", func(r *ghttp.Request){ + tplContent := `id:{{.id}}, name:{{.name}}` + r.Response.WriteTplContent(tplContent, g.Map{ + "id" : 123, + "name" : "john", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +After execution, visiting [http://127.0.0.1:8199/template](http://127.0.0.1:8199/template) will show the parsed content as: `id:123, name:john` + +### Example 3: Custom Template Delimiters + +In projects, we often encounter a conflict between `Golang` default template variable delimiters and `Vue` variable delimiters (both use `{{ }}`). We can use the `SetDelimiters` method to customize the global `Golang` template variable delimiters: + +```go +// main.go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + v := g.View() + v.SetDelimiters("${", "}") + b, err := v.Parse( + context.TODO(), + "gview_delimiters.tpl", + map[string]interface{}{ + "k": "v", + }) + fmt.Println(err) + fmt.Println(b) +} +``` + +```html + +test.tpl content, vars: ${.} +``` + +After execution, the resulting output is: + +```html + +test.tpl content, vars: map[k:v] +``` + +## Directory Configuration Method + +The `GoFrame` framework's template engine supports a highly flexible multi-directory auto-search function. The `SetPath` can modify the template directory to a single directory address. Additionally, the `AddPath` method can add multiple search directories, and the template engine will prioritize the added directories according to the order they were added, performing automatic retrieval. It searches until it finds a matching file path. If no template file is found in all search directories, it returns failure. + +**Default Directory Configuration**: + +When initializing the `gview` view object, the following template file search directories are automatically added by default: + +1. **The current working directory and its `template` subdirectory**: For example, if the current working directory is `/home/www`, `/home/www` and `/home/www/template` will be added; +2. **The directory where the current executable is located and its `template` subdirectory**: For example, if the binary file is located in `/tmp`, `/tmp` and `/tmp/template` will be added; +3. **The directory where the `main` source code package is located and its `template` subdirectory** (effective only in the source code development environment): For example, if the `main` package is located in `/home/john/workspace/gf-app`, `/home/john/workspace/gf-app` and `/home/john/workspace/gf-app/template` will be added; + +## Modifying the Template Directory + +The view object's template file search directory can be modified in the following ways. The view object will only perform configuration file retrieval in the specified directory: + +1. (Recommended) Retrieve the global View object in singleton mode and modify it via the `SetPath` method; +2. Modify command line start parameters - `gf.gview.path`; +3. Modify specified environment variables - `GF_GVIEW_PATH`; + +For example, if our executable file is `main`, the template engine's template directory can be modified in the following ways on Linux: + +1. (Recommended) Through singleton mode + +```go +g.View().SetPath("/opt/template") +``` + +2. Via command line parameters + +```shell +./main --gf.gview.path=/opt/template/ +``` + +3. Via environment variables + - Modify the environment variable at startup: + + ```shell + GF_GVIEW_PATH=/opt/config/; ./main + ``` + + - Use the `genv` module to modify the environment variable: + + ```go + genv.Set("GF_GVIEW_PATH", "/opt/template") + ``` + +## Automatic Update Detection + +The template engine uses a carefully designed **cache mechanism**. When a template file is first read, it is cached in memory, and future reads will directly access the cache to improve execution efficiency. Additionally, the template engine provides an **automatic update detection mechanism** for template files. When a template file is modified externally, the template engine can promptly detect it and refresh the cache content of the template file. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..b75dab3200a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/core/gconv-converter' +title: 'Type Conversion - Converter' +sidebar_position: 5 +hide_title: true +keywords: [type conversion, Converter feature, GoFrame, custom conversion, struct conversion, attribute conversion, alias type, gconv.Scan, gconv.ConvertWithRefer, data structure] +description: "From version v2.6.2 of the GoFrame framework, the conversion component adds the Converter feature, allowing developers to customize type conversion logic. This document showcases how to use the Converter feature for registering and applying custom conversion methods for data structures and alias types, as well as how to handle these types of conversions with the help of gconv.Scan and gconv.ConvertWithRefer methods." +--- + +Starting from version v2.6.2 of the framework, the conversion component provides the `Converter` feature, allowing developers to customize conversion methods to specify conversion logic between specific types. + +## Conversion Method Definition + +The conversion method is defined as follows: + +```go +func(T1) (T2, error) +``` + +Where `T1` needs to be a non-pointer object, and `T2` needs to be a pointer type. If the types are incorrect, the registration of the conversion method will result in an error. +:::tip +The design requiring the input parameter (`T1`) to be a non-pointer object aims to ensure the safety of input parameters, minimizing potential issues outside the scope of the conversion method. +::: +The function to register a conversion method is as follows: + +```go +// RegisterConverter to register custom converter. +// It must be registered before you use this custom converting feature. +// It is suggested to do it in boot procedure of the process. +// +// Note: +// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`. +// It will convert type `T1` to type `T2`. +// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer. +func RegisterConverter(fn interface{}) (err error) +``` + +## Struct Type Conversion + +A common custom data structure conversion involves conversion between structs. Let's look at two examples. + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/util/gconv" +) + +type Src struct { + A int +} + +type Dst struct { + B int +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var ( + src = Src{A: 1} + dst *Dst + ) + err = gconv.Scan(src, &dst) + if err != nil { + panic(err) + } + + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var ( + srcWrap = SrcWrap{Src{A: 1}} + dstWrap *DstWrap + ) + err = gconv.Scan(srcWrap, &dstWrap) + if err != nil { + panic(err) + } + + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +In this example code, two conversion scenarios are demonstrated: custom struct conversion and automatic conversion of structs as attributes. The conversion method used is the generic struct conversion method `gconv.Scan`. The internal implementation will automatically determine if a custom type conversion function exists and prioritize its use; otherwise, the default conversion logic will be used. + +Upon execution, the terminal outputs: + +``` +src: {1} +dst: &{1} +srcWrap: {{1}} +dstWrap: &{{1}} +``` + +In addition to using the `gconv.Scan` method, we can also use the `gconv.ConvertWithRefer` method to achieve type conversion, both of which have the same effect: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/util/gconv" +) + +type Src struct { + A int +} + +type Dst struct { + B int +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A}, nil +} + +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var src = Src{A: 1} + dst := gconv.ConvertWithRefer(src, Dst{}) + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var srcWrap = SrcWrap{Src{A: 1}} + dstWrap := gconv.ConvertWithRefer(srcWrap, &DstWrap{}) + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +## Alias Type Conversion + +We can also use the `Converter` feature to implement conversions for **alias types**. Alias types are not limited to structs and can also be aliases for basic types such as `int, string`, etc. Here are two examples. + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +type MyTime = *gtime.Time + +type Src struct { + A MyTime +} + +type Dst struct { + B string +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A.Format("Y-m-d")}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var ( + src = Src{A: gtime.Now()} + dst *Dst + ) + err = gconv.Scan(src, &dst) + if err != nil { + panic(err) + } + + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var ( + srcWrap = SrcWrap{Src{A: gtime.Now()}} + dstWrap *DstWrap + ) + err = gconv.Scan(srcWrap, &dstWrap) + if err != nil { + panic(err) + } + + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +The `type xxx = yyy` in the code is due to the needs of the `*gtime.Time` type. Other types can choose whether to use the alias symbol `=` as needed. For example, basic types such as `int, string` do not need the alias symbol. + +Upon execution, the terminal outputs: + +``` +src: {2024-01-22 21:45:28} +dst: &{2024-01-22} +srcWrap: {{2024-01-22 21:45:28}} +dstWrap: &{{2024-01-22}} +``` + +Similarly, in addition to using the `gconv.Scan` method, we can also use the `gconv.ConvertWithRefer` method to achieve type conversion, both of which have the same effect: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +type MyTime = *gtime.Time + +type Src struct { + A MyTime +} + +type Dst struct { + B string +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A.Format("Y-m-d")}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var src = Src{A: gtime.Now()} + dst := gconv.ConvertWithRefer(src, &Dst{}) + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var srcWrap = SrcWrap{Src{A: gtime.Now()}} + dstWrap := gconv.ConvertWithRefer(srcWrap, &DstWrap{}) + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..20cd15fbfa5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" @@ -0,0 +1,218 @@ +--- +slug: '/docs/core/gconv-map' +title: 'Type Conversion - Map' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Type Conversion, Map Conversion, GoFrame Framework, Property Tags, Recursive Conversion, Custom Tags, MapDeep, struct Conversion, gconv] +description: "Use the gconv.Map method in the GoFrame framework for type conversion, including converting any map or struct/*struct type to a map[string]interface{} type. It supports property tags and custom tags, and can achieve recursive conversion through the MapDeep method to parse out the detailed structure of nested objects, suitable for multi-layer data processing." +--- + +`gconv.Map` supports converting any `map` or `struct`/ `*struct` type to the commonly used `map[string]interface{}` type. When the conversion parameter is of type `struct`/ `*struct`, it automatically recognizes the `c/gconv/json` tags of `struct`, and you can specify custom conversion tags and the priority of multiple tag parsing via the second parameter `tags` of the `Map` method. If the conversion fails, it returns `nil`. +:::tip +Property Tags: When converting `struct`/ `*struct` types, it supports `c/gconv/json` property tags, as well as `-` and `omitempty` tag properties. When using the `-` tag property, it means that the property will not be converted. When using the `omitempty` tag property, it means that the conversion will not be performed when the property is empty (nil pointer, number `0`, string `""`, empty array `[]`, etc.). Please refer to the examples below for details. +::: +Common conversion methods: + +```go +func Map(value interface{}, tags ...string) map[string]interface{} +func MapDeep(value interface{}, tags ...string) map[string]interface{} +``` + +Among them, `MapDeep` supports recursive conversion, which will recursively convert the `struct`/ `*struct` objects within the properties. +:::tip +For more map-related conversion methods, please refer to the interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: +## Basic Example + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int `c:"uid"` + Name string `c:"name"` + } + // Object + g.Dump(gconv.Map(User{ + Uid: 1, + Name: "john", + })) + // Object pointer + g.Dump(gconv.Map(&User{ + Uid: 1, + Name: "john", + })) + + // Any map type + g.Dump(gconv.Map(map[int]int{ + 100: 10000, + })) +} +``` + +After execution, the terminal outputs: + +``` +{ + "name": "john", + "uid": 1, +} + +{ + "name": "john", + "uid": 1, +} + +{ + "100": 10000, +} +``` + +## Property Tags + +We can customize the `map` key name after conversion via the `c/gconv/json` tag. When multiple tags exist, the priority is determined by the order of `gconv/c/json` tags. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string `c:"-"` + NickName string `c:"nickname, omitempty"` + Pass1 string `c:"password1"` + Pass2 string `c:"password2"` + } + user := User{ + Uid: 100, + Name: "john", + Pass1: "123", + Pass2: "456", + } + g.Dump(gconv.Map(user)) +} +``` + +After execution, the terminal outputs: + +``` +{ + "Uid": 100, + "password1": "123", + "password2": "456", + "nickname": "", +} +``` + +## Custom Tags + +Besides, we can also customize the tag names of `struct` properties and specify the tag priority during `map` conversion via the second parameter. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Id int `c:"uid"` + Name string `my-tag:"nick-name" c:"name"` + } + user := &User{ + Id: 1, + Name: "john", + } + g.Dump(gconv.Map(user, "my-tag")) +} +``` + +After execution, the output result is: + +``` +{ + "nick-name": "john", + "uid": 1, +} +``` + +## Recursive Conversion + +When the parameter is of `map`/ `struct`/ `*struct` type, if the key value/property is an object (or an object pointer) and is not an `embedded` struct without any alias tags bound, the `Map` method will convert the object as a key-value in the result. We can use the `MapDeep` method to recursively convert sub-objects of the parameters, i.e., converting properties to `map` type as well. Let's look at an example. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "reflect" +) + +func main() { + type Base struct { + Id int `c:"id"` + Date string `c:"date"` + } + type User struct { + UserBase Base `c:"base"` + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` + } + user := &User{ + UserBase: Base{ + Id: 1, + Date: "2019-10-01", + }, + Passport: "john", + Password: "123456", + Nickname: "JohnGuo", + } + m1 := gconv.Map(user) + m2 := gconv.MapDeep(user) + g.Dump(m1, m2) + fmt.Println(reflect.TypeOf(m1["base"])) + fmt.Println(reflect.TypeOf(m2["base"])) +} +``` + +After execution, the terminal output is: + +``` +{ + "base": { + Id: 1, + Date: "2019-10-01", + }, + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", +} +{ + "base": { + "id": 1, + "date": "2019-10-01", + }, + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", +} +main.Base +map[string]interface {} +``` + +Can you see the difference? \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..8be9fda3672 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" @@ -0,0 +1,188 @@ +--- +slug: '/docs/core/gconv-scan' +title: 'Type Conversion - Scan' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame framework, type conversion, Scan method, struct conversion, map conversion, Go language, auto recognition, parameter conversion, programming example] +description: "Learn how to use the powerful Scan method in the GoFrame framework to automatically convert arbitrary parameters into structs, struct arrays, maps, and map arrays. This article introduces the definition of the Scan method and its application in the Go language, providing multiple programming examples to help quickly understand and master this function, offering developers an efficient and convenient type conversion solution." +--- + +If the previous complex type conversion functionality is not sufficient, you can explore the `Scan` conversion method. This method can convert arbitrary parameters to `struct/struct arrays/map/map arrays` and automatically recognize and execute conversion based on the developer's input target parameters. + +The method is defined as follows: + +```go +// Scan automatically calls MapToMap, MapToMaps, Struct or Structs function according to +// the type of parameter `pointer` to implement the converting. +// It calls function MapToMap if `pointer` is type of *map to do the converting. +// It calls function MapToMaps if `pointer` is type of *[]map/*[]*map to do the converting. +// It calls function Struct if `pointer` is type of *struct/**struct to do the converting. +// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting. +func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +Let's look at a few examples for a quick understanding. + +## Auto-recognition Conversion `Struct` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Map{ + "uid": 1, + "name": "john", + } + var user *User + if err := gconv.Scan(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +After execution, the output result is: + +``` +{ + Uid: 1, + Name: "john", +} +``` + +## Auto-recognition Conversion `Struct` Array + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + var users []*User + if err := gconv.Scan(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +After execution, the terminal output is: + +``` +[ + { + Uid: 1, + Name: "john", + }, + { + Uid: 2, + Name: "smith", + }, +] +``` + +## Auto-recognition Conversion to Map + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + var ( + user map[string]string + params = g.Map{ + "uid": 1, + "name": "john", + } + ) + if err := gconv.Scan(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +After execution, the output result is: + +``` +{ + "uid": "1", + "name": "john", +} +``` + +## Auto-recognition Conversion `Map` Array + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + var ( + users []map[string]string + params = g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + ) + if err := gconv.Scan(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +After execution, the output result is: + +``` +[ + { + "uid": "1", + "name": "john", + }, + { + "uid": "2", + "name": "smith", + }, +] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..38c6e0ba1c5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/core/gconv-structs' +title: 'Type Conversion - Structs' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame framework,Type conversion,Struct,Structs,struct array,conversion method,gconv,usage example,Go language] +description: "Type conversion methods in the GoFrame framework, focusing on using the Structs method for the conversion of struct arrays. The Structs method is similar to the Struct method and extends its support to struct arrays. The article also provides specific code examples demonstrating how to apply this feature in practice." +--- + +## Introduction + +As previously mentioned, the `Struct` method can be used for the conversion of `struct` objects; similarly, the conversion of `struct` arrays is achieved using the `Structs` method. The `Structs` method is built upon the `Struct` method, and all conversion rules are the same as `Struct`, with additional support for `struct` array types. Before understanding the `Structs` method, it is recommended to first understand the introduction to the `Struct` method: [Type Conversion - Struct](类型转换-Struct转换.md) + +## Method Definition + +The `Structs` method is defined as follows: + +```go +// Structs converts any slice to given struct slice. +func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +Here, the type of `pointer` for the target conversion parameter needs to be `*[]struct/*[]*struct`. + +## Usage Example + +Let's look at a simple example to understand it. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + var users []*User + if err := gconv.Structs(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +After execution, the terminal output is: + +``` +[ + { + Uid: 1, + Name: "john", + }, + { + Uid: 2, + Name: "smith", + }, +] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..5ee66fadc98 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" @@ -0,0 +1,314 @@ +--- +slug: '/docs/core/gconv-struct' +title: 'Type Conversion - Struct' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,gconv,struct conversion,data mapping,struct conversion,automatic object creation,recursive conversion,mapping rules,struct,Go framework] +description: "Using the gconv module of GoFrame for struct conversion, including mapping from various data types to structs, automatic object creation, recursive conversion, and mapping rules, among other practical features, to help developers enhance coding efficiency and project maintenance capabilities." +--- + +In projects, we frequently encounter the use of a large number of `struct`, and conversions/assignments from various data types to `struct` (especially `json`/`xml`/various protocol encoding conversions). To improve coding and project maintenance efficiency, the `gconv` module provides developers with substantial benefits by offering greater flexibility in data parsing. + +The `gconv` module performs `struct` type conversion through the `Struct` method, defined as follows: + +```go +// Struct maps the params key-value pairs to the corresponding struct object's attributes. +// The third parameter `mapping` is unnecessary, indicating the mapping rules between the +// custom key name and the attribute name(case sensitive). +// +// Note: +// 1. The `params` can be any type of map/struct, usually a map. +// 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object +// or struct pointer. +// 3. Only the public attributes of struct object can be mapped. +// 4. If `params` is a map, the key of the map `params` can be lowercase. +// It will automatically convert the first letter of the key to uppercase +// in mapping procedure to do the matching. +// It ignores the map key, if it does not match. +func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +Where: + +1. `params` is the variable parameter to be converted to a `struct`. It can be of any data type, commonly a `map`. +2. `pointer` is the target `struct` object for conversion. This parameter must be a pointer to the `struct` object; the object's attributes will be updated upon successful conversion. +3. `mapping` is a custom mapping between `map key name` and `struct attribute`. In this case, the `params` parameter must be of `map` type, otherwise the parameter is meaningless. In most cases, this parameter can be omitted, using the default conversion rules instead. + +:::tip +For more `struct` related conversion methods, please refer to the interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: + +## Conversion Rules + +The `gconv` module's `struct` conversion feature is powerful, supporting mapping conversion from any data type to `struct` attributes. Without custom `mapping` conversion rules, the default conversion rules are as follows: + +1. Attributes in the `struct` that need to be matched must be **public attributes** (capitalized first letter). +2. Depending on the type of `params`, the logic varies: + - If `params` is of type `map`: The key name will be automatically matched to the struct attribute in a **case-insensitive** and **special character ignored** manner. + - If `params` is of another type: The value will be matched against the first attribute of the `struct`. + - Additionally, if the attribute of the `struct` is a complex data type such as `slice`, `map`, `struct`, recursive matching and assignment will be performed. +3. If matching is successful, the key value is assigned to the attribute, otherwise the key value is ignored. + +## Matching Rules Priority (only for map to struct conversion) + +1. If the `mapping` parameter is not empty, it maps according to the `key` to `struct field name` relationship. +2. If a field `tag` is set, it will use the `tag` to match the `key` of the `params` parameter. + If no `tag` is set, gconv will look for the field `tag` in the order of `gconv, param, c, p, json`. +3. Match according to `field name`. +4. If none of the above matches, gconv will iterate through all `key`s in `params`, matching according to the following rules: + - `Field name`: ignore case and underscores + - `Key`: ignore case, underscores, and special characters + +Tip + +:::warning +Unless there are special circumstances, please try to satisfy the first three rules as the fourth rule is less performant. +::: + +Here are some examples of `map` key names and `struct` attribute names: + +``` +map key struct attribute match? +name Name match +Email Email match +nickname NickName match +NICKNAME NickName match +Nick-Name NickName match +nick_name NickName match +nick name NickName match +NickName Nick_Name match +Nick-name Nick_Name match +nick_name Nick_Name match +nick name Nick_Name match +``` + +## Automatic Object Creation + +When the given `pointer` parameter type is `**struct`, the `Struct` method will automatically create the `struct` object and modify the pointer address to which the variable points. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Map{ + "uid": 1, + "name": "john", + } + var user *User + if err := gconv.Struct(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +After execution, the output is: + +``` +{ + Uid: 1, + Name: "john", +} +``` + +## `Struct` Recursive Conversion + +Recursive conversion refers to the capability to map `params` parameter data (the first parameter) recursively onto sub-objects when a `struct` object contains sub-objects that are defined in an `embedded` manner. It is often used in `struct` objects with inherited objects. + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type Ids struct { + Id int `json:"id"` + Uid int `json:"uid"` + } + type Base struct { + Ids + CreateTime string `json:"create_time"` + } + type User struct { + Base + Passport string `json:"passport"` + Password string `json:"password"` + Nickname string `json:"nickname"` + } + data := g.Map{ + "id" : 1, + "uid" : 100, + "passport" : "john", + "password" : "123456", + "nickname" : "John", + "create_time" : "2019", + } + user := new(User) + gconv.Struct(data, user) + g.Dump(user) +} +``` + +After execution, the output in the terminal is: + +``` +{ + Id: 1, + Uid: 100, + CreateTime: "2019", + Passport: "john", + Password: "123456", + Nickname: "John", +} +``` + +## Example 1: Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +type User struct { + Uid int + Name string + SiteUrl string + NickName string + Pass1 string `c:"password1"` + Pass2 string `c:"password2"` +} + +func main() { + var user *User + + // Use default mapping rules to bind attribute values to objects + user = new(User) + params1 := g.Map{ + "uid": 1, + "Name": "john", + "site_url": "https://goframe.org", + "nick_name": "johng", + "PASS1": "123", + "PASS2": "456", + } + if err := gconv.Struct(params1, user); err == nil { + g.Dump(user) + } + + // Use struct tag mapping to bind attribute values to objects + user = new(User) + params2 := g.Map{ + "uid": 2, + "name": "smith", + "site-url": "https://goframe.org", + "nick name": "johng", + "password1": "111", + "password2": "222", + } + if err := gconv.Struct(params2, user); err == nil { + g.Dump(user) + } +} +``` + +As seen, you can directly bind a `map` to a `struct` using the `Struct` method with default rules or flexibly configure using the `struct tag`. Additionally, the `Struct` method has the third `map` parameter to specify custom parameter name to attribute name mappings. + +After execution, the output is: + +``` +{ + Uid: 1, + Name: "john", + SiteUrl: "https://goframe.org", + NickName: "johng", + Pass1: "123", + Pass2: "456", +} +{ + Uid: 2, + Name: "smith", + SiteUrl: "https://goframe.org", + NickName: "johng", + Pass1: "111", + Pass2: "222", +} +``` + +## Example 2: Complex Attribute Types + +Attributes support conversion of `struct` objects or `struct` object pointers (if the target is a pointer and `nil`, it will be initialized during conversion). + +```go +package main + +import ( + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/frame/g" + "fmt" +) + +func main() { + type Score struct { + Name string + Result int + } + type User1 struct { + Scores Score + } + type User2 struct { + Scores *Score + } + + user1 := new(User1) + user2 := new(User2) + scores := g.Map{ + "Scores": g.Map{ + "Name": "john", + "Result": 100, + }, + } + + if err := gconv.Struct(scores, user1); err != nil { + fmt.Println(err) + } else { + g.Dump(user1) + } + if err := gconv.Struct(scores, user2); err != nil { + fmt.Println(err) + } else { + g.Dump(user2) + } +} +``` + +After execution, the output is: + +``` +{ + Scores: { + Name: "john", + Result: 100, + }, +} +{ + Scores: { + Name: "john", + Result: 100, + }, +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" new file mode 100644 index 00000000000..b2b2300f4ca --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" @@ -0,0 +1,281 @@ +--- +slug: '/docs/core/gconv-unmarshal-value' +title: 'Type Conversion - Interface' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gconv, UnmarshalValue, type conversion, custom conversion, struct conversion, interface definition, performance optimization, reflection] +description: "Use the gconv module in the GoFrame framework for type conversions, especially custom conversions through the UnmarshalValue interface. A detailed discussion on the application of reflection features in complex type conversions and their performance impact, along with multiple code examples, including custom struct conversion and TCP data unpacking, to help developers optimize conversion efficiency." +--- + +Of course, you might have already guessed that the `gconv` module actually uses reflection for conversions involving complex types such as `struct`. While it provides a great convenience for developers, it does come at the cost of performance. For `struct` conversions, if developers have clearly defined conversion rules and are concerned about performance costs, they can implement the `UnmarshalValue` interface for specific `structs` to achieve **custom conversion**. When using the `gconv` module for conversion, whether the `struct` is directly the conversion object or a property of the conversion object, `gconv` will automatically recognize the implemented `UnmarshalValue` interface and use it for type conversion instead of using reflection. +:::tip +Common deserialization interfaces in the standard library, such as `UnmarshalText(text []byte) error`, are also supported, and they are used in a similar manner to `UnmarshalValue`, with different parameters. +::: +## Interface Definition + +```go +// apiUnmarshalValue is the interface for custom defined types customizing value assignment. +// Note that only pointer can implement interface apiUnmarshalValue. +type apiUnmarshalValue interface { + UnmarshalValue(interface{}) error +} +``` + +As you can see, custom types can implement the `UnmarshalValue` method for custom type conversion. The input parameter here is of `interface{}` type, and developers can use type assertions or other methods for type conversion in practical use cases. +:::warning +It's important to note that because the `UnmarshalValue` type conversion modifies the properties of the current object, the receiver of the interface implementation must be a pointer type. + +Correct interface implementation example (using pointer receiver): + +```go +func (c *Receiver) UnmarshalValue(interface{}) error +``` + +**Incorrect** interface implementation example (using value receiver): + +```go +func (c Receiver) UnmarshalValue(interface{}) error +``` +::: +## Usage Examples + +### 1. Custom Data Table Query Result `struct` Conversion + +Data table structure: + +```sql +CREATE TABLE `user` ( + id bigint unsigned NOT NULL AUTO_INCREMENT, + passport varchar(45), + password char(32) NOT NULL, + nickname varchar(45) NOT NULL, + create_time timestamp NOT NULL, + PRIMARY KEY (id) +) ; +``` + +Example code: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "reflect" +) + +type User struct { + Id int + Passport string + Password string + Nickname string + CreateTime *gtime.Time +} + +// Implement UnmarshalValue interface for custom struct conversion +func (user *User) UnmarshalValue(value interface{}) error { + if record, ok := value.(gdb.Record); ok { + *user = User{ + Id: record["id"].Int(), + Passport: record["passport"].String(), + Password: "", + Nickname: record["nickname"].String(), + CreateTime: record["create_time"].GTime(), + } + return nil + } + return gerror.Newf(`unsupported value type for UnmarshalValue: %v`, reflect.TypeOf(value)) +} + +func main() { + var ( + err error + users []*User + ) + array := garray.New(true) + for i := 1; i <= 10; i++ { + array.Append(g.Map{ + "id": i, + "passport": fmt.Sprintf(`user_%d`, i), + "password": fmt.Sprintf(`pass_%d`, i), + "nickname": fmt.Sprintf(`name_%d`, i), + "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), + }) + } + // Insert data + _, err = g.Model("user").Data(array).Insert() + if err != nil { + panic(err) + } + // Query data + err = g.Model("user").Order("id asc").Scan(&users) + if err != nil { + panic(err) + } + g.Dump(users) +} +``` + +After execution, the terminal output: + +``` +[ + { + Id: 1, + Passport: "user_1", + Password: "", + Nickname: "name_1", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 2, + Passport: "user_2", + Password: "", + Nickname: "name_2", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 3, + Passport: "user_3", + Password: "", + Nickname: "name_3", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 4, + Passport: "user_4", + Password: "", + Nickname: "name_4", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 5, + Passport: "user_5", + Password: "", + Nickname: "name_5", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 6, + Passport: "user_6", + Password: "", + Nickname: "name_6", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 7, + Passport: "user_7", + Password: "", + Nickname: "name_7", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 8, + Passport: "user_8", + Password: "", + Nickname: "name_8", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 9, + Passport: "user_9", + Password: "", + Nickname: "name_9", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 10, + Passport: "user_10", + Password: "", + Nickname: "name_10", + CreateTime: "2018-10-24 10:00:00", + }, +] +``` +:::tip +As you can see, the custom `UnmarshalValue` type conversion method does not use reflection, which greatly improves conversion performance. You can try increasing the data volume (e.g., `1 million`) and compare the time cost of type conversion without `UnmarshalValue`. +::: +### 2. Custom Binary TCP Data Unpacking + +An example of unpacking a TCP communication data packet. + +```go +package main + +import ( + "errors" + "fmt" + "github.com/gogf/gf/v2/crypto/gcrc32" + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/util/gconv" +) + +type Pkg struct { + Length uint16 // Total length. + Crc32 uint32 // CRC32. + Data []byte +} + +// NewPkg creates and returns a package with given data. +func NewPkg(data []byte) *Pkg { + return &Pkg{ + Length: uint16(len(data) + 6), + Crc32: gcrc32.Encrypt(data), + Data: data, + } +} + +// Marshal encodes the protocol struct to bytes. +func (p *Pkg) Marshal() []byte { + b := make([]byte, 6+len(p.Data)) + copy(b, gbinary.EncodeUint16(p.Length)) + copy(b[2:], gbinary.EncodeUint32(p.Crc32)) + copy(b[6:], p.Data) + return b +} + +// UnmarshalValue decodes bytes to protocol struct. +func (p *Pkg) UnmarshalValue(v interface{}) error { + b := gconv.Bytes(v) + if len(b) < 6 { + return errors.New("invalid package length") + } + p.Length = gbinary.DecodeToUint16(b[:2]) + if len(b) < int(p.Length) { + return errors.New("invalid data length") + } + p.Crc32 = gbinary.DecodeToUint32(b[2:6]) + p.Data = b[6:] + if gcrc32.Encrypt(p.Data) != p.Crc32 { + return errors.New("crc32 validation failed") + } + return nil +} + +func main() { + var p1, p2 *Pkg + + // Create a demo pkg as p1. + p1 = NewPkg([]byte("123")) + fmt.Println(p1) + + // Convert bytes from p1 to p2 using gconv.Struct. + err := gconv.Struct(p1.Marshal(), &p2) + if err != nil { + panic(err) + } + fmt.Println(p2) +} +``` + +After execution, the terminal output: + +``` +&{9 2286445522 [49 50 51]} +&{9 2286445522 [49 50 51]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..33223d884e4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" @@ -0,0 +1,79 @@ +--- +slug: '/docs/core/gconv-basic' +title: 'Type Conversion - Types' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, type conversion, basic types, gconv, Int conversion, Uint conversion, Float conversion, Bool conversion, string conversion] +description: "The method of common basic type conversion using the GoFrame framework. Focuses on the application of the gconv package in converting integers, floating-point numbers, booleans, strings, etc. Provides simple and understandable code examples demonstrating how to use these conversion functions for efficient type conversion in actual development." +--- + +The conversion methods for common basic types are relatively simple. We will demonstrate the usage and effects of conversion methods with an example here. + +## Basic Example +:::tip +For more type conversion methods, please refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + i := 123.456 + fmt.Printf("%10s %v\n", "Int:", gconv.Int(i)) + fmt.Printf("%10s %v\n", "Int8:", gconv.Int8(i)) + fmt.Printf("%10s %v\n", "Int16:", gconv.Int16(i)) + fmt.Printf("%10s %v\n", "Int32:", gconv.Int32(i)) + fmt.Printf("%10s %v\n", "Int64:", gconv.Int64(i)) + fmt.Printf("%10s %v\n", "Uint:", gconv.Uint(i)) + fmt.Printf("%10s %v\n", "Uint8:", gconv.Uint8(i)) + fmt.Printf("%10s %v\n", "Uint16:", gconv.Uint16(i)) + fmt.Printf("%10s %v\n", "Uint32:", gconv.Uint32(i)) + fmt.Printf("%10s %v\n", "Uint64:", gconv.Uint64(i)) + fmt.Printf("%10s %v\n", "Float32:", gconv.Float32(i)) + fmt.Printf("%10s %v\n", "Float64:", gconv.Float64(i)) + fmt.Printf("%10s %v\n", "Bool:", gconv.Bool(i)) + fmt.Printf("%10s %v\n", "String:", gconv.String(i)) + fmt.Printf("%10s %v\n", "Bytes:", gconv.Bytes(i)) + fmt.Printf("%10s %v\n", "Strings:", gconv.Strings(i)) + fmt.Printf("%10s %v\n", "Ints:", gconv.Ints(i)) + fmt.Printf("%10s %v\n", "Floats:", gconv.Floats(i)) + fmt.Printf("%10s %v\n", "Interfaces:", gconv.Interfaces(i)) +} + +``` + +After execution, the output results are: + +``` + Int: 123 + Int8: 123 + Int16: 123 + Int32: 123 + Int64: 123 + Uint: 123 + Uint8: 123 + Uint16: 123 + Uint32: 123 + Uint64: 123 + Float32: 123.456 + Float64: 123.456 + Bool: true + String: 123.456 + Bytes: [119 190 159 26 47 221 94 64] + Strings: [123.456] + Ints: [123] + Floats: [123.456] +Interfaces: [123.456] +``` + +## Precautions + +Number conversion methods such as `gconv.Int/Uint`, etc., will automatically recognize hexadecimal and octal when the given conversion parameter is a string. + +### Hexadecimal Conversion + +`gconv` treats numeric strings starting with `0x` as hexadecimal conversions. For example, `gconv.Int("0xff")` will return `255`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..abd289278ac --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,38 @@ +--- +slug: '/docs/core/gconv-benchmark' +title: 'Type Conversion - Performance' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, Type Conversion, Performance Testing, Benchmark, gconv, GoFrame Framework, Basic Type Conversion, Go Language, Optimization, Performance] +description: "A detailed introduction and analysis of the basic type conversion usage in the GoFrame framework through performance benchmark tests. By performing performance tests on multiple different data type conversions, it demonstrates their efficiency under different conditions, providing developers with optimization references and guidance when using the GoFrame framework." +--- + +Basic Type Conversion Performance Benchmark: + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gconv$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +BenchmarkString-4 20000000 71.8 ns/op 24 B/op 2 allocs/op +BenchmarkInt-4 100000000 22.2 ns/op 8 B/op 1 allocs/op +BenchmarkInt8-4 100000000 24.5 ns/op 8 B/op 1 allocs/op +BenchmarkInt16-4 50000000 23.8 ns/op 8 B/op 1 allocs/op +BenchmarkInt32-4 100000000 24.1 ns/op 8 B/op 1 allocs/op +BenchmarkInt64-4 100000000 21.7 ns/op 8 B/op 1 allocs/op +BenchmarkUint-4 100000000 22.2 ns/op 8 B/op 1 allocs/op +BenchmarkUint8-4 50000000 25.6 ns/op 8 B/op 1 allocs/op +BenchmarkUint16-4 50000000 32.1 ns/op 8 B/op 1 allocs/op +BenchmarkUint32-4 50000000 27.7 ns/op 8 B/op 1 allocs/op +BenchmarkUint64-4 50000000 28.1 ns/op 8 B/op 1 allocs/op +BenchmarkFloat32-4 10000000 155 ns/op 24 B/op 2 allocs/op +BenchmarkFloat64-4 10000000 177 ns/op 24 B/op 2 allocs/op +BenchmarkTime-4 5000000 240 ns/op 72 B/op 4 allocs/op +BenchmarkTimeDuration-4 50000000 26.2 ns/op 8 B/op 1 allocs/op +BenchmarkBytes-4 10000000 149 ns/op 128 B/op 3 allocs/op +BenchmarkStrings-4 10000000 223 ns/op 40 B/op 3 allocs/op +BenchmarkInts-4 20000000 55.0 ns/op 16 B/op 2 allocs/op +BenchmarkFloats-4 10000000 186 ns/op 32 B/op 3 allocs/op +BenchmarkInterfaces-4 20000000 66.6 ns/op 24 B/op 2 allocs/op +PASS +ok command-line-arguments 35.356s +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..a53a442a5a7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" @@ -0,0 +1,35 @@ +--- +slug: '/docs/core/gconv' +title: 'Type Conversion' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Type Conversion, gconv, Efficient Conversion, Struct Conversion, Data Types, Programming Efficiency, API Documentation, Seamless Conversion] +description: "The gconv package in the GoFrame framework provides efficient type conversion functionality, supporting conversion from common data types to specified types, and can convert any type to a struct object. gconv extensively uses assertion, effectively enhancing program performance, making it an ideal choice for developers to achieve type conversion." +--- + +## Introduction + +The `GoFrame` framework provides a very powerful and easy-to-use type conversion package `gconv`, which can convert common data types into specified data types, seamlessly converting between common basic data types, and also supports conversion of any type to a `struct` object. Since the `gconv` module internally prioritizes the use of assertions over reflection, the execution efficiency is very high. + +**Notes:** + +The primary goal of the `gconv` package is to provide simple and efficient type conversion functionality. Developers should be aware of the input parameters for the conversion and the current business scenario being used. If some methods fail to convert, the method will not return the reason for the error, nor will it cause a `panic`, but will directly return the value after the conversion fails. Therefore, developers often need to comprehensively judge the correctness of the result based on the return value and the current business scenario. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/util/gconv" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) + +**Method List:** + +The method list may lag behind the code, please refer to the API documentation for details. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..8e7026c0146 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" @@ -0,0 +1,61 @@ +--- +slug: '/docs/core/gcache-redis' +title: 'Caching - Redis' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Cache Management, Redis Cache, gcache, Redis Adapter, Session Sharing, Database Query Cache, Redis DB, Cache Object] +description: "The cache management module in the GoFrame framework focuses on the implementation and usage of the Redis cache adapter. It provides examples on ensuring data consistency in multi-node environments. Detailed steps on setting up Redis clients and using Redis cache adapters are given, and the operation notes for Clear and Size methods in multi-object connections are discussed. It is also recommended to configure independent Redis DBs for different business scenarios." +--- + +## Introduction + +The cache component provides a `Redis` cache adapter implementation for `gcache`. `Redis` cache is very useful in ensuring data consistency in multi-node environments, especially in scenarios such as `Session` sharing and database query caching. + +## Usage Example + +```go +func ExampleCache_SetAdapter() { + var ( + err error + ctx = gctx.New() + cache = gcache.New() + redisConfig = &gredis.Config{ + Address: "127.0.0.1:6379", + Db: 9, + } + cacheKey = `key` + cacheValue = `value` + ) + // Create redis client object. + redis, err := gredis.New(redisConfig) + if err != nil { + panic(err) + } + // Create redis cache adapter and set it to cache object. + cache.SetAdapter(gcache.NewAdapterRedis(redis)) + + // Set and Get using cache object. + err = cache.Set(ctx, cacheKey, cacheValue, time.Second) + if err != nil { + panic(err) + } + fmt.Println(cache.MustGet(ctx, cacheKey).String()) + + // Get using redis client. + fmt.Println(redis.MustDo(ctx, "GET", cacheKey).String()) + + // Output: + // value + // value +} +``` + +## Notes + +### On `Clear/Size` Methods + +First, the same `gredis.Config` will always connect to the same `redis db`, and since `Redis` itself doesn't have data grouping features, when multiple `gcache.Cache` objects connect to the same `redis db`, they will share the entire `redis db` rather than having a separate group to store the current `gcache.Cache` object's content. Therefore, operations like `Clear` and `Size` will act on the entire `redis db` instead of just the content in the current `gcache.Cache` object as with in-memory caches, which can be counterintuitive, so please use with caution. + +### It is recommended to use different `redis db` to differentiate business cache types + +Additionally, it is recommended to configure different `db` for usage when using `Redis` cache, rather than sharing a `db` with other business data. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..afa39850841 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" @@ -0,0 +1,304 @@ +--- +slug: '/docs/core/gcache-memory' +title: 'Caching - In-Memory' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Cache Management, In-Memory Cache, Efficient Caching, gcache, Go Language, Performance Optimization, LRU Eviction, Concurrency Control] +description: "Using in-memory caching with the GoFrame framework for efficient cache management, including basic usage, expiration control, the use of GetOrSetFunc functions, and LRU cache eviction control. Through example code, it demonstrates how to set caches, retrieve cache values, and perform concurrency control, aiming to help users optimize program performance." +--- + +The caching component by default provides a high-speed in-memory cache, with operation efficiency at the `ns` nanosecond level of CPU performance loss. + +## Usage Example + +### Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + // Create a cache object, + // but you can also conveniently use the gcache package methods directly + var ( + ctx = gctx.New() + cache = gcache.New() + ) + + // Set cache, no expiration + err := cache.Set(ctx, "k1", "v1", 0) + if err != nil { + panic(err) + } + + // Get cache value + value, err := cache.Get(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(value) + + // Get cache size + size, err := cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + // Check if a specific key name exists in the cache + b, err := cache.Contains(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(b) + + // Remove and return the deleted key-value + removedValue, err := cache.Remove(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(removedValue) + + // Close the cache object to allow GC to reclaim resources + if err = cache.Close(ctx); err != nil { + panic(err) + } +} +``` + +After execution, the output is: + +``` +v1 +1 +true +v1 +``` + +### Expiration Control + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + // Write when the key name does not exist, set expiration time to 1000 milliseconds + _, err := gcache.SetIfNotExist(ctx, "k1", "v1", time.Second) + if err != nil { + panic(err) + } + + // Print current list of key names + keys, err := gcache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) + + // Print current list of key-values + values, err := gcache.Values(ctx) + if err != nil { + panic(err) + } + fmt.Println(values) + + // Get a specific key value, write if it does not exist, and return the key value + value, err := gcache.GetOrSet(ctx, "k2", "v2", 0) + if err != nil { + panic(err) + } + fmt.Println(value) + + // Print current key-value pairs + data1, err := gcache.Data(ctx) + if err != nil { + panic(err) + } + fmt.Println(data1) + + // Wait for 1 second, so k1:v1 expires automatically + time.Sleep(time.Second) + + // Print current key-value pairs again, and find that k1:v1 has expired, leaving only k2:v2 + data2, err := gcache.Data(ctx) + if err != nil { + panic(err) + } + fmt.Println(data2) +} +``` + +After execution, the output is: + +``` +[k1] +[v1] +v2 +map[k1:v1 k2:v2] +map[k2:v2] +``` + +### `GetOrSetFunc*` + +`GetOrSetFunc` retrieves a cache value, and if the cache does not exist, executes the specified `f func(context.Context) (interface{}, error)`, caches the result of the `f` method, and returns that result. + +It is important to note that `GetOrSetFunc`'s cache method parameter `f` is executed **outside of the cache's lock mechanism**, so `GetOrSetFunc` can be nested within `f`. However, if `f` is computationally intensive, it may be executed multiple times under high concurrency (only the result from the first executed `f` can be successfully cached, the rest will be discarded). On the other hand, `GetOrSetFuncLock`'s cache method `f` is executed **within the cache's lock mechanism**, ensuring that `f` is executed only once when the cache item does not exist, but the cache write lock duration depends on the execution time of the `f` method. + +Here's an example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ch = make(chan struct{}, 0) + ctx = gctx.New() + key = `key` + value = `value` + ) + for i := 0; i < 10; i++ { + go func(index int) { + <-ch + _, err := gcache.GetOrSetFuncLock(ctx, key, func(ctx context.Context) (interface{}, error) { + fmt.Println(index, "entered") + return value, nil + }, 0) + if err != nil { + panic(err) + } + }(i) + } + close(ch) + time.Sleep(time.Second) +} +``` + +After execution, the terminal outputs (random, but only one message is output): + +``` +9 entered +``` + +You can see that when multiple `goroutine`s concurrently call the `GetOrSetFuncLock` method, due to its concurrent safety control, only one `goroutine`'s value-generation function executes successfully, and once successful, other `goroutine`s immediately return with the data without executing their corresponding value-generation functions. + +### `LRU` Cache Eviction Control + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + cache = gcache.New(2) // Set LRU eviction count + ) + + // Add 10 elements, no expiration + for i := 0; i < 10; i++ { + if err := cache.Set(ctx, i, i, 0); err != nil { + panic(err) + } + } + size, err := cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + keys, err := cache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) + + // Read key name 1 to ensure that this key name is preferentially retained + value, err := cache.Get(ctx, 1) + if err != nil { + panic(err) + } + fmt.Println(value) + + // After waiting for some time (checked once per second by default), + // elements will be evicted in order from oldest to newest + time.Sleep(3 * time.Second) + size, err = cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + keys, err = cache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) +} +``` + +After execution, the output is: + +```10 +[2 3 5 6 7 0 1 4 8 9] +1 +2 +[1 9] +``` + +## Performance Testing + +### Test Environment + +``` +CPU: Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz +MEM: 8GB +SYS: Ubuntu 16.04 amd64 +``` + +### Test Results + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcache$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +Benchmark_CacheSet-4 2000000 897 ns/op 249 B/op 4 allocs/op +Benchmark_CacheGet-4 5000000 202 ns/op 49 B/op 1 allocs/op +Benchmark_CacheRemove-4 50000000 35.7 ns/op 0 B/op 0 allocs/op +Benchmark_CacheLruSet-4 2000000 880 ns/op 399 B/op 4 allocs/op +Benchmark_CacheLruGet-4 3000000 212 ns/op 33 B/op 1 allocs/op +Benchmark_CacheLruRemove-4 50000000 35.9 ns/op 0 B/op 0 allocs/op +Benchmark_InterfaceMapWithLockSet-4 3000000 477 ns/op 73 B/op 2 allocs/op +Benchmark_InterfaceMapWithLockGet-4 10000000 149 ns/op 0 B/op 0 allocs/op +Benchmark_InterfaceMapWithLockRemove-4 50000000 39.8 ns/op 0 B/op 0 allocs/op +Benchmark_IntMapWithLockWithLockSet-4 5000000 304 ns/op 53 B/op 0 allocs/op +Benchmark_IntMapWithLockGet-4 20000000 164 ns/op 0 B/op 0 allocs/op +Benchmark_IntMapWithLockRemove-4 50000000 33.1 ns/op 0 B/op 0 allocs/op +PASS +ok command-line-arguments 47.503s +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..bea310ddf97 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" @@ -0,0 +1,40 @@ +--- +slug: '/docs/core/gcache-interface' +title: 'Caching - Interface' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, cache management, interface design, Adapter interface, gcache, cache interface, custom implementation, integrate cache, GoFrame cache] +description: "The interface design and implementation of the cache management component in the GoFrame framework provide the Adapter interface, allowing developers to flexibly register and customize cache management objects, achieving seamless integration of different caching strategies. It details how to register and obtain interface implementations through SetAdapter and GetAdapter methods." +--- + +The cache component adopts an interface design, providing the `Adapter` interface. Any object implementing the `Adapter` interface can be registered into the cache management object, allowing developers to flexibly customize and extend the cache management object. + +## Interface Definition + +The `Adapter` interface is defined as follows: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter) + +## Registering Interface Implementation + +This method applies the implemented `adapter` to the corresponding `Cache` object: + +```go +// SetAdapter changes the adapter for this cache. +// Be very note that, this setting function is not concurrent-safe, which means you should not call +// this setting function concurrently in multiple goroutines. +func (c *Cache) SetAdapter(adapter Adapter) +``` + +For specific examples, please refer to the [Caching - Redis](缓存管理-Redis缓存.md) section. + +## Obtaining Interface Implementation + +This method retrieves the currently registered `adapter` interface implementation object: + +```go +// GetAdapter returns the adapter that is set in current Cache. +func (c *Cache) GetAdapter() Adapter +``` + +For specific examples, please refer to the [Caching - Redis](缓存管理-Redis缓存.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..6d6c331ea10 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1037 @@ +--- +slug: '/docs/core/gcache-funcs' +title: 'Caching - Methods' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, cache management, data storage, methods introduction, efficient caching, Go language, interface implementation, cache adapter, data processing, cache updating] +description: "Methods for using cache management in the GoFrame framework, including basic set and get operations, adapter setup methods, and cache update strategies. Users can learn how to efficiently manage and operate cache data in the GoFrame framework through example code." +--- +:::tip +The following is a list of commonly used methods. The documentation may lag behind new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache) +::: +## `Set` + +- Description: Use `key-value` pairs to set cache, and key-values can be of any type. +- Signature: + +```go +Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error +``` + +- Example: Set the `slice` to cache with the key name `k1`. + +```go +func ExampleCache_Set() { + c := gcache.New() + c.Set(ctx, "k1", g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}, 0) + fmt.Println(c.Get(ctx, "k1")) + + // Output: + // [1,2,3,4,5,6,7,8,9] +} +``` + +## `SetAdapter` + +- Description: `SetAdapter` changes the underlying adapter of this cache object. Note that this setup function is not concurrency safe. +- Signature: + +```go +SetAdapter(adapter Adapter) +``` + +- Example: You can implement any cache adapter according to your needs by implementing the interface method. + +```go +func ExampleCache_SetAdapter() { + c := gcache.New() + adapter := gcache.New() + c.SetAdapter(adapter) + c.Set(ctx, "k1", g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}, 0) + fmt.Println(c.Get(ctx, "k1")) + + // Output: + // [1,2,3,4,5,6,7,8,9] +} +``` + +## `SetIfNotExist` + +- Description: Sets the corresponding key value `value` and returns `true` when the specified `key` does not exist. Otherwise, it does nothing and returns `false`. +- Signature: + +```go +SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) +``` + +- Example: Directly `judging and writing` through `SetIfNotExist`, and set the expiration time. + +```go +func ExampleCache_SetIfNotExist() { + c := gcache.New() + // Write when the key name does not exist, and set the expiration time to 1000 milliseconds + k1, err := c.SetIfNotExist(ctx, "k1", "v1", 1000*time.Millisecond) + fmt.Println(k1, err) + + // Returns false when the key name already exists + k2, err := c.SetIfNotExist(ctx, "k1", "v2", 1000*time.Millisecond) + fmt.Println(k2, err) + + // Print the current list of key values + keys1, _ := c.Keys(ctx) + fmt.Println(keys1) + + // It does not expire if `duration` == 0. It deletes the `key` if `duration` < 0 or given `value` is nil. + c.SetIfNotExist(ctx, "k1", 0, -10000) + + // Wait 1 second for K1: V1 to expire automatically + time.Sleep(1200 * time.Millisecond) + + // Print the current key-value pair again and find that K1: V1 has expired + keys2, _ := c.Keys(ctx) + fmt.Println(keys2) + + // Output: + // true + // false + // [k1] + // [] +} +``` + +## `SetMap` + +- Description: Set key-value pairs in batches, the input parameter type is `map[interface{}]interface{}`. +- Signature: + +```go +SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error +``` + +- Example: + +```go +func ExampleCache_SetMap() { + c := gcache.New() + // map[interface{}]interface{} + data := g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + } + c.SetMap(ctx, data, 1000*time.Millisecond) + + // Gets the specified key value + v1, _ := c.Get(ctx, "k1") + v2, _ := c.Get(ctx, "k2") + v3, _ := c.Get(ctx, "k3") + + fmt.Println(v1, v2, v3) + + // Output: + // v1 v2 v3 +} +``` + +## `Size` + +- Description: `Size` returns the `number of items` in the cache. +- Signature: + +```go +Size(ctx context.Context) (size int, err error) +``` + +- Example: + +```go +func ExampleCache_Size() { + c := gcache.New() + + // Add 10 elements without expiration + for i := 0; i < 10; i++ { + c.Set(ctx, i, i, 0) + } + + // Size returns the number of items in the cache. + n, _ := c.Size(ctx) + fmt.Println(n) + + // Output: + // 10 +} +``` + +## `Update` + +- Description: `Update` updates the value corresponding to the `key` without changing its `expiration time`, and returns the old value. If `key` does not exist in the cache, the returned `exist` value is `false`. +- Signature: + +```go +Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) +``` + +- Example: Add multiple caches through `SetMap` and modify `value` through `Update` with the specified `key`. + +```go +func ExampleCache_Update() { + c := gcache.New() + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3"}, 0) + + k1, _ := c.Get(ctx, "k1") + fmt.Println(k1) + k2, _ := c.Get(ctx, "k2") + fmt.Println(k2) + k3, _ := c.Get(ctx, "k3") + fmt.Println(k3) + + re, exist, _ := c.Update(ctx, "k1", "v11") + fmt.Println(re, exist) + + re1, exist1, _ := c.Update(ctx, "k4", "v44") + fmt.Println(re1, exist1) + + kup1, _ := c.Get(ctx, "k1") + fmt.Println(kup1) + kup2, _ := c.Get(ctx, "k2") + fmt.Println(kup2) + kup3, _ := c.Get(ctx, "k3") + fmt.Println(kup3) + + // Output: + // v1 + // v2 + // v3 + // v1 true + // false + // v11 + // v2 + // v3 +} +``` + +## `UpdateExpire` + +- Description: `UpdateExpire` updates the expiration time of the `key` and returns the old expiration time value. If `key` does not exist in the cache, it returns `-1`. +- Signature: + +```go +UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) +``` + +- Example: Update the expiration time of `key` through `UpdateExpire` and print to check. + +```go +func ExampleCache_UpdateExpire() { + c := gcache.New() + c.Set(ctx, "k1", "v1", 1000*time.Millisecond) + expire, _ := c.GetExpire(ctx, "k1") + fmt.Println(expire) + + c.UpdateExpire(ctx, "k1", 500*time.Millisecond) + + expire1, _ := c.GetExpire(ctx, "k1") + fmt.Println(expire1) + + // Output: + // 1s + // 500ms +} +``` + +## `Values` + +- Description: Get all values in the cache via `Values`, returned in slice form. +- Signature: + +```go +Values(ctx context.Context) (values []interface{}, err error) +``` + +- Example: + +```go +func ExampleCache_Values() { + c := gcache.New() + + c.Set(ctx, "k1", g.Map{"k1": "v1", "k2": "v2"}, 0) + + // Values returns all values in the cache as slice. + data, _ := c.Values(ctx) + fmt.Println(data) + + // May Output: + // [map[k1:v1 k2:v2]] +} +``` + +## `Close` + +- Description: Close the cache and let `GC` reclaim resources, by default `no need to close`. +- Signature: + +```go +Close(ctx context.Context) error +``` + +- Example: Close the cache by `Close`. + +```go +func ExampleCache_Close() { + c := gcache.New() + + c.Set(ctx, "k1", "v", 0) + data, _ := c.Get(ctx, "k1") + fmt.Println(data) + + // Close closes the cache if necessary. + c.Close(ctx) + + data1, _ := c.Get(ctx, "k1") + + fmt.Println(data1) + + // Output: + // v + // v +} +``` + +## `Contains` + +- Description: `Contains` returns `true` if the specified `key` exists in the cache, otherwise it returns `false`. +- Signature: + +```go +Contains(ctx context.Context, key interface{}) (bool, error) +``` + +- Example: + +```go +func ExampleCache_Contains() { + c := gcache.New() + + // Set Cache + c.Set(ctx, "k", "v", 0) + + data, _ := c.Contains(ctx, "k") + fmt.Println(data) + + // return false + data1, _ := c.Contains(ctx, "k1") + fmt.Println(data1) + + // Output: + // true + // false +} +``` + +## `Data` + +- Description: Data is returned as a `map` type with a copy of all `key-value pairs` ('key':'value') in the cache. +- Signature: + +```go +Data(ctx context.Context) (data map[interface{}]interface{}, err error) +``` + +- Example: Get all cached data and return it as `map[interface{}]interface{}` + +```go +func ExampleCache_Data() { + c := gcache.New() + + c.Set(ctx, "k5", "v5", 0) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k1:v1] +} +``` + +## `Get` + +- Description: `Get` retrieves and returns the associated value for a given `key`. If it doesn't exist, the value is zero or has expired; it returns `nil`. +- Signature: + +```go +Get(ctx context.Context, key interface{}) (*gvar.Var, error) +``` + +- Example: + +```go +func ExampleCache_Get() { + c := gcache.New() + + // Set Cache Object + c.Set(ctx, "k1", "v1", 0) + + data, _ := c.Get(ctx, "k1") + fmt.Println(data) + // Output: + // v1 +} +``` + +## `GetExpire` + +- Description: `GetExpire` retrieves and returns the expiration time of `key` in the cache. Note that if `key` never expires, it returns `0`. If `key` does not exist in the cache, it returns `-1`. +- Signature: + +```go +GetExpire(ctx context.Context, key interface{}) (time.Duration, error) +``` + +- Example: + +```go +func ExampleCache_GetExpire() { + c := gcache.New() + + // Set cache without expiration + c.Set(ctx, "k", "v", 10000*time.Millisecond) + + expire, _ := c.GetExpire(ctx, "k") + fmt.Println(expire) + + // Output: + // 10s +} +``` + +## `GetOrSet` + +- Description: Retrieve and return the value of `key`, or set the `key-value` pair directly if `key` does not exist in the cache. +- Signature: + +```go +GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) +``` + +- Example: Use `GetOrSet` to determine if `key` does not exist, then set it directly, and set the `duration` time. + +```go +func ExampleCache_GetOrSet() { + c := gcache.New() + + data, _ := c.GetOrSet(ctx, "k", "v", 10000*time.Millisecond) + fmt.Println(data) + + data1, _ := c.Get(ctx, "k") + fmt.Println(data1) + + // Output: + // v + // v +} +``` + +## `GetOrSetFunc` + +- Description: Retrieve and return the value of `key`; if the value corresponding to `key` does not exist, set `key` with the result of the function `func`. If `key` exists in the cache, return its result. +- Signature: + +```go +GetOrSetFunc(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) (result *gvar.Var, err error) +``` + +- Example: The setting of `k1` returns the execution result of `func`, `k2` returns `nil` and does not perform any operation. + +```go +func ExampleCache_GetOrSetFunc() { + c := gcache.New() + + c.GetOrSetFunc(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "v1", nil + }, 10000*time.Millisecond) + v, _ := c.Get(ctx, "k1") + fmt.Println(v) + + c.GetOrSetFunc(ctx, "k2", func(ctx context.Context) (value interface{}, err error) { + return nil, nil + }, 10000*time.Millisecond) + v1, _ := c.Get(ctx, "k2") + fmt.Println(v1) + + // Output: + // v1 +} +``` + +## `GetOrSetFuncLock` + +- Description: It is consistent with `GetOrSetFunc`, but cannot repeat or `overwrite registration` of the cache. +- Signature: + +```go +GetOrSetFuncLock(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) (result *gvar.Var, err error) +``` + +- Example: The first time setting returns the execution result of `func`, the operation for the second time setting is invalid. + +```go +func ExampleCache_GetOrSetFuncLock() { + c := gcache.New() + + c.GetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "v1", nil + }, 0) + v, _ := c.Get(ctx, "k1") + fmt.Println(v) + + c.GetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "update v1", nil + }, 0) + v, _ = c.Get(ctx, "k1") + fmt.Println(v) + + c.Remove(ctx, g.Slice{"k1"}...) + + // Output: + // v1 + // v1 +} +``` + +## `Keys` + +- Description: All `keys` in the cache are returned in the form of `slice`. +- Signature: + +```go +Keys(ctx context.Context) (keys []interface{}, err error) +``` + +- Example: + +```go +func ExampleCache_Keys() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1"}, 0) + + // Print the current list of key values + keys1, _ := c.Keys(ctx) + fmt.Println(keys1) + + // Output: + // [k1] +} +``` + +## `KeyStrings` + +- Description: `KeyStrings` returns all the keys in the cache as a string `slice`. +- Signature: + +```go +func (c *Cache) KeyStrings(ctx context.Context) ([]string, error) { + keys, err := c.Keys(ctx) + if err != nil { + return nil, err + } + return gconv.Strings(keys), nil +} +``` + +- Example: + +```go +func ExampleCache_KeyStrings() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // KeyStrings returns all keys in the cache as string slice. + keys,_ := c.KeyStrings(ctx) + fmt.Println(keys) + + // May Output: + // [k1 k2] +} +``` + +## `Remove` + +- Description: Remove `one or more keys` from the cache, and return the value of the last deleted key. +- Signature: + +```go +Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) +``` + +- Example: + +```go +func ExampleCache_Remove() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + c.Remove(ctx, "k1") + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k2:v2] +} +``` + +## `Removes` + +- Description: Remove multiple keys from the cache. +- Signature: + +```go +func (c *Cache) Removes(ctx context.Context, keys []interface{}) error { + _, err := c.Remove(ctx, keys...) + return err +} +``` + +- Example: + +```go +func ExampleCache_Removes() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4"}, 0) + + c.Removes(ctx, g.Slice{"k1", "k2", "k3"}) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k4:v4] +} +``` + +## `Clear` + +- Description: Clear `all cache`. +- Signature: + +```go +func (c *Cache) Clear(ctx context.Context) error +``` + +- Example: + +```go +func ExampleCache_Clear() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4"}, 0) + + c.Clear(ctx) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[] +} +``` + +## `MustGet` + +- Description: `MustGet` retrieves and returns the associated value for a given `key`. If it doesn't exist, the value is zero or has expired, it returns `nil`. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustGet(ctx context.Context, key interface{}) *gvar.Var { + v, err := c.Get(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustGet() { + c := gcache.New() + + c.Set(ctx, "k1", "v1", 0) + k2 := c.MustGet(ctx, "k2") + + k1 := c.MustGet(ctx, "k1") + fmt.Println(k1) + + fmt.Println(k2) + + // Output: + // v1 + // +} +``` + +## `MustGetOrSet` + +- Description: Retrieve and return the value of `key`, or set the `key-value` pair directly if `key` does not exist in the cache. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustGetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) *gvar.Var { + v, err := c.GetOrSet(ctx, key, value, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustGetOrSet() { + + // Create a cache object, + // Of course, you can also easily use the gcache package method directly + c := gcache.New() + + // MustGetOrSet acts like GetOrSet, but it panics if any error occurs. + k1 := c.MustGetOrSet(ctx, "k1", "v1", 0) + fmt.Println(k1) + + k2 := c.MustGetOrSet(ctx, "k1", "v2", 0) + fmt.Println(k2) + + // Output: + // v1 + // v1 + +} +``` + +## `MustGetOrSetFunc` + +- Description: Retrieve and return the value of `key`; if the value corresponding to `key` does not exist, set `key` with the result of the function `func`. If `key` exists in the cache, return its result. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustGetOrSetFunc(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) *gvar.Var { + v, err := c.GetOrSetFunc(ctx, key, f, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustGetOrSetFunc() { + c := gcache.New() + + c.MustGetOrSetFunc(ctx, 1, func(ctx context.Context) (interface{}, error) { + return 111, nil + }, 10000*time.Millisecond) + v := c.MustGet(ctx, 1) + fmt.Println(v) + + c.MustGetOrSetFunc(ctx, 2, func(ctx context.Context) (interface{}, error) { + return nil, nil + }, 10000*time.Millisecond) + v1 := c.MustGet(ctx, 2) + fmt.Println(v1) + + // Output: + // 111 + // +} +``` + +## `MustGetOrSetFuncLock` + +- Description: It is consistent with `MustGetOrSetFunc`, but cannot repeat or `overwrite registration` of the cache. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustGetOrSetFuncLock(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) *gvar.Var { + v, err := c.GetOrSetFuncLock(ctx, key, f, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustGetOrSetFuncLock() { + c := gcache.New() + + c.MustGetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (interface{}, error) { + return "v1", nil + }, 0) + v := c.MustGet(ctx, "k1") + fmt.Println(v) + + // Modification failed + c.MustGetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (interface{}, error) { + return "update v1", nil + }, 0) + v = c.MustGet(ctx, "k1") + fmt.Println(v) + + // Output: + // v1 + // v1 +} +``` + +## `MustContains` + +- Description: `Contains` returns `true` if the specified `key` exists in the cache, otherwise it returns `false`. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustContains(ctx context.Context, key interface{}) bool { + v, err := c.Contains(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustContains() { + c := gcache.New() + + // Set Cache + c.Set(ctx, "k", "v", 0) + + // Contains returns true if `key` exists in the cache, or else returns false. + // return true + data := c.MustContains(ctx, "k") + fmt.Println(data) + + // return false + data1 := c.MustContains(ctx, "k1") + fmt.Println(data1) + + // Output: + // true + // false + +} +``` + +## `MustGetExpire` + +- Description:  `MustGetExpire` retrieves and returns the expiration time of `key` in the cache. Note that if `key` never expires, it returns `0`. If `key` does not exist in the cache, it returns `-1`, if `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustGetExpire(ctx context.Context, key interface{}) time.Duration { + v, err := c.GetExpire(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustGetExpire() { + c := gcache.New() + + // Set cache without expiration + c.Set(ctx, "k", "v", 10000*time.Millisecond) + + // MustGetExpire acts like GetExpire, but it panics if any error occurs. + expire := c.MustGetExpire(ctx, "k") + fmt.Println(expire) + + // May Output: + // 10s +} +``` + +## `MustSize` + +- Description:  `MustSize` returns the number of items in the cache. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustSize(ctx context.Context) int { + v, err := c.Size(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustSize() { + c := gcache.New() + + // Add 10 elements without expiration + for i := 0; i < 10; i++ { + c.Set(ctx, i, i, 0) + } + + // Size returns the number of items in the cache. + n := c.MustSize(ctx) + fmt.Println(n) + + // Output: + // 10 +} +``` + +## `MustData` + +- Description: Data is returned as a `map` type with a copy of all `key-value pairs` ('key':'value') in the cache. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustData(ctx context.Context) map[interface{}]interface{} { + v, err := c.Data(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustData() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1"}, 0) + + data := c.MustData(ctx) + fmt.Println(data) + + // Set Cache + c.Set(ctx, "k5", "v5", 0) + data1, _ := c.Get(ctx, "k1") + fmt.Println(data1) + + // Output: + // map[k1:v1] + // v1 +} +``` + +## `MustKeys` + +- Description: `MustKeys` returns all keys in the cache in the form of `(slice)`, if `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustKeys(ctx context.Context) []interface{} { + v, err := c.Keys(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustKeys() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // MustKeys acts like Keys, but it panics if any error occurs. + keys1 := c.MustKeys(ctx) + fmt.Println(keys1) + + // May Output: + // [k1 k2] + +} +``` + +## `MustKeyStrings` + +- Description: `MustKeyStrings` returns all the keys in the cache as a string `(slice)`. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustKeyStrings(ctx context.Context) []string { + v, err := c.KeyStrings(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustKeyStrings() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // MustKeyStrings returns all keys in the cache as string slice. + // MustKeyStrings acts like KeyStrings, but it panics if any error occurs. + keys := c.MustKeyStrings(ctx) + fmt.Println(keys) + + // May Output: + // [k1 k2] +} +``` + +## `MustValues` + +- Description: `MustValues` returns all values in the cache in the form of `(slice)`. If `err` is not nil, it will `panic(err)`. +- Signature: + +```go +func (c *Cache) MustValues(ctx context.Context) []interface{} { + v, err := c.Values(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- Example: + +```go +func ExampleCache_MustValues() { + c := gcache.New() + + // Write value + c.Set(ctx, "k1", "v1", 0) + + // Values returns all values in the cache as slice. + data := c.MustValues(ctx) + fmt.Println(data) + + // Output: + // [v1] +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..2c82ced4aa8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" @@ -0,0 +1,125 @@ +--- +slug: '/docs/core/gcache' +title: 'Caching' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gcache, cache management, in-memory cache, cache adapter, key-value pair, interface design, data type conversion, adapter interface] +description: "The gcache module in the GoFrame framework provides unified cache management functions, including in-memory cache adapter implementation. gcache supports custom key data types and stores any data type, using generic objects for type conversion to avoid risks from direct type assertions. Additionally, gcache offers cache expiration settings, making it flexibly suitable for various caching scenarios." +--- + +## Introduction + +`gcache` is a module providing unified cache management, offering developers a customizable and flexible cache adapter interface, with a default high-speed in-memory cache adapter implementation. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gcache" +``` + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache) + +**Brief Introduction:** + +1. `gcache` provides a default high-speed in-memory cache object, which can be operated by package methods or created using the `New` method. When using cache functions via package methods, operations are on a globally provided `gcache.Cache` object, hence be cautious of global key name collisions in use. + +2. The key type used in `gcache` is `interface{}`, not `string`, meaning any variable type can be used as a key name. However, it is generally recommended to use `string` or `[]byte` as key names and to unify the key name data type for maintenance purposes. + +3. The key-value type stored by `gcache` is `interface{}`, meaning any data type can be stored. When data is retrieved, it is returned as `interface{}`. If conversion to other types is needed, `gcache`'s `Get*` methods can conveniently obtain common types. Note, if you are sure that in-memory cache is being used, you can directly use assertions for type conversion; otherwise, it is recommended to use the returned generic object's corresponding method for type conversion. + +4. Additionally, note that the cache expiration time parameter `duration` in `gcache` is of type `time.Duration`. When setting a cache variable, `duration = 0` means no expiration, `duration < 0` means immediate expiration, and `duration > 0` means timeout expiration. + +## Notes + +### About Key Name Data Types + +You may notice that the data types of key-value pairs in the cache component are `interface{}`. This design aims for generality and ease of use, but requires attention to `interface{}` comparison: true matching requires both **data** and **type** to be equal. Here's an example. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key1 int32 = 1 + key2 float64 = 1 + value = `value` + ) + _ = gcache.Set(ctx, key1, value, 0) + fmt.Println(gcache.MustGet(ctx, key1).Val()) + fmt.Println(gcache.MustGet(ctx, key2).Val()) +} +``` + +After execution, the console outputs: + +```value + +``` + +As you can see, although `key1` and `key2` have the same value, their types are different, so `key2` cannot be used to obtain the key-value pair. + +### About Retrieving Object Key-Values + +Since the key-value type is also `interface{}`, it is often converted to the desired data type after retrieval. A common conversion method is direct type assertion, but this carries a risk. The `gcache` component uses an **adapter interface design pattern**, meaning the implementation (besides the default in-memory adapter) often changes the original data type (non-memory implementations often involve serialization/deserialization storage). Thus, direct type assertion for data type conversion is not recommended. + +To improve key-value retrieval, the cache component does not directly return `interface{}` but a framework generic `*gvar.Var` object, allowing developers to convert to the needed data type based on business scenarios. This is particularly useful for object cache storage and reading scenarios. Here's an example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Id int + Name string + Site string + } + var ( + ctx = gctx.New() + user *User + key = `UserKey` + value = &User{ + Id: 1, + Name: "GoFrame", + Site: "https://goframe.org", + } + ) + err := gcache.Set(ctx, key, value, 0) + if err != nil { + panic(err) + } + v, err := gcache.Get(ctx, key) + if err != nil { + panic(err) + } + if err = v.Scan(&user); err != nil { + panic(err) + } + fmt.Printf(`%#v`, user) +} +``` + +After execution, the console outputs: + +```bash +&main.User{Id:1, Name:"GoFrame", Site:"https://goframe.org"} +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..e69e83c9394 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" @@ -0,0 +1,66 @@ +--- +slug: '/docs/core/debugging' +title: 'Debug Mode' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Debug Mode, Log Output, Environment Variables, Command Line Arguments, Terminal Standard Output, v1.10.0, v1.14.0, Performance Optimization] +description: "Debug mode of the GoFrame framework, available to all users starting from version v1.10.0. In this mode, key functional nodes output debug information at the [INTE] log level. Developers can open debug mode through command line arguments or environment variables, but debug information is only output to terminal standard output and not supported in log files. In GoFrame v1.14.0, the debug mode can be manually controlled using the g.SetDebug method." +--- + +## Introduction + +The various components of the `goframe` framework print out some debug information at key functional nodes, originally only for internal developers to use during the development phase. Due to its powerful capabilities, starting from version `v1.10.0` of the `goframe` framework, it is fully open to all framework users. + +In debug mode, the debug information printed by the framework will be prefixed with `[INTE]` log level and output to the terminal standard output, and it will print the name of the source file and the line number of the code, for example: + +```html +2021-04-14 15:24:52.954 [INTE] gdb_driver_mysql.go:49 Open: root:12345678@tcp(127.0.0.1:3306)/test +2021-04-14 15:24:52.954 [INTE] gdb.go:492 open new connection success, master:false, config:&gdb.ConfigNode{Host:"", Port:"", User:"", Pass:"", Name:"", Type:"mysql", Role:"", Debug:false, Prefix:"", DryRun:false, Weight:0, Charset:"", LinkInfo:"root:12345678@tcp(127.0.0.1:3306)/test", MaxIdleConnCount:0, MaxOpenConnCount:0, MaxConnLifeTime:0, QueryTimeout:0, ExecTimeout:0, TranTimeout:0, PrepareTimeout:0, CreatedAt:"", UpdatedAt:"", DeletedAt:"", TimeMaintainDisabled:false}, node:&gdb.ConfigNode{Host:"", Port:"", User:"", Pass:"", Name:"", Type:"mysql", Role:"", Debug:false, Prefix:"", DryRun:false, Weight:0, Charset:"utf8", LinkInfo:"root:12345678@tcp(127.0.0.1:3306)/test", MaxIdleConnCount:0, MaxOpenConnCount:0, MaxConnLifeTime:0, QueryTimeout:0, ExecTimeout:0, TranTimeout:0, PrepareTimeout:0, CreatedAt:"", UpdatedAt:"", DeletedAt:"", TimeMaintainDisabled:false} +``` + +## Features Enabling + +By default, this debug information is turned off and does not affect the framework's performance. Framework developers and users can enable it through the following methods: + +1. Command line startup parameter \- `gf.debug=true`. +2. Specified environment variable \- `GF_DEBUG=true`. +3. After `GoFrame v1.14.0`, use the `g.SetDebug` method in the program startup `boot` package to manually toggle on/off. This method is not thread-safe, which means you cannot call this method dynamically in asynchronous multi-coroutines at runtime to set the debug mode. +:::tip +You can find that many functional modules of the `goframe` framework are also configured in the form of **command line startup parameters + environment variables** according to certain rules 🐸. +::: +:::info +It is important to note that key debug information of each module of the framework will only output to the **terminal standard output** and is not supported for output to log files. +::: +## Usage Example + +### Enable Debug Mode via Environment Variables + +Take `Goland IDE` as an example, just add the `GF_DEBUG` environment variable in the running template. + +![](/markdown/ea1f4c4aa8ca8da50174f8240c34912a.png) + +![](/markdown/a4c6819caeacadf867d8ca621372cb8f.png) + +### Enable Debug Mode via Command Line Parameters + +Simply start the program with `--gf.debug=true`, for example: + +```bash +$ ./app --gf.debug=true +``` + +```bash +$ ./app --gf.debug true +``` + +Alternatively, + +```bash +$ ./app --gf.debug=1 +``` + +```bash +$ ./app --gf.debug 1 +``` + +![](/markdown/38ed97756a955abfab1df56acaea5b07.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..283d4a01241 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/core/gres-example' +title: 'Resource - Examples' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Resource Management, WebServer, Static Service, Configuration Management, Template Engine, Resource Files, Example, Programming] +description: "An example of resource management using the GoFrame framework, demonstrating its application in the static service of a WebServer, configuration management, and template engine. By importing resource files, convenient resource management is achieved without extra code setup." +--- + +Let's look at a usage example, which demonstrates the use of resource management in the static service, configuration management, and template engine of a `WebServer`. + +### Resource Files + +Source code of resource files: [https://github.com/gogf/gf/tree/master/os/gres/testdata/example/files](https://github.com/gogf/gf/tree/master/os/gres/testdata/example/files) + +Resource file packaging: [https://github.com/gogf/gf/tree/master/os/gres/testdata/example/boot](https://github.com/gogf/gf/tree/master/os/gres/testdata/example/boot) + +List of resource files: + +``` +2020-03-28T13:04:10+00:00 0.00B config +2020-03-28T13:03:06+00:00 135.00B config/config.toml +2020-03-28T13:04:10+00:00 0.00B public +2020-03-28T12:57:54+00:00 6.00B public/index.html +2020-03-28T13:04:10+00:00 0.00B template +2020-03-28T13:03:17+00:00 15.00B template/index.tpl +TOTAL FILES: 6 +``` + +The contents of the three files are as follows: + +1. `config.toml` + +```toml + [server] + Address = ":8888" + ServerRoot = "public" + + [viewer] + DefaultFile = "index.tpl" + Delimiters = ["${", "}"] +``` + +This file is the configuration file for the application. + +2. `index.html` + +```html + Hello! +``` + +This file is used for static resource requests. + +3. `index.tpl` + +```html + Hello ${.name}! +``` + +This file is used for template file parsing and display. + +### Creating the Application + +```go +package main + +import ( + _ "github.com/gogf/gf/v2/os/gres/testdata/example/boot" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/template", func(r *ghttp.Request) { + r.Response.WriteTplDefault(g.Map{ + "name": "GoFrame", + }) + }) + }) + s.Run() +} +``` + +As you can see, besides an additional package import `_ "github.com/gogf/gf/v2/os/gres/testdata/example/boot"`, the entire code has no other settings. This is the convenience of resource management in the `GoFrame` framework, as resource management does not require any special configuration during the development phase. By packaging the resource files before the application is deployed and adding the resource files via `import`, resource management is achieved. + +After running, the terminal outputs: + +```html +2020-03-28 21:36:19.828 75892: http server started listening on [:8888] + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +|---------|---------|---------|--------|-----------|-------------------|------------| + default | default | :8888 | GET | /template | main.main.func1.1 | +|---------|---------|---------|--------|-----------|-------------------|------------| +``` + +You can see that the configuration file has been automatically read and applied to the `WebServer`. + +Let's use the `curl` command to test accessing static files and the template engine. + +```bash +$ curl http://127.0.0.1:8888/ +Hello! + +$ curl http://127.0.0.1:8888/template +Hello GoFrame! +``` + +You can see that the `index.html` static file and the `index.tpl` template file were both accessed successfully. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" new file mode 100644 index 00000000000..24dd3689aeb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/gres-pack-using-cli' +title: 'Resource - Packing With Tool' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, gf command line tool, resource packaging, gres module, Go file generation, project directory structure, resource management, command line tool, Goland IDE] +description: "Use the gf command line tool in the GoFrame framework for resource packaging. The gf pack command can package the project's config, public, and template directories into Go files, and the generated files are automatically introduced into the project. The packed resource package is preferentially introduced in the boot package, and the packaged resource files are operated through the gres module. The gres.Dump() method can print the resource file list for easy management and debugging." +--- + +We can package any files/directories using the `pack` command of the `gf` command line tool. For specific installation and usage of the `gf` command line tool, please refer to the [Resource Packing](../../开发工具/资源打包-pack.md) section. Since it is quite convenient to package using the command line tool, it is the recommended packaging method. + +## `gf pack` Generates `Go` File + +The recommended way is to directly generate the `Go` file to the `boot` startup directory and set the package name of the generated `Go` file to `boot`, so the resource file will be automatically introduced into the project. We pack the files of the project's `config, public, template` directories into a `Go` file, the packaging command is: + +``` +gf pack config,public,template packed/data.go -n packed +``` + +The content of the generated `Go` file is similar to: + +```go +package packed + +import "github.com/gogf/gf/v2/os/gres" + +func init() { + if err := gres.Add("H4sIAAAAAAAC/5y8c5Bl0Zbuu9O2bVaq0rZZ6Urbtm3bNnfatipto9"); err != nil { + panic(err) + } +} +``` + +As you can see, the generated `Go` file adds the binary content of the resource file to the default resource manager through the `gres.Add` method. The parameter of this method is a compressed BASE64 string, which will be decompressed during program startup and generate a file tree object in memory for quick file manipulation during runtime. + +## Using the Packaged `Go` File + +### Prioritize Importing `packed` Resource Package in `boot` Package + +In the project's `boot` program startup setting package, automatically import the `packed` resource package, and it should be the first package to be imported, so that other imported packages can use the resource content during initialization (`init` method). For example (the `module` name is `my-app`): + +```go +import ( + _ "my-app/packed" + + // other packages +) +``` + +It is recommended to include a blank line between the `packed` package and other packages, especially since the `Goland` IDE's `import` plugin will not automatically sort the imported packages. + +### Prioritize Importing `boot` Package in `main` Package + +Since the project's `main` entry program file will import the `boot` package, and it should be the first package to be imported: + +```go +import ( + _ "my-app/boot" + + // other packages +) +``` + +Here, it is recommended to include a blank line between the `boot` package and other packages for distinction, especially since the `Goland` IDE's `import` plugin will not automatically sort the imported packages. + +Then you can use the `gres` module anywhere in the project to access the packaged resource files. + +> If using the recommended project directory structure of `GoFrame` (new project), the directory structure will have a `boot` directory (corresponding package name is also `boot`) for program startup settings. Therefore, if the `Go` file is generated in the `boot` directory, it will be automatically compiled into the executable file. + +## Print Resource Management File List + +The `gres.Dump()` method can be used to print out all file lists in the current resource manager, with output similar to: + +``` +2019-09-15T13:36:28+00:00 0.00B config +2019-07-27T07:26:12+00:00 1.34K config/config.toml +2019-09-15T13:36:28+00:00 0.00B public +2019-06-25T17:03:56+00:00 0.00B public/resource +2018-12-04T12:50:16+00:00 0.00B public/resource/css +2018-12-17T12:54:26+00:00 0.00B public/resource/css/document +2018-12-17T12:54:26+00:00 4.20K public/resource/css/document/style.css +2018-08-24T01:46:58+00:00 32.00B public/resource/css/index.css +2019-05-23T03:51:24+00:00 0.00B public/resource/image +2018-08-20T05:02:08+00:00 24.01K public/resource/image/cover.png +2019-05-23T03:51:24+00:00 4.19K public/resource/image/favicon.ico +2018-08-23T01:44:50+00:00 4.19K public/resource/image/gf.ico +2018-12-04T13:04:34+00:00 0.00B public/resource/js +2019-06-27T11:06:12+00:00 0.00B public/resource/js/document +2019-06-27T11:06:12+00:00 11.67K public/resource/js/document/index.js +2019-09-15T13:36:28+00:00 0.00B template +2019-02-02T09:08:56+00:00 0.00B template/document +2018-12-04T12:49:08+00:00 0.00B template/document/include +2018-12-04T12:49:08+00:00 329.00B template/document/include/404.html +2019-03-06T01:52:56+00:00 3.42K template/document/index.html +... +``` + +Note that when using resource files in the resource manager, you need to strictly follow their paths for retrieval. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..870d6e27a98 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,327 @@ +--- +slug: '/docs/core/gres-funcs' +title: 'Resource - Methods' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Resource Management, Method Introduction, Add, Load, Get, GetWithIndex, GetContent, Contains, ScanDir] +description: "Methods for resource management in the GoFrame framework, including how to add resources, load resource files, get specified path files, check if resources exist, scan files in directories, and provide relevant example code for understanding and usage." +--- +:::tip +The following is a list of common methods. Documentation updates may lag behind new code features; please refer to the code documentation for more methods and examples: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) +::: +## Add + +- Description: `Add` decompresses and adds `content` to the default resource object. `prefix` is an optional parameter that indicates the prefix for each file stored in the current resource object. + +- Format: + +```go +func Add(content string, prefix ...string) error +``` + +- Example: + +```go +package main + +import "github.com/gogf/gf/v2/os/gres" + +func main() { + if err := gres.Add("H4sIAAAAAAAC/5TWV1TTydsH8B+EpoAginEBDVIEQYp0kUTpSjAJhCwsxSwlgEtRwVCkKwJSo9IWCCChSAkoTZoivfeF0JEEQpMgvQrvcc/x1X/Zi/9zMTfzzPfMc/OZQcFBLKcBDoAD6DTsRQE/FS9wDLC/64xzl3PHuXng3GRt3R5iTFgBJt6X83cexsJLqjT4mrd9w0fQ6OPh+uAVEbGTj1k4TK1bdBu6nrT2Sab5GyC7ao5yc613l3Ly29Yfbl6fSovmph6AbK6L+TBrhl03yQhO9w7hHUcIcWSAEJd/z6I0MtJHaaKscBAb6STB5unZoVdhgDIFqQaCzyEuLwhza87xLoKCvMJtmjmYMYn63Sjx/X3IuE66VuXJKcVB08OurMS3IObBV6ilar8HjNhjTAGCzixCKecGJI4HfU3NBhngff1mmvfuihCWagSzqpwiiWaLhGkW8ZUMtrUOV0nZWSiGUl8ADw2xVAvrVMqUy2Fdmmmo4nl5CLKwynbmI/hlC5qZSd5INHqjEshnzRH8Sy60vatCdrS2MpPTWqGMEQEfxExDuC0T6/aRAmQZc8NLZ/0reVtVbeKKswKpFFBw4NFq4J7WWY2yE9p6i/oLwNCCT2HkioAirGf/cEcnDGH+6SCBPXAaCsiyOHNOMrfy8MpzqcyqbmcIUh9NqM3d240wHkieqIsLVQvYCCrFHtxYwQsjoEQNW6VrAbc/d9estPr1Cspt8kdMzResBrnITixfkXV+dH6IvwaET9i1fEFbKv2SHOHvYj8S86uPXU3zWv6LnglKVf37PpeU0WIVfU/lJFEJS++4ZAPt2x7NU1Ci+DwkxJCcWDN2IknmvjBEBoro/g0UJw2rLTQ17sCt42H05ZJ1KHyoWakNRBajN4lEB7di4EUDnXH9yfEp40qd719eKB6BRsV2o63HvMbKgno9JmOKCeTCKsmcLKPqdg+xwplYW9fUSI1IqgeMEV1/710WoQUzxyPavxZc3se1g7g4r9fS8uaZYvb7d0QmP1Qgof6MyYqy8ojAytWI2CZkYyHDerexvnz/tHI3+bNekf4Ted1wm4CRRXXwPpNvaf/RKdEvJ0PVw1h4zbCdtZVH5/Y3nlk9ciD31aPS+v4wPytp7FgDk2t6hUhHQ3pUryFB05l6FWr8Y50oQUWOCdzmfMJaGR5zDKIizdPHTiZBLNr2KKSEiQdJ/XyDq790DfrfjEmWdw/fqATXWcJqy8GeaC4g3dVTUAxU0vjc1n3Rcwwvv+RPt2iKQStpOVGrPat55gbqaiaq8LgBNkRdj3T7nBUE1+OB2o7aCJNRjZP7QFukFSRqGONqF7atay17bt2payyZJ5YEWuBmVnMC7KqjKHXLUCj0y3ZTgsnp3ZyPMc7U7ArlJj5cckJfjJFD3PM6ZL7o7zuVxT5eZWttT8do+Lwy+5fzU63SjjL41Mk9fzWuui63P1nLWsLF19Y+NmEDbqWVPDu3UZuzgWUCgKMjFJydY3YuFzkBAgAHVgD4H4xwwnn/vxHOg6p8+cPMLL0pmJqUvac3hnndgyJUmS8oRH7R8yrZw65fJCKuLSzOHKykcdwIGoL0zB2pnefaBe+QnvRMsV25wEJjYk2l6E6yahC5s051Gl8yKcNwSdom5bmPkaKirYs9P0SSM9wXevPii0iGUv2PxJTM8bRPKsfL1lfvk1O6kqX0Exhj3mnyDZf1FMCqqZ9sinQaph574+UOFgU3H0uIwNuVcYbRZ2S06elo89E8pajoz3OZCPHLq9Ikd4M8GjMX0TsiFdasw8gQumbczMQLiVK64At+IEO9jrKshRT0FtiHS97tHnJXik2CrTTmKLzeEigV2yHBamdlVzt/Oy+uY388ttv0KnXsEJHtLZdrDZS7HDyhpvC3oMhdPgf7KClBBwlHi5HY11hxb1Zrd33LYKE7Dfbs6lCLeyliUt7Jt++vi4dq3VXJ8ctsGM6ylFyVlfsietoLY6+Axns8mbGLdyFY28cyncgHm4QKfxIK1Bo9gZeKdGrmdngtkd6WoJ8aDksDw1uwbCXZX1MkUC9JW8/G4UPxBltxXLbVrnrCjg/gOr1hlRKEnrZZY8adj5KcebmdIkRpi9C8+fKrIImcV8QL8UeTe8uGHNTbyMpmSpLG80n/xvUXIQ0Or7Qe7xMgVKZqecdUOPnrznT9G2FU7cAWnfc859l7PAm65sBNtB84QJeueNjRvxFoVKWQqZ6ngI9Qi2JFaj8t3lQvNxMprPhzl3oT+dd6pfXUHySH34lvNyUT6plmYFEMuU3DCIr628SKmRGY0OZqsdwfpdoybrHg2HEx3Y8Du2vsvB08nypJIn1/Xerq8icM1iM7HK+LRZ2KYeFnGLnBtk4UXIljwBTrPsNDUkWmyhtquc055AbRDTqOqtUfK6adlDC2AreiOSUzDUhW2bZK0vG/uLedasI62TKvzrOdCTqz3Fb2m0UUsRFNC7ORz56dyDxWk0agq/fh2QfcorOXDhTuqJHqUy7uVCe60IDcvI8ZJZinotfsta+Li0HUFgweztjFPM4It8cqO+H5pa2KwcTKc3Iho0R72ssr+fQnzhlWmPJhq6CYItPnckjp9nuHkSnRmzym6C2GN5I8I8WVbWKju75d+OvbFldDghD25N2GIFMzPee7ngK5wwbJ7cGaR8NJqa1r8gEM053Lh9BKii86rjgew7aqAoS/f33+2YdCXrDwEM7kkAzBhl4d3UX2HGTXkQ1iz1RpJLp2okp8fL9aJt9zWTnvAEncndWB9RusvDMeNiehnp/zO86fTyUk+BZkmS4r2gnR4zbV98UF8TlTjf1RuItnalBGiXnMoa0dydUvRMX9fV2cd212waFRny7EQ4L4/FqalQRFkzC5C+ocOv3L+FOSL97dku7TetZ7Ur3g1Ko2ZnvuAbPyEvmAme6vmBQ1QJq0v42aWHzE4u+9Z/RG69ZtoEs6h9IbrccQe+yK9kgl+yIyE7jPpk2wjL6/SVacpfnZKef+FdwlZMZZgDbBalpLcncNr++XOPVuXbbzHuqjLmpdmuad6MtiPLWK2fSvWSHSbuSedlgifhWqgA/FebVe9NopPn5Dd9xRPFBrlB5KL5q+SbnDSP0gQHe7r5+o2VHNaYUIaRHkWygeRyC72aU5sT6mRuUR0W/4eBw0ZQvzfDpz1/TuZ7T4zfLpSJfOz6GbMkrTitSSv4ws1kn55L1h4RyMbcUaB4p/VVU+4vmOKYfAsggbKwBEsP3AFACgAWFGP2PK+h3TvwUNyejBfjv7cwcKzsR8GvSD4p+R/kbx98oM+rb+4+ft32N+vt6/xoixAP/s+4+Y/z7L9yfiSPPBMeA/JmNl+7YLAkBAOwAA5L/b/y8AAP//A6tAlY0KAAA="); err != nil { + panic("add binary content to resource manager failed: " + err.Error()) + } +} +``` + +## `Load` + +- Description: `Load` loads, decompresses, and reads file data at path `path` into the default resource object. `prefix` is an optional parameter that signifies the prefix for each file stored in the current resource object. + +- Format: + +```go +func Load(path string, prefix ...string) error +``` + +- Example: + +```go +package main + +import "github.com/gogf/gf/v2/os/gres" + +func main() { + if err := gres.Load("../res/myfile"); err != nil { + panic("load binary content to resource manager failed: " + err.Error()) + } +} +``` + +## `Get` + +- Description: `Get` returns the file at the specified path. + +- Format: + +```go +func Get(path string) *File +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + file := gres.Get("../res/myfile") + if file == nil { + glog.Error(gctx.New(), "get file failed!") + return + } + + fmt.Println("Get File Name:", file.Name()) +} +``` + +## `GetWithIndex` + +- Description: `GetWithIndex` searches for a file with the given path. If the file is a directory, it searches for index files within that directory. `GetWithIndex` is typically used for HTTP static file services. + +- Format: + +```go +func GetWithIndex(path string, indexFiles []string) *File +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + file := gres.GetWithIndex("../res", []string{"myfile", "myconfig"}) + if file == nil { + glog.Error(gctx.New(), "get file failed!") + return + } + + fmt.Println("Get File Name:", file.Name()) +} +``` + +## `GetContent` + +- Description: `GetContent` directly returns the content at path `path` from the default resource object. + +- Format: + +```go +func GetContent(path string) []byte +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + fileContent := gres.GetContent("../res/myfile") + fmt.Println("Get File Content:", fileContent) +} +``` + +## `Contains` + +- Description: `Contains` checks if a resource with the path `path` exists in the default resource object. + +- Format: + +```go +func Contains(path string) bool +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + if gres.Contains("../res/myfile") { + fmt.Println("myfile is exist!") + } else{ + fmt.Println("myfile is not exist!") + } +} +``` + +## `IsEmpty` + +- Description: `IsEmpty` checks and returns whether the resource manager is empty. + +- Format: + +```go +func IsEmpty() bool +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + fmt.Println(gres.IsEmpty()) + + gres.Add("xxxxxxxxxxxxxxxxx") + + fmt.Println(gres.IsEmpty()) + + // Output: + // true + // false +} +``` + +## `ScanDir` + +- Description: `ScanDir` returns files under a given path. The parameter `path` should be of type folder. The parameter `pattern` supports multiple filename patterns, separated by `,`. If the parameter `recursive` is `true`, it recursively scans directories. + +- Format: + +```go +func ScanDir(path string, pattern string, recursive ...bool) []*File +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + files := gres.ScanDir("../res", "*.doc,*.go", true) + if len(files) > 0 { + for _, file := range files { + fmt.Println("ScanDir Result:", file.Name()) + } + } +} +``` + +## `ScanDirFile` + +- Description: `ScanDirFile` returns all subfiles for the given absolute path `path`. If the parameter `recursive` is `true`, it will recursively scan directories. + +- Note: Only returns files, not directories. +- Format: + +```go +func ScanDirFile(path string, pattern string, recursive ...bool) []*File +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + files := gres.ScanDirFile("../res", "*.*", true) + if len(files) > 0 { + for _, file := range files { + fmt.Println("ScanDirFile Result:", file.Name()) + } + } +} +``` + +## `Export` + +- Description: `Export` recursively saves the specified path `src` and all its subfiles to the specified system path `dst`. + +- Format: + +```go +func Export(src, dst string, option ...ExportOption) error +``` + +- Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + err := gres.Export("../res/src", "../res/dst") + if err != nil { + fmt.Println("gres.Export Error:", err) + } +} +``` + +## `Dump` + +- Description: `Dump` prints the files of the default resource object. + +- Format: + +```go +func Dump() +``` + +- Example: + +```go +package main + +import ( + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + gres.Add("xxxxxxxxx") + + gres.Dump() +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" new file mode 100644 index 00000000000..cc48951604a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/core/gres-pack-using-funcs' +title: 'Resource - Packing With Method' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Resource Management, Method Packing, File Packing, Binary File, Encryption and Decryption, Pack Method, Unpack Method, ScanDir, ScanDirFile] +description: "Resource management and method packing using the GoFrame framework. Implement custom methods for packaging and unpacking files and directories, supporting binary and Go code files. The examples also demonstrate how to protect resource file contents through custom encryption and decryption, providing detailed interface documentation and implementation details." +--- +:::tip +The examples in this chapter demonstrate encryption/decryption while performing packing/unpacking. Most business projects do not actually require encryption/decryption, so direct use of tools for packing is sufficient. +::: +In the previous chapter, we introduced how to use the `gf` toolchain for file/directory packing and generate `Go` files compiled into executables. In this chapter, we introduce the methods involved in resource management and demonstrate a custom packing/unpacking feature through an example of binary resource file packing/unpacking. We also show how to use custom encryption and decryption to protect our resource file contents. + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) + +**Brief Introduction:** + +1. The `Pack*`/ `Unpack*` methods can implement packing/unpacking for any file, packing to binary files or Go code files. +2. Resource management is implemented by the `Resource` object, which can add packed content, search files, and traverse directories. +3. Resource files are implemented by the `File` object, which is similar to the `os.File` object and implements the `http.File` interface. +4. `ScanDir` is used for file/directory search in a specific directory, supporting recursive search. +5. `ScanDirFile` is used for file search in a specific directory, supporting recursive search. +6. The `Dump` method prints out all files of the `Resource` object on the terminal, with the file separator in the resource manager unified as `/`. +7. Furthermore, the `gres` resource management module provides a default `Resource` object with package methods for operating that default object. + +## Custom Packing Example + +We will pack the `public` and `config` directories under the project root directory into a `data.bin` binary file, and encrypt the generated binary content using the `gaes` encryption algorithm. + +```go +package main + +import ( + "github.com/gogf/gf/v2/crypto/gaes" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" +) + +var ( + CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0") +) + +func main() { + binContent, err := gres.Pack("public,config") + if err != nil { + panic(err) + } + binContent, err = gaes.Encrypt(binContent, CryptoKey) + if err != nil { + panic(err) + } + if err := gfile.PutBytes("data.bin", binContent); err != nil { + panic(err) + } +} +``` + +## Custom Unpacking Example + +We will use the `data.bin` just packed, requiring decryption and unpacking operations. + +```go +package main + +import ( + "github.com/gogf/gf/v2/crypto/gaes" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" +) + +var ( + CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0") +) + +func main() { + binContent := gfile.GetBytes("data.bin") + binContent, err := gaes.Decrypt(binContent, CryptoKey) + if err != nil { + panic(err) + } + if err := gres.Add(binContent); err != nil { + panic(err) + } + gres.Dump() +} +``` + +Finally, we use `gres.Dump()` to print out the list of successfully added files to see if the resource files are added successfully. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..ce2cf317dbc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/core/gres-practice' +title: 'Resource - Best Practices' +sidebar_position: 4 +hide_title: true +keywords: [Resource Management, GoFrame, Framework Engineering, Static Files, Development Phase, CLI Tool, Cross Compilation, Binary File, Configuration Management, Development Efficiency] +description: "Best practices for resource management in the GoFrame framework. By using the engineering directory structure and CLI tools provided by GoFrame, developers can effectively manage static resources without affecting the development process. During the release phase, the resource components package static files into the binary executable file, achieving efficient resource release and management." +--- + +One of the goals of resource management design is not to affect the development and management of static files during the development phase, only to execute packaging at release time. Once packaging is complete, temporary files are cleaned up, so it only affects the generated binary executable file, making it imperceptible and convenient for developers to use. + +## Prepare the Project + +It is recommended to use the engineering directory structure officially provided by `GoFrame` and to use the `CLI` tool to create your project, as the entire framework engineering concept and some examples are based on a standardized engineering directory structure. This is more conducive to learning and using the entire development framework, improving development efficiency. In particular, there is a `packed` directory in the project directory to store the packaged content related to the resource management component. By default, it contains a blank `go` file that does nothing. + +![](/markdown/f684a4fd1a310e760d058df443cf2108.png) + +## Development Phase + +During the development phase, generally, developers do not need to care about resource management; they should code as usual, and static files should be placed in the `resource` directory as suggested. In the development phase, static file access will be directly through the file system. + +## Prepare for Release + +After development is complete, if you wish to package static files, template files, and configuration files into the binary executable file for release alongside it, the resource component will now begin to showcase its capability. You need to configure cross-compilation settings, specifically refer to the command line section [Cross-Compiling](../../开发工具/交叉编译-build.md). We need to use the CLI tool to execute executable file compilation, managing your compile configuration through a configuration file (placed in the hack/config.yaml file). A reference compile configuration is as follows: + +``` +gfcli: + build: + name: "my-app" + arch: "amd64" + system: "linux" + mod: "none" + cgo: 0 + packSrc: "manifest/config,manifest/i18n,resource/public,resource/template" + version: "" + output: "./bin" + extra: "" +``` + +Please note the `pack` configuration, indicating the directories to be automatically packaged into the binary executable file during compilation. Then, execute the compile command in the root directory of the project: + +``` +gf build +``` + +This command automatically packages the directories specified in the configuration file into temporary packaged `go` files during compilation, then cleans up the temporary packaged `go` files after compilation is complete. + +:::tip +In most scenarios, configuration files may not need to be packaged into the binary executable file. Choose according to your business scenario. +::: + +## Release and Run + +Release and execute the binary as needed. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..0af6fcd8dcd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/core/gres' +title: 'Resource' +sidebar_position: 12 +hide_title: true +keywords: [Resource Management, GoFrame, GoFrame Framework, Golang, File Management, Memory Operations, Binary Resources, IO Efficiency, embed.FS, Engineering Management] +description: "Resource management through the GoFrame framework, which packages any file or directory into a Golang source file for efficient memory operations. Resource files support custom encryption, decryption, and compression, making file operations quicker as they can serve as standalone binary resource files." +--- + +## Introduction + +`Resource Management` refers to the ability to package any file/directory into a `Golang` source file and compile it into an executable file, which is then released with the executable file. + +When the program starts, resource files will be decompressed and released into memory for read-only access by the program. You can consider it as a memory-based file manager. Additionally, the `GoFrame` resource management feature supports packaging files/directories for use as standalone binary resource files. Since resource file operations are memory-based during program execution, there is no disk `IO` overhead, resulting in very high file operation efficiency. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gres" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) + +## Features + +The `gres` resource management component has the following notable features: + +1. It can package any file/directory as a `Go` file, supporting custom encryption and decryption. +2. The packaged `Go` file/resource file is automatically compressed, with common `css/js` files achieving a compression rate of `50~90%`. +3. It supports easily exporting resource content packaged in `Go` files to the local file system. +4. Resource manager content is entirely memory-based and read-only, with no dynamic modification possible. +5. The resource manager is integrated by default with the `WebServer`, configuration management, and template engine modules. +6. Any file, such as website static files or configuration files, can be compiled into a binary file and also into the released executable file. +7. Developers can publish just one executable file, making software distribution easier and protecting software intellectual property possible. + +## Comparison with `embed.FS` + +Starting from `Golang v1.16`, the official release provides a static file embedding feature `embed.FS`. The overall underlying design is similar to the `gres` component, with comparable compression rates and execution efficiency, although there are significant differences in usage design and engineering management. The `GoFrame` resource management component is more feature-rich, and the core components of the framework have been fully integrated with the `gres` resource management component. Under GoFrame's standard engineering management, developers can seamlessly use the resource management features without being aware of them. For details, see the chapter [Resource - Best Practices](资源管理-最佳实践.md). + +In the future, the GoFrame base framework will not consider built-in support for the `embed.FS` component. `embed.FS` and the `gres` component can be independently used without affecting each other. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..56ba8c87673 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/core/gcfg-toml' +title: 'Configuration - TOML' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, TOML Configuration, Configuration Management, TOML Syntax, Key-Value Pairs, Table Arrays, Data Serialization, YAML Comparison, JSON Parsing, Open Source Framework] +description: "The use of TOML format in the GoFrame framework, including its comparison with other file formats, basic syntax, numeric and BOOL value processing, date and time, arrays, tables, table arrays, etc., to help users better understand and apply TOML configuration management." +--- + +## Introduction + +`Toml` is a small, easy-to-read language created by the former CEO of `GitHub`, `Tom`. `Tom's Obvious, Minimal Language`. + +`TOML` is focused on minimizing and making configuration files easy to read. + +1. WIKI Introduction: [https://github.com/toml-lang/toml/wiki](https://github.com/toml-lang/toml/wiki) +2. Official Address: [https://github.com/toml-lang/toml](https://github.com/toml-lang/toml) +3. Chinese Version: [https://github.com/LongTengDao/TOML/blob/%E9%BE%99%E8%85%BE%E9%81%93-%E8%AF%91/toml-v1.0.0.md](https://github.com/LongTengDao/TOML/blob/%E9%BE%99%E8%85%BE%E9%81%93-%E8%AF%91/toml-v1.0.0.md) + +## Comparison with Other Formats + +`TOML` shares the same characteristics as other file formats like `YAML` and `JSON` used for application configuration and data serialization. Both `TOML` and `JSON` are simple and use ubiquitous data types, making them easy to parse by code or machines. `TOML` and `YAML` both emphasize human readability, such as comments, which make understanding the purpose of a given line easier. The distinct feature of `TOML` is that it supports comments (unlike `JSON`) while maintaining simplicity (unlike `YAML`). + +Since `TOML` is explicitly designed as a configuration file format, parsing it is easy, but it is not intended to serialize arbitrary data structures. The top level of a `TOML` file is a hash table which can easily nest data in keys, but it doesn't allow top-level arrays or floating point numbers, so it cannot directly serialize some data. There is no standard to identify the start or end of a `TOML` file, which complicates sending files over streams. These details must be negotiated at the application level. + +`INI` files are often compared with `TOML` because of their syntactical similarity and common use as configuration files. However, `INI` files do not have a standardized format and do not handle more than one or two levels of nesting gracefully. + +## Basic Syntax + +```toml +title = "TOML Example" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # Date and time are first-class citizens. Why not? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + # You can indent as you please. Tabs or spaces. TOML doesn't care. + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] + +# Newlines in arrays are no problem. +hosts = [ + "alpha", + "omega" +] +``` + +Features: + +- Case sensitive, must be `UTF-8` encoded +- Comments: `#` +- Whitespace: `tab(0x09)` or `space(0x20)` +- Newline: `LF(0x0A)` or `CRLF(0x0D 0x0A)` +- Key-value pairs: single line, keys without a value are not allowed, only one key-value pair per line + +`TOML`'s primary structure is key-value pairs, similar to `JSON`. Values must be of the following types: `String`, `Integer`, `Float`, `Boolean`, `Datetime`, `Array`, `Table` + +## Comments + +Comments are represented with `#`: + +```toml +# I am a comment. Hear me roar. Roar. +key = "value" # Yeah, you can do this. +``` + +## Strings + +There are four ways of representing strings in `TOML`: basic, multi-line basic, literal, and multi-line literal. + +### 1. Basic String + +Enclosed in double quotes, all `Unicode` characters can appear, except double quotes, backslashes, and control characters (`U+0000` to `U+001F`) which need to be escaped. + +### 2. Multi-line Basic String + +Enclosed in three double quotes, newlines within the string will be preserved, except for the newline at the start of the delimiter: + +```toml +str1 = """ +Roses are red +Violets are blue""" +``` + +### 3. Literal String + +Enclosed in single quotes, escapes are not allowed within, making it easy to represent content that needs to be escaped in basic strings: + +```toml +winpath = 'C:\Users\nodejs\templates' +``` + +### 4. Multi-line Literal String + +Similar to a multi-line basic string: + +```toml +str1 = ''' +Roses are red +Violets are blue''' +``` + +## Numbers and BOOL Values + +```toml +int1 = +99 +flt3 = -0.01 +bool1 = true +``` + +## Date and Time + +```toml +date = 1979-05-27T07:32:00Z +``` + +## Arrays + +Arrays are enclosed in square brackets. Spaces are ignored. Elements are separated by commas. + +Note that mixing data types within the same array is not allowed. + +```toml +array1 = [ 1, 2, 3 ] +array2 = [ "red", "yellow", "green" ] +array3 = [ [ 1, 2 ], [3, 4, 5] ] +array4 = [ [ 1, 2 ], ["a", "b", "c"] ] # This is allowed. +array5 = [ 1, 2.0 ] # Note: This is not allowed. +``` + +## Tables + +Tables (also called hash tables or dictionaries) are collections of key-value pairs. They are their own line enclosed in square brackets. Note the distinction from arrays, which contain only values. + +```toml +[table] +``` + +Below this, until the next `table` or `EOF`, are the key-value pairs of this table. Keys on the left, values on the right, with an equal sign in between. Keys start with a non-space character and end with the last non-space character before the equal sign. Key-value pairs are unordered. + +```toml +[table] +key = "value" +``` + +You can indent however you want, using `Tabs` or `spaces`. Why indent? Because you can nest tables. + +Nested tables use the `.` symbol in the table name. You can name your tables as you like, just don't use a dot, as it's reserved. + +```toml +[dog.tater] +type = "pug" +``` + +This is equivalent to the following `JSON` structure: + +``` +{ "dog": { "tater": { "type": "pug" } } } +``` + +You don't need to declare all parent tables if you don't want to. TOML knows how to handle it. + +```toml +# [x] You +# [x.y] don't need +# [x.y.z] these +[x.y.z.w] # You can write it directly +``` + +Empty tables are allowed, with no key-value pairs. + +As long as a parent table hasn't been defined directly and no specific key has been defined, you can continue writing: + +```toml +[a.b] +c = 1 + +[a] +d = 2 +``` + +However, you cannot redefine keys and tables multiple times. Doing so is illegal. + +```toml +# Don't do this! + +[a] +b = 1 + +[a] +c = 2 +# Don't do this either + +[a] +b = 1 + +[a.b] +c = 2 +``` + +## Table Arrays + +The last type to introduce is table arrays. Table arrays are expressed by enclosing the table name within double square brackets. Tables with the same double bracket names are elements of the same array. Tables are inserted in the order they are written. Double bracket tables without key-value pairs are considered empty tables. + +```toml +[[products]] +name = "Hammer" +sku = 738594937 + +[[products]] + +[[products]] +name = "Nail" +sku = 284758393 +color = "gray" +``` + +This is equivalent to the following `JSON` structure: + +``` +{ + "products": [ + { "name": "Hammer", "sku": 738594937 }, + { }, + { "name": "Nail", "sku": 284758393, "color": "gray" } + ] +} +``` + +Table arrays can also be nested. Just use the same double bracket syntax on the child table. Each double-bracketed child table belongs to the most recently defined upper-level table element. + +```toml +[[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + +[[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" +``` + +This is equivalent to the following `JSON` structure: + +``` +{ + "fruit": [ + { + "name": "apple", + "physical": { + "color": "red", + "shape": "round" + }, + "variety": [ + { "name": "red delicious" }, + { "name": "granny smith" } + ] + }, + { + "name": "banana", + "variety": [ + { "name": "plantain" } + ] + } + ] +} +``` + +Attempting to define a regular table using an already defined array name will throw a parse error: + +```toml +# Invalid TOML + +[[fruit]] + name = "apple" + + [[fruit.variety]] + name = "red delicious" + + # Conflicts with above + [fruit.variety] + name = "granny smith" +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..3cfd8f85aac --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" @@ -0,0 +1,374 @@ +--- +slug: '/docs/core/gcfg-yaml' +title: 'Configuration - YAML' +sidebar_position: 4 +hide_title: true +keywords: [Configuration Management, YAML Format, GoFrame, Data Serialization, Mapping, Array, Scalar, Object, Composite Structure, JavaScript Conversion] +description: "Configuration management using YAML format in the GoFrame framework. YAML is a data serialization format that is easy for humans to read and write and supports data structures like objects, arrays, and scalars. The article also provides examples of conversion between YAML and JavaScript to help readers better understand the application and implementation of YAML format." +--- + +### I. Introduction + +The design goal of the YAML language (pronounced /ˈjæməl/) is to facilitate human readability and writability. It is essentially a general-purpose data serialization format. + +The basic syntax rules are as follows. + +> - Case sensitive +> - Use indentation to represent hierarchical relationships +> - Tabs are not allowed for indentation, only spaces are permitted. +> - The number of spaces for indentation is not important, as long as elements at the same level are aligned to the left + +`#` indicates a comment, from this character to the end of the line will be ignored by the parser. + +YAML supports three data structures. + +> - Object: A collection of key-value pairs, also known as mapping/hashes/dictionary +> - Array: An ordered sequence of values, also known as sequence/list +> - Scalars: Single, indivisible values + +These three structures are described below. + +### II. Object + +A set of key-value pairs in an object is represented using a colon structure. + +> ```javascript +> animal: pets +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { animal: 'pets' } +> ``` + +YAML also allows another notation, writing all key-value pairs as an inline object. + +> ```javascript +> hash: { name: Steve, foo: bar } +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { hash: { name: 'Steve', foo: 'bar' } } +> ``` + +### III. Array + +A set of lines starting with a hyphen constitutes an array. + +> ```javascript +> - Cat +> - Dog +> - Goldfish +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> [ 'Cat', 'Dog', 'Goldfish' ] +> ``` + +If the sub-member of the data structure is an array, you can indent a space under that item. + +> ```javascript +> - +> - Cat +> - Dog +> - Goldfish +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> [ [ 'Cat', 'Dog', 'Goldfish' ] ] +> ``` + +Arrays can also be represented using inline notation. + +> ```javascript +> animal: [Cat, Dog] +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { animal: [ 'Cat', 'Dog' ] } +> ``` + +### IV. Composite Structures + +Objects and arrays can be combined to form composite structures. + +> ```javascript +> languages: +> - Ruby +> - Perl +> - Python +> websites: +> YAML: yaml.org +> Ruby: ruby-lang.org +> Python: python.org +> Perl: use.perl.org +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { languages: [ 'Ruby', 'Perl', 'Python' ], +> websites: +> { YAML: 'yaml.org', +> Ruby: 'ruby-lang.org', +> Python: 'python.org', +> Perl: 'use.perl.org' } } +> ``` + +### V. Scalars + +Scalars are the most basic, indivisible values. The following data types belong to JavaScript scalars. + +> - String +> - Boolean +> - Integer +> - Float +> - Null +> - Time +> - Date + +Values are represented in literal form. + +> ```javascript +> number: 12.30 +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { number: 12.30 } +> ``` + +Boolean values are represented by `true` and `false`. + +> ```javascript +> isSet: true +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { isSet: true } +> ``` + +`null` is represented by `~`. + +> ```javascript +> parent: ~ +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { parent: null } +> ``` + +Time is represented in ISO8601 format. + +> ```javascript +> iso8601: 2001-12-14t21:59:43.10-05:00 +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { iso8601: new Date('2001-12-14t21:59:43.10-05:00') } +> ``` + +Date is represented using the composite ISO8601 format of year, month, and day. + +> ```javascript +> date: 1976-07-31 +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { date: new Date('1976-07-31') } +> ``` + +YAML allows the use of two exclamation points to forcefully convert data types. + +> ```javascript +> e: !!str 123 +> f: !!str true +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { e: '123', f: 'true' } +> ``` + +### VI. Strings + +Strings are the most common and also the most complex data type. + +Strings are not quoted by default. + +> ```javascript +> str: 这是一行字符串 +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { str: '这是一行字符串' } +> ``` + +If a string contains spaces or special characters, it needs to be enclosed in quotes. + +> ```javascript +> str: '内容: 字符串' +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { str: '内容: 字符串' } +> ``` + +Both single and double quotes can be used, and double quotes will not escape special characters. + +> ```javascript +> s1: '内容\n字符串' +> s2: "内容\n字符串" +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { s1: '内容\\n字符串', s2: '内容\n字符串' } +> ``` + +If there are single quotes within single quotes, two consecutive single quotes must be used to escape. + +> ```javascript +> str: 'labor''s day' +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { str: 'labor\'s day' } +> ``` + +Strings can be written in multiple lines, and starting from the second line, there must be a single space indentation. Line breaks will be converted into spaces. + +> ```javascript +> str: 这是一段 +> 多行 +> 字符串 +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { str: '这是一段 多行 字符串' } +> ``` + +Multiline strings can use `|` to preserve line breaks or `>` to fold line breaks. + +> ```javascript +> this: | +> Foo +> Bar +> that: > Foo +> Bar +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { this: 'Foo\nBar\n', that: 'Foo Bar\n' } +> ``` + +`+` indicates preserving the ending line breaks of text blocks, and `-` indicates removing the ending line breaks of strings. + +> ```javascript +> s1: | +> Foo +> s2: |+ +> Foo +> s3: |- +> Foo +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' } +> ``` + +HTML tags can be inserted within strings. + +> ```javascript +> message: | +>

    段落 +>

    +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> { message: '\n

    \n 段落\n

    \n' } +> ``` + +### VII. References + +Anchors `&` and aliases `*` can be used for references. + +> ```javascript +> defaults: &defaults +> adapter: postgres +> host: localhost +> development: +> database: myapp_development +> <<: *defaults +> test: +> database: myapp_test +> <<: *defaults +> ``` + +Equivalent to the following code. + +> ```javascript +> defaults: +> adapter: postgres +> host: localhost +> development: +> database: myapp_development +> adapter: postgres +> host: localhost +> test: +> database: myapp_test +> adapter: postgres +> host: localhost +> ``` + +`&` is used to create an anchor (`defaults`), `<<` indicates merging into current data, and `*` is used to reference the anchor. + +Here is another example. + +> ```javascript +> - &showell Steve +> - Clark +> - Brian +> - Oren +> - *showell +> ``` + +Converted to JavaScript as follows. + +> ```javascript +> [ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ] +> ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..cb472835511 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" @@ -0,0 +1,372 @@ +--- +slug: '/docs/core/gcfg-funcs' +title: 'Configuration - Methods' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Configuration Management, GetWithEnv, GetWithCmd, MustGetWithCmd, Data Method, Golang Configuration, Environmental Variables, Command Line Acquisition, GoFrame Framework] +description: "Common methods in configuration management using the GoFrame framework, including how to obtain configuration data from environmental variables and command line, the Data method for acquiring and assembling configuration data, and the use of configuration adapters. Through example code, help developers better grasp the technical key points related to configuration management." +--- + +:::tip +The following common method list may lag behind code updates in the documentation. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg) +::: + +## `GetWithEnv` + +- Description + - The `GetWithEnv` method first obtains configuration data from the default configuration file. If the result is empty, it will try to get it from the current environment variables. Note the naming conversion rules: + - Environment variables convert the name to uppercase, and the `.` character in the name is converted to the `_` character. + - Parameter names convert the name to lowercase, and the `_` character in the name is converted to the `.` character. +- Format: + +```go +GetWithEnv(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + +- Example: + +```go +func ExampleConfig_GetWithEnv() { + var ( + key = `env.test` + ctx = gctx.New() + ) + v, err := g.Cfg().GetWithEnv(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("env:%s\n", v) + if err = genv.Set(`ENV_TEST`, "gf"); err != nil { + panic(err) + } + v, err = g.Cfg().GetWithEnv(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("env:%s", v) + + // Output: + // env: + // env:gf +} +``` + +## `GetWithCmd` + +- Description: The `GetWithCmd` method is similar to the `GetWithEnv` method. It first obtains configuration data from the default configuration object, but if the data is empty, it obtains configuration information from the command line. +- Format: + +```go +GetWithCmd(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + +- Example: + +```go +func ExampleConfig_GetWithCmd() { + var ( + key = `cmd.test` + ctx = gctx.New() + ) + v, err := g.Cfg().GetWithCmd(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("cmd:%s\n", v) + // Re-Initialize custom command arguments. + os.Args = append(os.Args, fmt.Sprintf(`--%s=yes`, key)) + gcmd.Init(os.Args...) + // Retrieve the configuration and command option again. + v, err = g.Cfg().GetWithCmd(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("cmd:%s", v) + + // Output: + // cmd: + // cmd:yes +} +``` + +## `MustGetWithCmd` + +- Description: The `MustGetWithCmd` method is similar to the `GetWithCmd` method. It only returns configuration content; if any error occurs internally, a `panic` will occur. +- Format: + +```go +MustGetWithCmd(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleConfig_MustGetWithCmd() { + var ( + key = `cmd.test` + ctx = gctx.New() + ) + v := g.Cfg().MustGetWithCmd(ctx, key) + + fmt.Printf("cmd:%s\n", v) + // Re-Initialize custom command arguments. + os.Args = append(os.Args, fmt.Sprintf(`--%s=yes`, key)) + gcmd.Init(os.Args...) + // Retrieve the configuration and command option again. + v = g.Cfg().MustGetWithCmd(ctx, key) + + fmt.Printf("cmd:%s", v) + + // Output: + // cmd: + // cmd:yes +} +``` + +## `MustGetWithEnv` + +- Description: The `MustGetWithEnv` method is similar to the `GetWithEnv` method. It only returns configuration content; if any error occurs internally, a `panic` will occur. +- Format: + +```go +MustGetWithEnv(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleConfig_MustGetWithEnv() { + var ( + key = `env.test` + ctx = gctx.New() + ) + v := g.Cfg().MustGetWithEnv(ctx, key) + + fmt.Printf("env:%s\n", v) + if err := genv.Set(`ENV_TEST`, "gf"); err != nil { + panic(err) + } + v = g.Cfg().MustGetWithEnv(ctx, key) + + fmt.Printf("env:%s", v) + + // Output: + // env: + // env:gf +} +``` + +## `Data` + +- Description: The `Data` method obtains configuration data from the configuration object and assembles it into the `map[string]interface{}` type. +- Format: + +```go +Data(ctx context.Context) (data map[string]interface{}, err error) +``` + +- Example: + +```go +func ExampleConfig_Data() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data, err := c.Data(ctx) + if err != nil{ + panic(err) + } + + fmt.Println(data) + + // Output: + // map[array:[1 2 3] redis:map[cache:127.0.0.1:6379,1 disk:127.0.0.1:6379,0] v1:1 v2:true v3:off v4:1.23] +} +``` + +## `MustData` + +- Description: The `MustData` method obtains configuration data from the configuration object and assembles it into the `map[string]interface{}` type. It does not return an error if it encounters one internally, but instead directly `panic`s. +- Format: + +```go +MustData(ctx context.Context) map[string]interface{} +``` + +- Example: + +```go +func ExampleConfig_MustData() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data := c.MustData(ctx) + + fmt.Println(data) + + // Output: + // map[array:[1 2 3] redis:map[cache:127.0.0.1:6379,1 disk:127.0.0.1:6379,0] v1:1 v2:true v3:off v4:1.23] +} +``` + +## `Get` + +- Description: The `Get` method obtains configuration data from the configuration object, returning a `gvar` generic object. +- Format: + +```go +Get(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + +- Example: + +```go +func ExampleConfig_Get() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data,err := c.Get(ctx,"redis") + + if err != nil { + panic(err) + } + fmt.Println(data) + + // Output: + // {"cache":"127.0.0.1:6379,1","disk":"127.0.0.1:6379,0"} +} +``` + +## `MustGet` + +- Description: The `MustGet` method is similar to `Get`; it also obtains configuration data from the configuration object, assembling it into a `gvar` structure. It only returns one parameter: `*gvar.Var`. +- Note: If the configuration file does not exist or if there’s another `error`, it will directly `panic`; proper exception handling is needed. +- Format: + +```go +MustGet(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleConfig_MustGet() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data := c.MustGet(ctx,"redis") + + fmt.Println(data) + + // Output: + // {"cache":"127.0.0.1:6379,1","disk":"127.0.0.1:6379,0"} +} +``` + +## `GetAdapter` + +- Description: The `GetAdapter` method retrieves the current running `gcfg` adapter information. For more about adapters, you can click here [Configuration - Interface](./配置管理-接口化设计/配置管理-接口化设计.md) +- Format: + +```go +GetAdapter() Adapter +``` + +- Example: + +```go +func ExampleConfig_GetAdapter() { + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + adapter := c.GetAdapter() + fmt.Println(adapter) + + // Output: + // &{config.toml 0xc00014d720 0xc000371880 false} +} +``` + +## `SetAdapter` + +- Description: The `SetAdapter` method sets the current running `gcfg` adapter information. For more about adapters, you can click here [Configuration - Interface](./配置管理-接口化设计/配置管理-接口化设计.md) +- Format: + +```go +SetAdapter(adapter Adapter) +``` + +- Example: + +```go +func ExampleConfig_SetAdapter() { + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + adapter := c.GetAdapter() + c.SetAdapter(adapter) + fmt.Println(adapter) + + // Output: + // &{config.toml 0xc00014d720 0xc000371880 false} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" new file mode 100644 index 00000000000..2a93e34fe47 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gcfg-interface-adapter-content' +title: 'Configuration - AdapterContent' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Configuration Management, AdapterContent, Configuration Content, Configuration Format, g.Cfg Singleton, gcfg, Configuration Example, Golang Framework] +description: "Use the AdapterContent interface in the GoFrame framework to manage configurations. Users can generate the corresponding Adapter interface object by providing specific configuration content, supporting multiple formats. Example code demonstrates how to use the g.Cfg singleton for file-based configuration management." +--- + +## `AdapterContent` + +`AdapterContent` is an implementation based on configuration content, allowing users to generate an `Adapter` interface object by providing specific configuration content. The configuration content supports multiple formats, which are consistent with the configuration management component. + +## Usage Example + +In most scenarios, we can conveniently use file-based configuration management through the g.Cfg singleton object already encapsulated by the framework. For example: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +const content = ` +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +` + +func main() { + var ctx = gctx.New() + adapter, err := gcfg.NewAdapterContent(content) + if err != nil { + panic(err) + } + config := gcfg.NewWithAdapter(adapter) + fmt.Println(config.MustGet(ctx, "server.address").String()) + fmt.Println(config.MustGet(ctx, "database.default").Map()) +} +``` + +After running, the terminal outputs: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" new file mode 100644 index 00000000000..8da37967c7f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" @@ -0,0 +1,107 @@ +--- +slug: '/docs/core/gcfg-interface-adapter-file' +title: 'Configuration - AdapterFile' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, configuration management, AdapterFile, g.Cfg object, file-based configuration, gcfg.NewWithAdapter, configuration component, configuration loading, Golang] +description: "In the GoFrame framework, configuration management is implemented primarily through AdapterFile for file-based configuration loading and reading. Users can conveniently use configuration management through the g.Cfg singleton object or create configuration management objects via the gcfg.NewWithAdapter method. Example code demonstrates how to implement and execute these configuration operations in Golang." +--- + +## `AdapterFile` + +`AdapterFile` is the default configuration management implementation in the framework, based on file loading and reading. + +## Use through `g.Cfg` Singleton Object + +In most scenarios, we can conveniently use the file-based configuration management implementation through the g.Cfg singleton object already encapsulated by the framework. For example: + +`config.yaml` + +```yaml +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +``` + +`main.go` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(g.Cfg().MustGet(ctx, "server.address").String()) + fmt.Println(g.Cfg().MustGet(ctx, "database.default").Map()) +} +``` + +After running, the terminal output: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` + +## Use via `gcfg.NewWithAdapter` + +We can also create a configuration management object based on a given `Adapter` via the configuration component's `NewWithAdapter` method. Here, we provide an `AdapterFile` interface object. + +`config.yaml` + +```yaml +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +``` + +`main.go` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + adapter, err := gcfg.NewAdapterFile("config") + if err != nil { + panic(err) + } + config := gcfg.NewWithAdapter(adapter) + fmt.Println(config.MustGet(ctx, "server.address").String()) + fmt.Println(config.MustGet(ctx, "database.default").Map()) +} +``` + +After running, the terminal output: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..d44279d32dd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gcfg-interface' +title: 'Configuration - Interface' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, configuration management, interface design, high scalability, configuration retrieval, etcd, zookeeper, consul, kubernetes, apollo] +description: "The interface design and high scalability of the gcfg component in the GoFrame framework. Through interface design, users can customize configuration retrieval methods, including the use of etcd, zookeeper, consul, kubernetes configmap, and apollo, meeting diverse configuration management needs. Detailed interface definition and implementation guidance provide you with flexibility in configuration functionality." +--- + +The `gcfg` component adopts an interface design to achieve high scalability. Through interface design, users can customize configuration retrieval methods, such as `etcd, zookeeper, consul, kubernetes configmap, apollo`, etc. + +## Interface Definition + +[https://github.com/gogf/gf/blob/master/os/gcfg/gcfg\_adaper.go](https://github.com/gogf/gf/blob/master/os/gcfg/gcfg_adaper.go) + +```go +// Adapter is the interface for configuration retrieving. +type Adapter interface { + // Available checks and returns the configuration service is available. + // The optional parameter `resource` specifies certain configuration resource. + // + // It returns true if configuration file is present in default AdapterFile, or else false. + // Note that this function does not return error as it just does simply check for backend configuration service. + Available(ctx context.Context, resource ...string) (ok bool) + + // Get retrieves and returns value by specified `pattern`. + Get(ctx context.Context, pattern string) (value interface{}, err error) + + // Data retrieves and returns all configuration data as map type. + // Note that this function may lead lots of memory usage if configuration data is too large, + // you can implement this function if necessary. + Data(ctx context.Context) (data map[string]interface{}, err error) +} +``` + +## Setting Interface Implementation + +The configuration object can set the currently used interface implementation through the `SetAdapter` method. + +```go +// SetAdapter sets the adapter of current Config object. +func (c *Config) SetAdapter(adapter Adapter) +``` + +## Retrieving Interface Implementation + +The configuration object can retrieve the currently used interface implementation through the `GetAdapter` method. + +```go +// GetAdapter returns the adapter of current Config object. +func (c *Config) GetAdapter() Adapter +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..e4c5f97c630 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" @@ -0,0 +1,212 @@ +--- +slug: '/docs/core/gcfg-file' +title: 'Configuration - File' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, gcfg, configuration management, file configuration, auto detect update, directory configuration, multi-format support, hierarchical access, caching mechanism] +description: "The gcfg component in the GoFrame framework supports multiple configuration file formats such as JSON, XML, YAML, etc., and offers flexible directory configuration and hierarchical access capabilities. It uses an interface-based design, allowing configuration items to be dynamically modified through environment variables and command line parameters. The unique caching mechanism and auto-detect update feature ensure configuration efficiency and real-time capability, making it an ideal choice for configuration management." +--- + +The `gcfg` component uses an interface-based design, with the default implementation based on the file system. Supported data file formats include: `JSON/XML/YAML(YML)/TOML/INI/PROPERTIES`. Developers can flexibly choose their familiar configuration file format for configuration management. + +## Configuration File + +### Default Configuration File + +We recommend using a singleton way to obtain the configuration object. The singleton object will automatically search configuration files according to the file suffix `toml/yaml/yml/json/ini/xml/properties`. By default, it will automatically search and cache configuration files like `config.toml/yaml/yml/json/ini/xml/properties`. When a configuration file is modified externally, it will automatically refresh the cache. + +If you want to customize the file format, you can modify the default configuration file name through the `SetFileName` method (e.g., `default.yaml`, `default.json`, `default.xml`, etc.). For example, we can read the database `database` configuration item from the `default.yaml` file in the following way. + +```go +// Set the default configuration file to default.yaml +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("default.yaml") + +// Subsequent reads will fetch the content from default.yaml +g.Cfg().Get(ctx, "database") +``` + +### Default File Modification + +:::tip +The file can be a specific file name or a complete absolute file path. +::: + +We can modify the default file name in multiple ways: + +1. Modify through the configuration management method `SetFileName`. +2. Modify the command line startup parameter - `gf.gcfg.file`. +3. Modify the specified environment variable - `GF_GCFG_FILE`. + +If our executable file is `main`, we can modify the configuration manager's configuration file directory (on `Linux`) in the following way: + +1. **Through Singleton Pattern** + +```go +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("default.yaml") +``` + +2. **Through Command Line Startup Parameter** + +```shell + ./main --gf.gcfg.file=config.prod.toml +``` + +3. **Through Environment Variable (commonly used in containers)** + - Modify the environment variable at startup: + + ```shell + GF_GCFG_FILE=config.prod.toml; ./main + ``` + + - Use the `genv` module to modify environment variables: + + ```go + genv.Set("GF_GCFG_FILE", "config.prod.toml") + ``` + +## Configuration Directory + +### Directory Configuration Method + +The `gcfg` configuration manager supports a very flexible multi-directory automatic search function. You can modify the directory management directory to a **unique** directory address using `SetPath`. Meanwhile, we recommend adding multiple search directories through the `AddPath` method. The configuration manager will automatically search in the order of the added directories by priority. It will stop when a matching file path is found. If no configuration file is found in all search directories, it will return a failure. + +### Default Directory Configuration + +When the `gcfg` configuration manager initializes, it automatically adds the following configuration file search directories: + +1. **The current working directory and its `config`, `manifest/config` directories**: For example, when the current working directory is `/home/www`, it will add: + 1. `/home/www` + 2. `/home/www/config` + 3. `/home/www/manifest/config` +2. **The directory where the current executable file is located and its `config`, `manifest/config` directories**: For example, when the binary file is located at `/tmp`, it will add: + 1. `/tmp` + 2. `/tmp/config` + 3. `/tmp/manifest/config` +3. **The directory where the current `main` source code package is located and its `config`, `manifest/config` directories** (valid for source code development environment only): For example, when the `main` package is located at `/home/john/workspace/gf-app`, it will add: + 1. `/home/john/workspace/gf-app` + 2. `/home/john/workspace/gf-app/config` + 3. `/home/john/workspace/gf-app/manifest/config` + +### Default Directory Modification + +:::warning +Note that the parameter modified here must be a directory, not a file path. +::: + +We can modify the configuration file search directory of the configuration manager in the following way. The configuration management object will only perform configuration file searches in the specified directory: + +1. Manually modify it through the configuration manager's `SetPath` method; +2. Modify the command line startup parameter - `gf.gcfg.path`; +3. Modify the specified environment variable - `GF_GCFG_PATH`; + +If our executable file is `main`, we can modify the configuration manager's configuration file directory (under Linux) in the following way: + +1. **Through Singleton Pattern** + +```go +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetPath("/opt/config") +``` + +2. **Through Command Line Startup Parameter** + +```shell + ./main --gf.gcfg.path=/opt/config/ +``` + +3. **Through Environment Variable (commonly used in containers)** + - Modify the environment variable at startup: + + ```shell + GF_GCFG_PATH=/opt/config/; ./main + ``` + + - Use the `genv` module to modify environment variables: + + ```go + genv.Set("GF_GCFG_PATH", "/opt/config") + ``` + +## Content Configuration + +The `gcfg` package also supports direct content configuration rather than reading configuration files, which is commonly used for dynamically modifying configuration content internally in programs. Implement global configuration through the package configuration methods below: + +```go +func (c *AdapterFile) SetContent(content string, file ...string) +func (c *AdapterFile) GetContent(file ...string) string +func (c *AdapterFile) RemoveContent(file ...string) +func (c *AdapterFile) ClearContent() +``` + +It is important to note that this configuration takes effect globally and has a higher priority than reading from configuration files. Therefore, if we configure the content of `config.toml` through `SetContent("v = 1", "config.toml")` and a `config.toml` file also exists, only the content configured by `SetContent` will be used, and the file content will be ignored. + +## Hierarchical Access + +With the default file system interface implementation, the `gcfg` component supports accessing configuration data by hierarchy. Hierarchical access is specified by the English `.` by default, where the `pattern` parameter is consistent with the `pattern` parameter of [General Codec](../../组件列表/编码解码/通用编解码-gjson/通用编解码-gjson.md). For example, the following configuration (`config.yaml`): + +```yaml +server: + address: ":8199" + serverRoot: "resource/public" + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/focus" + debug: true +``` + +For example, hierarchical reading of the above configuration file content: + +```go +// :8199 +g.Cfg().Get(ctx, "server.address") + +// true +g.Cfg().Get(ctx, "database.default.debug") +``` + +## Precautions + +As everyone knows, in `Golang`, types like `map/slice` are actually "reference types" (also called "pointer types"). Therefore, when you modify a key-value pair/index item of this type, it will also modify the corresponding underlying data. From the perspective of efficiency, when some `gcfg` retrieval methods return data types as `map/slice`, they do not perform a value copy. Therefore, when you modify the returned data, it will also modify the corresponding underlying data of `gcfg`. + +For example: + +Configuration file: + +```go +// config.json: +`{"map":{"key":"value"}, "slice":[59,90]}` +``` + +Example code: + +```go +var ctx = gctx.New() + +m := g.Cfg().MustGet(ctx, "map").Map() +fmt.Println(m) + +// Change the key-value pair. +m["key"] = "john" + +// It changes the underlying key-value pair. +fmt.Println(g.Cfg().MustGet(ctx, "map").Map()) + +s := g.Cfg().MustGet(ctx, "slice").Slice() +fmt.Println(s) + +// Change the value of specified index. +s[0] = 100 + +// It changes the underlying slice. +fmt.Println(g.Cfg().MustGet(ctx, "slice").Slice()) + +// output: +// map[key:value] +// map[key:john] +// [59 90] +// [100 90] +``` + +## Detecting Updates + +The configuration manager uses a **caching mechanism**. When a configuration file is read for the first time, it is cached in memory. The next read will directly fetch it from the cache to improve performance. Meanwhile, the configuration manager provides an **auto-detect update mechanism**. When the configuration file is modified externally, the configuration manager can immediately refresh the cached content of the configuration file. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..0ce4d730946 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" @@ -0,0 +1,81 @@ +--- +slug: '/docs/core/gcfg-cfg' +title: 'Configuration - Object' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Configuration Management, Global Configuration, Singleton Pattern, g.Cfg, gcfg.Instance, MySQL Database, Configuration Reading, Auto Search, Template Engine] +description: "Use the GoFrame framework for configuration management, acquiring configuration objects through the singleton pattern. With g.Cfg() and gcfg.Instance methods, it's easy to read global configurations like database connection info and template engine directory settings. Supports auto-searching and caching configuration files by file extension to enhance development efficiency." +--- + +We recommend using the singleton pattern to obtain the configuration management object. We can conveniently get the default global configuration management object through `g.Cfg()`. Additionally, we can get the configuration management object singleton through the `gcfg.Instance` package method. + +### Using `g.Cfg` + +Let's look at an example demonstrating how to read global configuration information. It's important to note that the global configuration is framework-related, so it is consistently obtained using `g.Cfg()`. Below is a default global configuration file, which includes the directory configuration for the template engine and the configuration for a `MySQL` database cluster (two `master` servers). + +Configuration example: + +```yaml +viewpath: "/home/www/templates/" +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "slave" +``` + +Code example: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(g.Cfg().Get(ctx, "viewpath")) + fmt.Println(g.Cfg().Get(ctx, "database.default.0.role")) +} +``` + +The above example reads the `role` information of the first database configuration. After running, the output is: + +``` +/home/www/templates/ +master +``` + +As you can see, we can obtain a global configuration manager singleton object using the `g.Cfg()` method. The configuration file contents can be accessed hierarchically using a `.` (dot) in the pattern (arrays start at `0` by default). The pattern parameter `database.default.0.role` refers to reading the `role` data of the 0th database server in the `default` database cluster within the `database` configuration item. + +### Using `gcfg.Instance` + +Of course, you can also independently use the `gcfg` package to get a singleton object through the `Instance` method. + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(gcfg.Instance().Get(ctx, "viewpath")) + fmt.Println(gcfg.Instance().Get(ctx, "database.default.0.role")) +} +``` + +### Auto Search Feature + +When a singleton object is created, it will automatically search for configuration files based on file extensions `toml/yaml/yml/json/ini/xml/properties`. By default, it will automatically search and cache configuration files like `config.toml/yaml/yml/json/ini/xml/properties`. When a configuration file is modified externally, the cache will automatically refresh. + +To facilitate calls to configuration files in multi-file scenarios, simplify usage, and enhance development efficiency, the singleton object will automatically use the **singleton name** for file searching upon creation. For instance, the singleton object obtained with `g.Cfg("redis")` will automatically search for `redis.toml/yaml/yml/json/ini/xml/properties`. If the search is successful, the file will be loaded into memory cache, and the next time, it will be read directly from memory. If the file does not exist, the default configuration file (`config.toml`) is used. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..3bb8736aec3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gcfg' +title: 'Configuration' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gcfg,Configuration Management,File System Interface,Configuration File Format,Environment Variables,Command Line Arguments,Hot Update Feature,Singleton Management Model] +description: "The configuration management component gcfg of the GoFrame framework, which supports concurrent safe operations, provides a file system interface implementation, supports various configuration file formats such as yaml, toml, json, etc., and allows reading environment variables or command line arguments when configuration items are missing, with features such as automatic detection of configuration file hot updates and singleton management." +--- + +## Introduction + +`GoFrame`'s configuration management is implemented by the `gcfg` component, and all methods of the `gcfg` component are concurrent safe. The `gcfg` component adopts an interface-based design and provides a file system-based interface implementation by default. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gcfg" +``` + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg) + +## Features + +The `gcfg` component has the following notable features: + +- Interface-based design, providing high flexibility and extensibility, with a default file system interface implementation +- Supports various common configuration file formats: `yaml/toml/json/xml/ini/properties` +- Supports reading specified environment variables or command line arguments when configuration items do not exist +- Supports retrieval and reading of configuration files from the resource management component +- Supports automatic detection of hot update features for configuration files +- Supports hierarchical access to configuration items +- Supports a singleton management model + +## Precautions + +The framework's configuration component supports a variety of common data formats, but the subsequent example code will be demonstrated using the `yaml` data format. In use, feel free to use the data format of your choice **without being restricted to the `yaml` data format used in the official examples**. For example, if the business project template provides a `config.yaml` configuration file template (because the default template can only provide one), you can also directly modify it to `config.toml` or `config.ini` and other supported data formats, **the configuration component can also automatically recognize and read based on the file name suffix**. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..812e999b351 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" @@ -0,0 +1,217 @@ +--- +slug: '/docs/core/gerror-other' +title: 'Error Handling - Other Features' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, Error Handling, NewOption, fmt formatting, Log Output, Stack Printing, Error Component, Code Call Chain, Stack Information, Environment Variable] +description: "In GoFrame framework, error handling other features include how to use NewOption to customize error object creation, print complete stack information through fmt formatting, and powerful support for log output. Additionally, from version v2.6.0, the error component offers different printing modes for stack information, meeting development needs in different scenarios." +--- + +## `NewWithOption` Custom Error Creation + +- Description: Used for creating custom-configured error objects. +- Format: + + ```go + // NewWithOption creates and returns a custom error with Option. + // It is the senior usage for creating error, which is often used internally in framework. + NewWithOption(option Option) error + ``` + +- Example: + + ```go + func ExampleNewWithOption() { + err := gerror.NewWithOption(gerror.Option{ + Text: "this feature is disabled in this storage", + Code: gcode.CodeNotSupported, + }) + } + ``` + Where `Option` is defined as: + ```go + // Option is option for creating error. + type Option struct { + Error error // Wrapped error if any. + Stack bool // Whether recording stack information into error. + Text string // Error text, which is created by New* functions. + Code gcode.Code // Error code if necessary. + } + ``` + +## `fmt` Formatting + +As shown in previous examples, using the `%+v` print format can print out complete stack information. Of course, the `gerror.Error` object supports multiple `fmt` formats: + +| Format Specifier | Output Content | +| --- | --- | +| `%v`, `%s` | Prints all hierarchical error information, returning a complete string, with multiple levels concatenated by `:`. | +| `%-v`, `%-s` | Prints the current level of error information, returning a string. | +| `%+s` | Prints a complete list of stack information. | +| `%+v` | Prints all hierarchical error information strings, along with complete stack information, equivalent to `%s\n%+s`. | + +Usage example: + +```go +package main + +import ( + "errors" + "fmt" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Printf(" %%s: %s\n", err) + fmt.Printf("%%-s: %-s\n", err) + fmt.Println("%+s: ") + fmt.Printf("%+s\n", err) +} +``` + +After execution, the output example: +```text + %s: api calling failed: adding failed: sql error +%-s: api calling failed +%+s: +1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 +2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 +3. sql error +``` + +## Log Component Support + +The `glog` log component naturally supports stack printing for `gerror` errors. This support is not strongly coupled but is supported through the `fmt` formatting print interface. + +Usage example: + +```go +package main + +import ( + "errors" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + g.Log().Printf("%+v", err) +} +``` + +After execution, the output example: +```text +2020-10-17 15:22:26.793 api calling failed: adding failed: sql error +1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 +2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 +3. sql error +``` + +## Stack Printing Mode + +This feature is provided from version `v2.6.0` of the framework. + +When printing stack information, the error component supports users specifying stack printing information mode via an environment variable (`GF_GERROR_STACK_MODE`) or a command line argument (`gf.gerror.stack.mode`): + +| Stack Mode | Default | Description | +| --- | --- | --- | +| `brief` | Yes | **Brief Mode**. When printing error stacks, framework-related stacks will not be printed. | +| `detail` | | **Detailed Mode**. When printing error stacks, the complete framework component code call chain will be printed. | + +**In detailed mode (`detail`), the complete framework stack information will be printed in the error object.** For instance, in the following stack error example, you can see that most of the framework stack information is not very meaningful for error localization, and as a developer, more concern is given to the stack information at their own code level. + +```text +2022-10-08 21:07:00.751 [ERRO] {328d1204e2191c179a09086890c857b8} request done, cost: 3 ms, code: -1, message: "", detail: , error: GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0}: rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +1. GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0} + 1). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).doGetParamsJson + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:66 + 2). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).GetParams + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:36 + 3). github.com/khaos/eros/app/khaos-oss/internal/controller.(*cParams).GetOne + /root/workspace/khaos/eros/app/khaos-oss/internal/controller/params.go:21 + 4). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:152 + 5). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 6). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:129 + 7). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:75 + 8). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 9). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 10). github.com/khaos/eros/app/khaos-oss/internal/logic/middleware.(*sMiddleware).CheckLimit + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/middleware/middleware.go:27 + 11). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 12). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 13). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 14). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 15). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 16). github.com/khaos/eros/utility/server.MiddlewareCommonResponse + /root/workspace/khaos/eros/utility/server/server_common.go:14 + 17). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 18). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 19). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 20). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 21). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 22). github.com/khaos/eros/utility/server.MiddlewareLogging + /root/workspace/khaos/eros/utility/server/server.go:46 + 23). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 24). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 25). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 26). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 27). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 28). github.com/gogf/gf/v2/net/ghttp.MiddlewareCORS + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_middleware_cors.go:12 + 29). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 30). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 +2. rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +``` + +While **in brief mode (`brief`), the stack information will not print the framework stack information**. For example: + +```text +2022-10-08 21:07:00.751 [ERRO] {328d1204e2191c179a09086890c857b8} request done, cost: 3 ms, code: -1, message: "", detail: , error: GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0}: rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +1. GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0} + 1). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).doGetParamsJson + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:66 + 2). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).GetParams + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:36 + 3). github.com/khaos/eros/app/khaos-oss/internal/controller.(*cParams).GetOne + /root/workspace/khaos/eros/app/khaos-oss/internal/controller/params.go:21 +2. rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..61e9760a5ac --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" @@ -0,0 +1,256 @@ +--- +slug: '/docs/core/gerror-stack' +title: 'Error Handling - Stack' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, error handling, stack features, gerror, Go language, error tracing, stack recording, Wrap method, error information, call chain] +description: "Error handling stack features in the GoFrame framework, implemented through the gerror module to trace error stacks and enhance the readability and maintainability of error information. The article explains in detail how to use the Wrap method to layer error information, the HasStack method to check error stacks, the Stack method to get stack information, and methods to access error information at different levels." +--- +## Error Stack + +The `error` implementation in the standard library is relatively simple and does not support stack tracing, which is not very user-friendly for the caller when an error occurs, as it cannot obtain detailed information about the error call chain. `gerror` supports error stack recording, and methods like `New/Newf`, `Wrap/Wrapf`, `WrapSkip/WrapSkipf` automatically record the stack information at the time the error occurs. + +Example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/errors/gerror" +) + +func OpenFile() error { + return gerror.New("permission denied") +} + +func OpenConfig() error { + return gerror.Wrap(OpenFile(), "configuration file opening failed") +} + +func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") +} + +func main() { + fmt.Printf("%+v", ReadConfig()) +} +``` + +Execution output example: +```text +reading configuration failed: configuration file opening failed: permission denied +1. reading configuration failed + 1). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 2). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +2. configuration file opening failed + 1). main.OpenConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:14 + 2). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 3). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +3. permission denied + 1). main.OpenFile + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:10 + 2). main.OpenConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:14 + 3). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 4). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +``` + +:::tip +The error stack path printed is the code path executed locally by the author. +::: + +As you can see, the caller can use the `Wrap` method to layer the underlying error information while including complete error stack information. + +## `HasStack` Determine Whether an Error Includes a Stack + +- Description: The `HasStack` method allows us to determine whether a given `error` interface object implements (contains) stack information. +- Format: + + ```go + // HasStack checks and reports whether `err` implemented interface `gerror.IStack`. + HasStack(err error) bool + ``` + +- Example: + + ```go + func ExampleHasStack() { + err1 := errors.New("sql error") + err2 := gerror.New("write error") + fmt.Println(gerror.HasStack(err1)) + fmt.Println(gerror.HasStack(err2)) + + // Output: + // false + // true + } + ``` + +:::warning +Currently, this method is implemented by checking if the error object implements the `gerror.IStack` interface. If the error object does not implement this interface, it cannot determine if it includes stack information. +::: + +## `Stack` Get Stack Information + +- Description: The `Stack` method allows us to obtain the complete stack information of an `error` object, returning a stack list string. +- Format: + + ```go + // Stack returns the stack callers as string. + // It returns the error string directly if the `err` does not support stacks. + Stack(err error) string + ``` + +- Example: + + ```go + func ExampleStack() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Println(gerror.Stack(err)) + } + ``` + Execution example output: + ```go + 1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 + 2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 + 3. sql error + ``` + +:::warning +Currently, this method is implemented by retrieving the error object's implementation of the `gerror.IStack` interface. If the error object does not implement this interface, this method will return an empty string. +::: + +## `Current` Get the Current `error` + +- Description: The `Current` method is used to get the error information of the current level, returning through the `error` interface object. +- Format: + + ```go + // Current creates and returns the current level error. + // It returns nil if current level error is nil. + Current(err error) error + ``` + +- Example: + + ```go + func ExampleCurrent() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Println(err) + fmt.Println(gerror.Current(err)) + + // Output: + // api calling failed: adding failed: sql error + // api calling failed + } + ``` + + +## `Unwrap` Get the Next Level `error` + +- Description: The `Unwrap` method is used to get the next level error `error` interface object. If the next level does not exist, it returns `nil`. +- Format: + + ```go + // Unwrap returns the next level error. + // It returns nil if current level error or the next level error is nil. + Unwrap(err error) error + ``` + +- Example 1: Simple error hierarchy access example. + + ```go + func ExampleUnwrap() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + + fmt.Println(err) + + err = gerror.Unwrap(err) + fmt.Println(err) + + err = gerror.Unwrap(err) + fmt.Println(err) + + // Output: + // api calling failed: adding failed: sql error + // adding failed: sql error + // sql error + } + ``` + +- Example 2: Common iteration logic code example. + + ```go + func IsGrpcErrorNotFound(err error) bool { + if err != nil { + for e := err; e != nil; e = gerror.Unwrap(e) { + if s, ok := status.FromError(e); ok && s != nil && s.Code() == codes.NotFound { + return true + } + } + } + return false + } + ``` + + +## `Cause` Get the Root Error `error` + +- Description: The `Cause` method allows us to obtain the root error information (original error) of the `error` object. +- Format: + + ```go + // Cause returns the root cause error of `err`. + Cause(err error) error + ``` + +- Example: + + ```go + package main + + import ( + "fmt" + "github.com/gogf/gf/v2/errors/gerror" + ) + + func OpenFile() error { + return gerror.New("permission denied") + } + + func OpenConfig() error { + return gerror.Wrap(OpenFile(), "configuration file opening failed") + } + + func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") + } + + func main() { + fmt.Println(gerror.Cause(ReadConfig())) + } + + // Output: + // permission denied + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..cfd15e16588 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gerror-funcs' +title: 'Error Handling - Methods' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Error Handling,Error Creation,New,Wrap,NewSkip,Error Code,gerror,Stack Information] +description: "Common methods for error handling in the GoFrame framework, including error creation, error wrapping, and functions related to error codes, providing multiple ways to create and wrap custom error messages to help developers effectively manage errors and debug code when using the GoFrame framework." +--- + +This chapter only introduces some commonly used methods. For a complete list of error methods, please refer to the interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror) + + +## `New/Newf` + +- Description: Used to create a custom error message `error` object, including stack information. +- Format: + + ```go + // New creates and returns an error which is formatted from given text. + New(text string) error + + // Newf returns an error that formats as the given format and args. + Newf(format string, args ...interface{}) error + ``` + + +## `Wrap/Wrapf` + +- Description: Used to wrap other `error` objects, constructing multi-level error messages, including stack information. +- Format: + + ```go + // Wrap wraps error with text. It returns nil if given err is nil. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func Wrap(err error, text string) error + + // Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier. + // It returns nil if given `err` is nil. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func Wrapf(err error, format string, args ...interface{}) error + ``` + + +## `NewSkip/NewSkipf` + +- Description: Used to create a custom error message `error` object, and ignore part of the stack information (ignoring upwards from the current method call location). Advanced functionality, rarely used by general developers. +- Format: + + ```go + // NewSkip creates and returns an error which is formatted from given text. + // The parameter `skip` specifies the stack callers skipped amount. + func NewSkip(skip int, text string) error + + // NewSkipf returns an error that formats as the given format and args. + // The parameter `skip` specifies the stack callers skipped amount. + func NewSkipf(skip int, format string, args ...interface{}) error + ``` + +## `WrapSkip/WrapSkipf` + +- Description: Similar to the `Wrap/Wrapf` methods, but ignores part of the stack information. +- Format: + + ```go + // WrapSkip wraps error with text. It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func WrapSkip(skip int, err error, text string) error + + // WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func WrapSkipf(skip int, err error, format string, args ...interface{}) error + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..947121533fd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/core/gerror-benchmark' +title: 'Error Handling - Performance' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame framework, error handling, performance testing, benchmark testing, performance optimization, Go language, programming framework, code efficiency, Go development] +description: "Covers benchmark performance testing for error handling using the GoFrame framework, providing performance comparison results of commonly used methods. These performance tests allow developers to better understand and optimize error handling efficiency in Go programming, thereby enhancing the overall performance of applications. Data is sourced from the open-source GoFrame project on GitHub, listing detailed performance results of different error handling methods under various configurations for developers' reference." +--- + +Benchmark performance tests of commonly used methods: [https://github.com/gogf/gf/blob/master/errors/gerror/gerror\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/errors/gerror/gerror_z_bench_test.go) + +```bash +$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +Benchmark_New-4 1890454 589 ns/op 256 B/op 1 allocs/op +Benchmark_Newf-4 1569258 762 ns/op 324 B/op 3 allocs/op +Benchmark_Wrap-4 2012910 600 ns/op 256 B/op 1 allocs/op +Benchmark_Wrapf-4 1600197 749 ns/op 324 B/op 3 allocs/op +Benchmark_NewSkip-4 2009928 579 ns/op 256 B/op 1 allocs/op +Benchmark_NewSkipf-4 1578370 752 ns/op 324 B/op 3 allocs/op +Benchmark_NewCode-4 2060835 591 ns/op 256 B/op 1 allocs/op +Benchmark_NewCodef-4 1603306 752 ns/op 324 B/op 3 allocs/op +Benchmark_NewCodeSkip-4 2047794 584 ns/op 256 B/op 1 allocs/op +Benchmark_NewCodeSkipf-4 1524495 779 ns/op 324 B/op 3 allocs/op +Benchmark_WrapCode-4 1972147 603 ns/op 256 B/op 1 allocs/op +Benchmark_WrapCodef-4 1651316 735 ns/op 324 B/op 3 allocs/op +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..c4477313494 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,116 @@ +--- +slug: '/docs/core/gerror-practice' +title: 'Error Handling - Best Practices' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Error Handling, Best Practices, Error Stack, Wrap Method, Error Object, Golang, Print Error, Error Message] +description: "Effectively handle errors in the GoFrame framework, including printing stack information of error objects and correctly using the Wrap method to encapsulate error objects, avoiding duplication of error messages. Through specific code examples, demonstrate how to optimize error handling and improve the stability of GoFrame framework applications." +--- + +## Print Stack Information in Error Objects + +When printing error objects via `fmt/glog` or other packages, stack information is not output by default. For example: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" +) + +func main() { + _, err := gjson.Encode(func() {}) + fmt.Printf("err: %v", err) +} +``` + +After execution, the terminal output: + +```html +err: json.Marshal failed: json: unsupported type: func() +``` + +To print stack information from an error object, use the `%+v` formatting option. For example: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" +) + +func main() { + _, err := gjson.Encode(func() {}) + fmt.Printf("err: %+v", err) +} +``` + +After execution, the terminal output: + +```text +err: json.Marshal failed: json: unsupported type: func() +1. json.Marshal failed + 1). github.com/gogf/gf/v2/internal/json.Marshal + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/internal/json/json.go:30 + 2). github.com/gogf/gf/v2/encoding/gjson.Encode + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/encoding/gjson/gjson_stdlib_json_util.go:41 + 3). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/main.go:10 +2. json: unsupported type: func() +``` + +## Avoid Repeated Error Message Printing When Using the Wrap Method + +Do not print the error object into the error message when `Wrap`ping an error object, as `Wrap` inherently encapsulates the target error object within the newly created error object. Including the error again in the error string can cause duplications when printing the error stack. For instance (the stack information is not printed for simplification): + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + _, err1 := gjson.Encode(func() {}) + err2 := gerror.Wrapf(err1, `error occurred: %v`, err1) + fmt.Printf("err: %v", err2) +} +``` + +After execution, the terminal output shows duplicate error messages: + +```text +err: error occurred: json.Marshal failed: json: unsupported type: func(): json.Marshal failed: json: unsupported type: func() +``` + +Let's fix the above code example as shown below: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + _, err1 := gjson.Encode(func() {}) + err2 := gerror.Wrap(err1, `error occurred`) + fmt.Printf("err: %v", err2) +} +``` + +After execution, the terminal output: + +```text +err: error occurred: json.Marshal failed: json: unsupported type: func() +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" new file mode 100644 index 00000000000..8b29e739981 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" @@ -0,0 +1,80 @@ +--- +slug: '/docs/core/gerror-comparison' +title: 'Error Handling - Comparison' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Error Handling, Error Comparison, Equal Method, Is Method, Error Object, Interface Definition, Error Component, Standard Library] +description: "Comparison methods in error handling within the GoFrame framework, including the usage of Equal and Is methods. The Equal method is used to determine if two error objects are the same, while the Is method is used to determine if a given error is in a specified error chain. Interface definitions and usage examples are provided to help developers better understand and apply these methods." +--- + +## `Equal` Comparison Method + +Error objects support comparison, and the `Equal` method is used to fully determine whether two `errors` are the same. This is mainly achieved through the following method: + +```go +// Equal reports whether current error `err` equals to error `target`. +// Please note that, in default comparison for `Error`, +// the errors are considered the same if both the `code` and `text` of them are the same. +func Equal(err, target error) bool +``` + +### Interface Definition + +If a custom error data structure needs to support comparison, the custom error structure needs to implement the following interface: + +```go +// IEqual is the interface for Equal feature. +type IEqual interface { + Error() string + Equal(target error) bool +} +``` + +Error objects created by the `GoFrame` framework's error component already implement this interface, and the component's default comparison logic checks the error code and error message. +:::info +Note that if both errors do not carry error codes and their error messages are the same, the component considers the two errors to be the same. +::: +### Usage Example + +```go +func ExampleEqual() { + err1 := errors.New("permission denied") + err2 := gerror.New("permission denied") + err3 := gerror.NewCode(gcode.CodeNotAuthorized, "permission denied") + fmt.Println(gerror.Equal(err1, err2)) + fmt.Println(gerror.Equal(err2, err3)) + + // Output: + // true + // false +} +``` + +## `Is` Inclusion Judgment + +Error objects support **inclusion** judgment. The `Is` method is used to determine whether the given `error` is in a specified `error` chain (if the `error` carries a stack, it will be judged recursively). This is mainly achieved through the following method: + +```go +// Is reports whether current error `err` has error `target` in its chaining errors. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func Is(err, target error) bool +``` + +Usage Example + +```go +func ExampleIs() { + err1 := errors.New("permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.Is(err1, err1)) + fmt.Println(gerror.Is(err2, err2)) + fmt.Println(gerror.Is(err2, err1)) + fmt.Println(gerror.Is(err1, err2)) + + // Output: + // false + // true + // true + // false +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" new file mode 100644 index 00000000000..bea59d897ac --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" @@ -0,0 +1,16 @@ +--- +slug: '/docs/core/gerror-code-builtin' +title: 'Error Code - Built-in Codes' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame framework, error handling, built-in error codes, error code definition, business error codes, gcode, GoFrame, framework reserved, error code usage, integer error codes] +description: "Common built-in error codes provided by the GoFrame framework for developers to use directly. Note that business-used integer error codes should be defined greater than 1000 to avoid conflict with framework-reserved error codes. A link to the error code definition file is provided to help developers better handle and use error codes." +--- + +The framework predefines some common error codes that developers can use directly: + +[https://github.com/gogf/gf/blob/master/errors/gcode/gcode.go](https://github.com/gogf/gf/blob/master/errors/gcode/gcode.go) + +:::warning +Please note, if business uses integer error codes, `<1000` error codes are reserved by the framework, and business error codes should be defined as `>1000`. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" new file mode 100644 index 00000000000..1ef519fd465 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" @@ -0,0 +1,96 @@ +--- +slug: '/docs/core/gerror-code-custom' +title: 'Error Code - Implementation' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, error handling, error code, custom error code, gcode, business error code, HttpCode, middleware, code example] +description: "Custom implementation of business error codes in the GoFrame framework. By implementing the gcode.Code interface, complex error codes can be defined; code examples for error handling are provided, showing how to use custom error codes in conjunction with middleware." +--- + +When the business requires more complex error code definitions, we can customize the implementation of business error codes by simply implementing the `gcode.Code` related interface. + +Let's look at an example. + +## Custom Error Codes + +Define the basic business error code structure and implement the `gcode.code` interface. + +```go +type BizCode struct { + code int + message string + detail BizCodeDetail +} +type BizCodeDetail struct { + Code string + HttpCode int +} + +func (c BizCode) BizDetail() BizCodeDetail { + return c.detail +} + +func (c BizCode) Code() int { + return c.code +} + +func (c BizCode) Message() string { + return c.message +} + +func (c BizCode) Detail() interface{} { + return c.detail +} + +func New(httpCode int, code string, message string) gcode.Code { + return BizCode{ + code: 0, + message: message, + detail: BizCodeDetail{ + Code: code, + HttpCode: httpCode, + }, + } +} +``` + +Define business error codes + +```go +var ( + CodeNil = New(200, "OK", "") + CodeNotFound = New(404, "Not Found", "Resource does not exist") + CodeInternal = New(500, "Internal Error", "An error occurred internally") + // ... +) +``` + +## Usage in Middleware + +```go +func ResponseHandler(r *ghttp.Request) { + r.Middleware.Next() + // There's custom buffer content, it then exits current handler. + if r.Response.BufferLength() > 0 { + return + } + var ( + err = r.GetError() + res = r.GetHandlerResponse() + code = gerror.Code(err) + ) + if code == gcode.CodeNil && err != nil { + code = CodeInternal + } else { + code = CodeNil + } + if bizCode, ok := code.(BizCode); ok { + r.Response.WriteStatus(bizCode.BizDetail().HttpCode) + } + r.Response.WriteJson(g.Map{ + `code`: gcode.CodeOK.Code(), + `message`: gcode.CodeOK.Message(), + `data`: res, + }) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" new file mode 100644 index 00000000000..765856f325d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/core/gerror-code-extension' +title: 'Error Code - Extension' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Error Handling, Error Code Extension, Business Logic, Middleware, Custom Extension, Error Code Definition, WithCode Method, gcode] +description: "Error handling in the GoFrame framework, especially the extension of error codes. By using the Detail parameter of error codes, error codes can be customized according to business needs. In specific scenarios, we can flexibly create new error codes using the WithCode method and apply and handle them in middleware. This solution not only enhances the flexibility of business logic but also provides detailed error information for upper layers." +--- + +When business requirements call for complex error code definitions, it is recommended to flexibly use the `Detail` parameter of error codes for extending error code functionality. + +Let's look at an example that primarily implements the following features: +- Use middleware to identify the execution results of route functions and obtain the error code information returned by these functions, returning the error code information as part of the response to the caller. +- If an error occurs, record the current user information causing the error into the logs. This is achieved using the `Detail` extension parameter. + +## Business Error Codes + +### Error Code Definition + +```go +type BizCode struct { + User User + // ... +} + +type User struct { + Id int + Name string + // ... +} +``` + +### Using Error Codes + +In most scenarios of error code extension, the `gcode.WithCode` method is required: + +```go +// WithCode creates and returns a new error code based on given Code. +// The code and message is from given `code`, but the detail if from given `detail`. +func WithCode(code Code, detail interface{}) Code +``` + +Thus, our custom extension can be used as follows: + +```go +code := gcode.WithCode(gcode.CodeNotFound, BizCode{ + User: User{ + Id: 1, + Name: "John", + }, +}) +fmt.Println(code) +``` + +That is, in error codes, we can inject some customized error code extension data according to business scenarios to facilitate further processing by the upper layer. + +## Modifying Middleware + +We will apply the custom error codes above in a request return middleware, allowing the top-layer business logic to obtain the details corresponding to the error code for further business processing. The data structure returned by the middleware can also be customized, such as the `code` field which may not necessarily be an integer, and can be fully customized. + +```go +func ResponseHandler(r *ghttp.Request) { + r.Middleware.Next() + // There's custom buffer content, it then exits current handler. + if r.Response.BufferLength() > 0 { + return + } + var ( + err = r.GetError() + res = r.GetHandlerResponse() + code = gerror.Code(err) + ) + if code == gcode.CodeNil && err != nil { + code = gcode.CodeInternalError + } + if detail, ok := code.Detail().(BizCode); ok { + g.Log().Errorf(r.Context(), `error caused by user "%+v"`, detail.User) + } + r.Response.WriteJson(ghttp.DefaultHandlerResponse{ + Code: gcode.CodeOK.Code(), + Message: gcode.CodeOK.Message(), + Data: res, + }) +} +``` +:::tip +The `Detail` data is automatically printed in the default logs of the `Server` framework. +::: \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..f82537dd804 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/core/gerror-code-interface' +title: 'Error Code - Interface' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gcode, Error Handling, Error Code Interface, Interface Definition, High Scalability, Error Code Component, Custom Error Code, Interface Design] +description: "The error code interface in the GoFrame framework is used for error handling, primarily describing the interface design and high scalability of the error code component gcode. By implementing the Code interface, developers can customize error codes. The framework provides a default implementation, but developers can also extend and implement their own error code logic as needed." +--- + +## Introduction + +Previously, we discussed the usage of the `gerror` component, which is mainly used for error management. In subsequent chapters, we will primarily explain the use of the `gcode` component, which is mainly used to extend the capabilities of the `gerror` component by providing error code features. The `gcode` error code component uses an interface design to offer high scalability. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/errors/gcode" +``` + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/errors/gcode](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gcode) + + +## Interface Definition + +```go +// Code is universal error code interface definition. +type Code interface { + // Code returns the integer number of current error code. + Code() int + + // Message returns the brief message for current error code. + Message() string + + // Detail returns the detailed information of current error code, + // which is mainly designed as an extension field for error code. + Detail() interface{} +} +``` + +## Default Implementation + +The framework provides a default implementation structure for `gcode.Code`. Developers can directly use the `gcode` component's `New/WithCode` methods to create error codes: + +- Format: + + ```go + // New creates and returns an error code. + // Note that it returns an interface object of Code. + func New(code int, message string, detail interface{}) Code + ``` + +- Example: + + ```go + func ExampleNew() { + c := gcode.New(1, "custom error", "detailed description") + fmt.Println(c.Code()) + fmt.Println(c.Message()) + fmt.Println(c.Detail()) + + // Output: + // 1 + // custom error + // detailed description + } + ``` + +If developers find that the framework's default implementation structure for `gcode.Code` does not meet their needs, they can define their own implementation as long as they implement `gcode.Code`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..029b8e91f65 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" @@ -0,0 +1,171 @@ +--- +slug: '/docs/core/gerror-code-example' +title: 'Error Code - Example' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Error Handling, Error Code, Stack Information, NewCode, WrapCode, error, gerror, gcode] +description: "In the GoFrame framework, the NewCode and WrapCode methods are used for error handling, allowing the creation and wrapping of error objects with custom error codes and stack information. This document provides example code based on the GoFrame framework to help developers better understand the application of error codes in programs." +--- + +## Create an `error` with Error Code + +### `NewCode/NewCodef` + +- Description: It functions the same as the `New/Newf` method, used to create an `error` object with custom error information, includes stack information, and adds an error code object as input. +- Format: + + ```go + // NewCode creates and returns an error that has error code and given text. + NewCode(code gcode.Code, text ...string) error + + // NewCodef returns an error that has error code and formats as the given format and args. + NewCodef(code gcode.Code, format string, args ...interface{}) error + ``` + +- Examples: + + ```go + func ExampleNewCode() { + err := gerror.NewCode(gcode.New(10000, "", nil), "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err)) + + // Output: + // My Error + // 10000 + } + + func ExampleNewCodef() { + err := gerror.NewCodef(gcode.New(10000, "", nil), "It's %s", "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err).Code()) + + // Output: + // It's My Error + // 10000 + } + ``` + +### `WrapCode/WrapCodef` + +- Description: It functions the same as the `Wrap/Wrapf` methods, used to wrap other `error` objects, constructing multi-level error information that includes stack information, and adds an input parameter for error codes. +- Format: + + ```go + // WrapCode wraps error with code and text. + // It returns nil if given err is nil. + WrapCode(code gcode.Code, err error, text ...string) error + + // WrapCodef wraps error with code and format specifier. + // It returns nil if given `err` is nil. + WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error + ``` + +- Examples: + + ```go + func ExampleWrapCode() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCode(gcode.New(10000, "", nil), err1, "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // Custom Error: permission denied + // 10000 + } + + func ExampleWrapCodef() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCodef(gcode.New(10000, "", nil), err1, "It's %s", "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // It's Custom Error: permission denied + // 10000 + } + ``` + +### `NewCodeSkip/NewCodeSkipf` + +- Description: It functions the same as `NewCode/NewCodef`, used to create an `error` object with an error code but ignores part of the stack information (ignoring upwards from the current method call position). +- Format: + + ```go + // NewCodeSkip creates and returns an error which has error code and is formatted from given text. + // The parameter `skip` specifies the stack callers skipped amount. + func NewCodeSkip(code, skip int, text string) error + + // NewCodeSkipf returns an error that has error code and formats as the given format and args. + // The parameter `skip` specifies the stack callers skipped amount. + func NewCodeSkipf(code, skip int, format string, args ...interface{}) error + ``` + +### `WrapCodeSkip/WrapCodeSkipf` + +- Description: It functions the same as `WrapCodeSkip/WrapCodeSkipf`, used to wrap an `error` object with an error code but ignores part of the stack information (ignoring upwards from the current method call position). +- Format: + + ```go + // WrapCodeSkip wraps error with code and text. + // It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error + + // WrapCodeSkipf wraps error with code and text that is formatted with given format and args. + // It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error + ``` + +## `Code` + +- Description: Retrieve the error code from the error object. This method will recursively retrieve it. If the current error object does not have an error code, it will trace back to find the error code of its preceding error object, and so on. When the given `error` does not carry error code information, this method returns the predefined error code `gcode.CodeNil`. +- Format: + + ```go + // Code returns the error code of `current error`. + // It returns `CodeNil` if it has no error code neither it does not implement interface Code. + func Code(err error) gcode.Code + ``` + +- Examples: + + ```go + func ExampleCode() { + err1 := gerror.NewCode(gcode.CodeInternalError, "permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.Code(err1)) + fmt.Println(gerror.Code(err2)) + + // Output: + // 50:Internal Error + // 50:Internal Error + } + ``` + +## `HasCode` + +- Description: This method checks whether the given error object contains the specified error code. Similar to the `Code` method, it will also trace back to find the error code of the preceding errors. +- Format: + + ```go + // HasCode checks and reports whether `err` has `code` in its chaining errors. + func HasCode(err error, code gcode.Code) bool + ``` + +- Examples: + + ```go + func ExampleHasCode() { + err1 := gerror.NewCode(gcode.CodeInternalError, "permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.HasCode(err1, gcode.CodeOK)) + fmt.Println(gerror.HasCode(err2, gcode.CodeInternalError)) + + // Output: + // false + // true + } + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..8c2a7b9d0f8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" @@ -0,0 +1,13 @@ +--- +slug: '/docs/core/gerror-code' +title: 'Error Handling - Error Code' +hide_title: true +keywords: [GoFrame, GoFrame Framework, Error Code Feature, Error Handling, Golang, Programming, Web Development, Software Framework, Development Documentation, Technical Guide] +description: "Error handling in the GoFrame framework, especially the use of the error code feature. Through detailed examples and guides, it helps developers easily identify and handle errors, improving code reliability and maintainability. This is an important resource for understanding the error handling mechanism of the GoFrame framework." +--- + + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..b66e06873e5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/gerror' +title: 'Error Handling' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame framework, error handling, gerror, stack information, developers, API documentation, unified error handling, Go, programming] +description: "The GoFrame framework provides powerful error handling capabilities implemented through the gerror component, with all components returning errors accompanied by stack information, allowing developers to quickly pinpoint issues. Using this framework can effectively enhance programming efficiency and application stability." +--- + +## Introduction + +The `GoFrame` framework offers powerful, rich, and unified error handling capabilities implemented through the `gerror` component. This component is also the unified error handling component of the framework. All components of the framework, if there is an error returned, are accompanied by stack information to facilitate developers in quickly identifying issues. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/errors/gerror" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror) + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..ce9534562d5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/design/context-variable' +title: 'Context Shared Variables' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Context, Context Variables, Shared Variables, Asynchronous IO, HTTP Requests, Session Management, User Information, Middleware] +description: "Transmitting and managing shared variables in context flow using Context from the GoFrame framework. In Go network applications, especially in HTTP/RPC services, Context is a key tool for transmitting asynchronous IO control and context variables. Through the sharing of structured objects, this article demonstrates how to achieve variable transmission during the request process, ensuring consistency and flexibility of variables in the request chain." +--- + +`Context` refers to the standard library `context.Context`, which is an interface object commonly used for **asynchronous IO control** and **transmission of context flow variables**. This article aims to demonstrate how to use `Context` to transmit inter-process shared variables. + +In the execution flow of `Go`, especially in `HTTP/RPC` execution flows, there is no "global variable" method to obtain request parameters; instead, context `Context` variables are passed to subsequent process methods, and `Context` variables contain all the shared variables needed. Moreover, the shared variables in this `Context` should be agreed upon in advance and are often stored in the form of object pointers. + +Sharing variables through `Context` is very simple. Here, we demonstrate how to transmit and use general shared variables in a practical project through an example in a project. + +## 1. Structure Definition + +Context objects often store some variables that need to be shared, and these variables are usually stored in structured objects for easy maintenance. For example, we define shared variables in the `model` in a context: + +```go +const ( + // Key name to store context variables, shared by front and back systems + ContextKey = "ContextKey" +) + +// Request context structure +type Context struct { + Session *ghttp.Session // Current Session management object + User *ContextUser // Context user information + Data g.Map // Custom KV variables set according to business module needs, not fixed +} + +// User information in the request context +type ContextUser struct { + Id uint // User ID + Passport string // User account + Nickname string // User name + Avatar string // User avatar +} +``` + +Where: + +1. `model.ContextKey` constant represents the key name stored in the `context.Context` variable, used to store/retrieve business custom shared variables from the transmitted `context.Context` variable. +2. `Session` in `model.Context` structure represents the `Session` object of the current request. In the `GoFrame` framework, each `HTTP` request object has an empty `Session` object, which adopts lazy initialization design and is initialized only when real read and write operations are performed. +3. `User` in `model.Context` structure represents the basic information of the currently logged-in user, with data available only after user login; otherwise, it is `nil`. +4. `Data` attribute in `model.Context` structure is used to store custom `KV` variables, so developers generally do not need to add custom key-value pairs to `context.Context`, but directly use this `Data` attribute of the `model.Context` object. See details in later sections. + +## 2. Logical Encapsulation + +The context object is also related to business logic, so we need to encapsulate the context variables with a `service` object for easy use by other modules. + +```go +// Context management service +var Context = new(contextService) + +type contextService struct{} + +// Initialize context object pointer into context object for subsequent request flow modifications. +func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) { + r.SetCtxVar(model.ContextKey, customCtx) +} + +// Get context variables, return nil if not set +func (s *contextService) Get(ctx context.Context) *model.Context { + value := ctx.Value(model.ContextKey) + if value == nil { + return nil + } + if localCtx, ok := value.(*model.Context); ok { + return localCtx + } + return nil +} + +// Set context information into context request, note it's complete overwrite +func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUser) { + s.Get(ctx).User = ctxUser +} +``` + +## 3. Context Variable Injection + +Context variables must be injected into the request flow at the very beginning of the request to be available for other method calls. In `HTTP` requests, we can achieve this with `GoFrame` middleware. For `GRPC` requests, interceptors can be used. We can define this in the `middleware` management object in the `service` layer as follows: + +```go +// Custom context object +func (s *middlewareService) Ctx(r *ghttp.Request) { + // Initialize, must be executed first + customCtx := &model.Context{ + Session: r.Session, + Data: make(g.Map), + } + service.Context.Init(r, customCtx) + if userEntity := Session.GetUser(r.Context()); userEntity != nil { + customCtx.User = &model.ContextUser{ + Id: userEntity.Id, + Passport: userEntity.Passport, + Nickname: userEntity.Nickname, + Avatar: userEntity.Avatar, + } + } + // Pass the custom context object to template variables for use + r.Assigns(g.Map{ + "Context": customCtx, + }) + // Execute the next request logic + r.Middleware.Next() +} +``` + +This middleware initializes a user-executed flow shared object, and the object stored in the `context.Context` variable is a pointer type `*model.Context`. By fetching this pointer anywhere, you can both access and modify the data within. + +If there is stored user login information in `Session`, the required shared user basic information will also be written into `*model.Context`. + +## 4. Context Variable Usage + +### Method Definition + +By convention, the first input parameter of a method is often reserved for `context.Context` type parameters to accept context variables, especially in the `service` layer methods. For example: + +```go +// Execute user login +func (s *userService) Login(ctx context.Context, loginReq *define.UserServiceLoginReq) error { + ... +} + +// Query content list +func (s *contentService) GetList(ctx context.Context, r *define.ContentServiceGetListReq) (*define.ContentServiceGetListRes, error) { + ... +} + +// Create reply content +func (s *replyService) Create(ctx context.Context, r *define.ReplyServiceCreateReq) error { + ... +} +``` + +Moreover, by convention, the last return parameter of a method is often of `error` type. If you are sure that there will never be an `error` inside this method, you can ignore it. + +### `Context` Object Retrieval + +Using the following method encapsulated in `service`, you can pass the `context.Context` variable. In the `GoFrame` framework's `HTTP` request, you can get the `context.Context` variable through the `r.Context()` method, and in `GRPC` requests, the first parameter of the methods generated in the compiled `pb` files is fixed as `context.Context`. + +```go +service.Context.Get(ctx) +``` + +### Custom Key-Value + +Set/Get custom key-value pairs via: + +```go +// Set custom key-value pair +service.Context.Get(ctx).Data[key] = value + +... + +// Get custom key-value pair +service.Context.Get(ctx).Data[key] +``` + +## 5. Precautions + +1. Only pass required link parameter data in context variables, don't put everything in them. Especially for some method argument passing data, not everything should be put into them; instead, explicit parameter transmission should be used. +2. Context variables should only be used for temporary runtime use and not for long-term persistent storage. For example, serializing `ctx` and storing it in a database, then deserializing it for use in the next request is incorrect. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..23853a447b5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" @@ -0,0 +1,45 @@ +--- +slug: '/docs/design/enums' +title: 'Golang Enums' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame framework, Go language, enum implementation, Kubernetes enum, Go constants, cross-service enum, OpenAPI enum, enumeration in Go, API development, SDK generation] +description: "Implementing enum values in Go. Although Go itself doesn't support enum definition, it can simulate enum types using const. This method is widely used in Kubernetes projects. Additionally, this article explores how to efficiently maintain enum values in cross-service calls and front-end and back-end collaboration, providing methods using OpenAPI standard protocols and related tools." +--- + +## Implementing Enum Values in Go + +The `Go` language does not provide an `enum` definition, but we can use `const` to simulate enum types, which is a conventional practice in `Go`. + +For example, in the `Kubernetes` project, there are numerous "enum values" defined in the form of constants: + +```go +// PodPhase is a label for the condition of a pod at the current time. +type PodPhase string + +// These are the valid statuses of pods. +const ( + // PodPending means the pod has been accepted by the system, but one or more of the containers + // has not been started. This includes time before being bound to a node, as well as time spent + // pulling images onto the host. + PodPending PodPhase = "Pending" + // PodRunning means the pod has been bound to a node and all of the containers have been started. + // At least one container is still running or is in the process of being restarted. + PodRunning PodPhase = "Running" + // PodSucceeded means that all containers in the pod have voluntarily terminated + // with a container exit code of 0, and the system is not going to restart any of these containers. + PodSucceeded PodPhase = "Succeeded" + // PodFailed means that all containers in the pod have terminated, and at least one container has + // terminated in a failure (exited with a non-zero exit code or was stopped by the system). + PodFailed PodPhase = "Failed" + // PodUnknown means that for some reason the state of the pod could not be obtained, typically due + // to an error in communicating with the host of the pod. + PodUnknown PodPhase = "Unknown" +) +``` + +## How to Efficiently Maintain Enum Values Across Services + +It is relatively simple to use enum values internally within a project, as they can be defined and used internally. However, when it comes to cross-service calls or front-end and back-end collaboration, efficiency tends to be lower. When services need to demonstrate interface capabilities to external callers, an `API` interface document (or interface definition file, such as `proto`) often needs to be generated, and the client `SDK` for calling is often generated based on the interface document (file). + +If it's an interface definition file, like `proto`, the problem can often be resolved by directly viewing the source code, so it's not a big issue. Here, we mainly discuss the problem of maintaining enum values in interface documents, particularly when maintaining enum values through the `OpenAPI` standard protocol during front-end and back-end collaboration. We provide specialized tools for maintaining these enum values, please refer to the chapter: [Enums Maintenance](../开发工具/代码生成-gen/枚举维护-gen%20enums.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..195f552f8ea --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" @@ -0,0 +1,50 @@ +--- +slug: '/docs/design/tracing' +title: 'OpenTelemetry Tracing' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, Full Link Tracing, OpenTelemetry, Observability, OTEL, Golang, Link Transmission, Log Support, Framework, Third-party Components] +description: "Link tracing is one of the key observability metrics, and the GoFrame framework excels in supporting the OpenTelemetry standard. This article explores the challenges of link tracing in project practices and how to achieve full link tracing using the GoFrame framework, ensuring standard implementation and log support. The framework's unified components and standard detection tools help enhance the stability of business projects." +--- + +Observability is important, and there is a relatively good `OpenTelemetry` standard available. Various third-party components and vendors following this standard library will make exposing and integrating observability data much simpler. Today, we will talk about link tracing, a crucial metric of observability. +:::tip +The `GoFrame` framework is highly forward-looking. It began to continuously focus on the `OpenTelemetry` standard during its draft phase and started supporting it when the `OTEL` standard released an `alpha` version. Currently, the `Golang` implementation of `OTEL` is stable. `GoFrame` is among the best frameworks supporting the `OTEL` standard and its implementation, implicitly providing link tracing features. Observability is also a key focus of the framework's future developments. +::: +## I. Pain Points in Project Practices + +In project practices, implementing link tracing often encounters the following common pain points. + +### 1. Components do not strictly follow the standard + +Even though there is an `OTEL` standard, third-party components may not adhere to it strictly. For instance, the logging and `ORM` components might not strictly enforce the passing of `ctx` context variables. + +### 2. Unorganized Third-party Components + +Numerous third-party components are pieced together in business projects, some of which do not support link tracing, let alone the `OTEL` standard. For example, common components such as command management, configuration management, cache management, data validation, scheduling tasks, etc., do not provide the `ctx` context variable transmission. Link information is lost when components used do not support link transmission. + +### 3. Business Logic Easily Loses Links + +In business projects, link tracing lacks detection mechanisms, which means it can easily be lost unintentionally during operation, such as by creating a new `ctx` or passing a `nil` `ctx`. When strategic design encounters difficulty in tactical execution, locating issues becomes a significant challenge, especially in `toB` businesses with time-sensitive support tickets. + +## II. Full Link Tracing in Framework + +### 1. Unified Framework + +As `GoFrame` is a fully-engineered foundational framework providing the general core components needed for projects, it facilitates the unified realization of link tracing standards across foundational components effortlessly. + +### 2. Standard Implementation + +Support for the `ctx` context variable is increased across the framework's core components, rigorously following the `OTEL` standard to ensure smooth standard implementation. + +### 3. Log Support + +In engineering practices, logging is a critical component for link tracing. In most business scenarios, tracing and logging contents are required to diagnose and locate specific problems. The logging components of the `GoFrame` framework also support and strictly follow the `OTEL` standard. Hence, using the framework's logging components will automatically log link-related information. + +### 4. Standard Detection Tool + +The framework provides engineering standards detection functionality via development tools, automatically identifying link loss issues in business projects, further promoting the `OTEL` standard's implementation and ensuring project quality. + +### 5. Support for Link Transmission + +Link transmission also requires unified components. The most common protocols currently are `HTTP/GRPC`. Thus, the framework also provides `HTTP Client/Server` and `GRPC Client/Server` components to ensure link transmission. To enhance usability and ease of integration, complex underlying function details are abstracted, and this link transmission is **implicitly implemented** at the underlying level, making it completely transparent to the user. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..bc117bc3a64 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/design/error-stack' +title: 'Error Stack' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame framework, error handling, error stack, unified error handling, log staking, error features, stack information, third-party components, error return] +description: "GoFrame framework's strategy and design in error handling, which address common issues of missing error stacks and redundant logs in projects through unified error handling solutions and error components. The whole component of GoFrame framework supports error stack functionality, reducing the cost of troubleshooting and maintenance, and enhancing the stability and usability of projects." +--- + +## A. Pain Points of Project Error Handling + +In business projects, we often encounter the following pain points. + +### 1. Lack of a Unified Error Handling Solution, Logs All Over the Code + +To facilitate problem location when an interface fails, logs are seen everywhere in the code, taken as a matter of course. However, this overwhelming amount of disorganized logs usually increases maintenance workload without achieving its intended purpose. + +### 2. Lack of an Error Stack After a Request Error, Making It Difficult to Quickly Locate Issues + +For example, when an 'error'-level mistake occurs at the lower level, only one error message is visible at the top level. How can we troubleshoot this? + +![](/markdown/d0a2ecfa83e3b3107e38a519bacf0f17.png) + +A screenshot from a real-world case investigation + +### 3. Error Returns from Third-party Components Lack Stack Information + +Not only third-party components, but all methods in the standard library return errors without a stack, posing a significant challenge for unified error handling at the business layer. Almost all errors returned from business layer code calls need to be wrapped again using methods like 'Wrap', so the business layer itself can achieve error stack returns. This maintenance cost is relatively high and almost relies on 'CodeReview' for human assurance, risking missing 'Wrap' handling. + +### 4. Diverse Error Components, Projects Tend to Wrap Another Layer + +There are many third-party error handling components. How to choose? Business projects often also wish to wrap another layer, further increasing the maintenance cost of error handling components. + +## B. Full Error Stack Design of the Framework + +### 1. Unified Error Component + +The 'GoFrame' framework provides the industry's most powerful error handling component, which is also the error component widely used within the framework, reducing the choice cost for business teams. + +### 2. Unified Error Handling Solution + +The 'GoFrame' framework provides powerful engineering design specifications, including a necessary unified error handling solution. Through the unified framework's engineering design, some common pain points have been solved through components and tools, allowing business teams to focus on the business itself, thereby making development work more efficient. + +Under the unified error handling solution, all method calls in the project take the 'error' return value as the basis for execution success or failure. If 'error' is not 'nil', return quickly and pass it layer by layer upward to handle errors at the top level. Furthermore, the critical components of the framework have already provided default error handling logic. + +![](/markdown/0237be84e57c222bd476dad67a883253.png) + +### 3. Full Component Support for Stack Errors + +🔥 The 'GoFrame' framework's error return objects in **all basic components** include error stacks! 🔥 + +🔥 The 'GoFrame' framework's error return objects in **all basic components** include error stacks! 🔥 + +🔥 The 'GoFrame' framework's error return objects in **all basic components** include error stacks! 🔥 + +🔥 Repeating important things three times! 🔥 + +This is a challenging feat because the components provided by the framework can cover almost all the needs of business projects, yet the framework indeed achieved it. Although the framework incurred significant initial costs (a separate version to realize this feature), it's a one-time setup with long-term benefits. This means if business projects use the unified 'GoFrame' basic framework, error handling will be more straightforward, the risk of missing error stacks will be significantly reduced, and projects will be more robust and easier for quick troubleshooting. + +### 4. Key Components Support Error Stack Printing + +The framework's key components provide **default handling** for error stack printing to improve usability and simplify the burden on users. These key components are the program's entry points, like 'HTTP/GRPC Server' and 'Command' command lines. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" new file mode 100644 index 00000000000..eae6ecf9a32 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/design/project-dao-improvement' +title: 'Pain Points and Improvements In Business Project' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, DAO Improvement, Database Operation, Data Model, Business Model, ORM Abstraction, Engineering Design, Data Permission, Automated Code Generation] +description: "The DAO engineering pain points and corresponding improvement strategies in project development using the GoFrame framework. Enhance data operation management efficiency through automated data model management, separation of data and business models, automated DAO code management, and introduction of DO data conversion models. Emphasize the importance of data operation permission consolidation to reduce maintenance costs and risks associated with data operations." +--- + +## 1. Introduction + +We all know that developing business projects is inextricably linked to using database operation components, and the database is the core of most business projects, which is the origin of the nickname "CRUD engineer." When performing database operations in business projects, a rather low approach is to directly "Open/New" and then engage in various SQL string operations. Slightly more standard projects might consider selecting or encapsulating an ORM abstraction layer to improve CRUD efficiency and reduce the risk of data operations. More rigorous projects might further consider engineering management by introducing design patterns and concepts such as DAO/DTO/VO. + +In the information age, data is extremely important and data operations are highly sensitive, so GoFrame framework's engineering approach to data operation management is rigorous. We provide necessary ORM abstractions, necessary DAO encapsulations, and necessary engineering norms and constraints. Additionally, we do not adopt a standardized design but maintain a simple, flexible, and easy-to-extend engineering design approach. + +## 2. Engineering Pain Points + +In some rigorous business projects, even with ORM & DAO abstractions and preliminary engineering design in place, the following common pain points still exist. + +### 1. Data Set and Code Structure Asynchrony + +When manually maintaining the data structures corresponding to data sets in the code, it's like digging a pit—just waiting to see who falls into it later. + +### 2. Ambiguity between Data Model and Business Model + +Confusing the responsibilities of data models with business models, and coupling data models with business logic and interface definitions, increases maintenance costs and risks associated with data models changes. Common pain points include: + +- In the `model`, there are both business-related data structures (business model) and data structures corresponding to data sets (data model). How can they be efficiently isolated and managed? +- In business processes, using data models as the **input parameters** and even embedding data models directly into the API interface input data structure (always finding ways to use data models in business models). + +### 3. Too Much Business Logic Encapsulation in the DAO Layer + +Have you ever felt that any data operation has a reason to be thrown into the `DAO`? + +### 4. Using Data Models as Parameters for ORM/DAO Operations + +You might think this is correct, but unclear data structures imply costs and risks. Any operation should clearly specify inputs/outputs; otherwise, it lacks rigor, especially in data operations. + +### 5. Open Data Operation Permissions, Allowing Calls from Anywhere in the Project + +Data operation permissions should be consolidated as much as possible. If overly open, maintenance costs and risks will exponentially increase as business and personnel complexity grows. + +### 6. Using the Same Data Structure from Top-level Business to Bottom-level Data Set Operations + +A common issue is designing a large structure, such as a data model (some even design all attributes as pointers or `interface{}`), passing it seamlessly from top-level business through to bottom-level data operations, with method logic determining parameters based on specific input attributes. What problems does this cause? + +- Method parameter definitions are unclear, resulting in extra collaboration costs and risks due to ambiguity. +- The same data structure is coupled with multiple methods, meaning any change in the data structure will affect all related methods. +- Related methods cannot be fully reused (especially methods in the `service` layer). + +## 3. Engineering Improvements + +### 1. Automated Data Model Management + +Implement automated generation of data models from data sets using tools to avoid asynchrony caused by manual maintenance. + +### 2. Separation of Data and Business Models + +Maintain data models through an `entity` package and business models through a `model` package, distinguishing responsibilities per package. Data models are tool-maintained, and business models are defined and maintained by developers based on business scenarios. + +### 3. Automated DAO Code Management + +Implement automated generation of `DAO` code from data sets using tools to increase production efficiency. Only basic data operations autogenerated, with no specific business logic encapsulation. + +### 4. Introduction of DO Data Conversion Model + +Avoid using data models directly as `DAO` parameters to avoid pitfalls. The GoFrame framework introduces a `DO` package that automatically converts to data set corresponding data structures during `DAO` operations, enhancing operational efficiency and reducing risks. + +### 5. Consolidating Data Operation Permissions + +Since data operations are centrally maintained by the `DAO` package, it can be migrated to the `internal` directory of the corresponding `logic` layer business module, ensuring that only `logic` layer business logic code can execute data operations through `DAO`. + +This is a stricter usage limitation, used selectively based on developer needs. The default project templates and tools of the framework do not consolidate data operation permissions. This consolidation can be achieved through framework development tool configurations to generate `dao` code for different `logic` business module locations. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" new file mode 100644 index 00000000000..e533a217bf9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" @@ -0,0 +1,136 @@ +--- +slug: '/docs/design/project-dao-pain' +title: 'Pain Points and Improvements In ORM Component' +sidebar_position: 0 +hide_title: true +keywords: [DAO,GoFrame,Data Access Object,ORM,GRPC,Database Optimization,Code Encapsulation,Automatic Mapping,SQL Logs,Code Generation] +description: "The advantages of using DAO design in the GoFrame framework, and the pain points and solutions in using existing ORM components. Through DAO design, it greatly improves development and maintenance efficiency, reduces code coupling, and increases observability support. The article details how to overcome common issues when using ORM, such as field mapping, hardcoding, and data structure inconsistencies. Additionally, the improvement design includes the encapsulation of DAO objects and support for SQL logging functionality." +--- + +The design of `DAO` (Data Access Object) is actually a significant part of the engineering practices in the `GoFrame` framework. + +The `DAO` design, combined with the `ORM` component of `GoFrame`, offers strong performance and ease of use, which can greatly enhance development and maintenance efficiency. After reading this chapter, individuals should be able to understand and appreciate the advantages of using DAO database access object design. + +:::info +I review this article every year to see if any parts can be removed. However, I am always disappointed because this article still applies to the current situation. I have even added new content this year. +::: + +## 1. Current `ORM` Usage Example + +### 1. Need to Define Models + +![](/markdown/77daf5d299eabade856d950ab3161f94.png) Basic User Table (for demonstration purposes only, real table has dozens of fields) + +![](/markdown/f4e8c70ee25ec329f2b64bb3a53ff503.png) Doctor Information Table (for demonstration purposes only, real table has hundreds of fields) + +### 2. `GRPC` Interface Implementation Example + +A simple `GRPC` information query interface. + +![](/markdown/b45b3af0a0bdc9ad30f739e31d0039ae.png) A simple `GRPC` data query interface + +## 2. Description of Current Pain Points + +### 1. Must Define `tag` to Associate Table Structure with `struct` Properties, Cannot Achieve Automatic Mapping + +There is already a certain correlation rule between table fields and entity object property names, making it unnecessary to define and maintain a large number of `tags`. + +![](/markdown/f1bb2d203d4fe4f2c44bbc7e14b7832a.png) + +A large number of unnecessary `tag` definitions used to map database table fields to entity object properties + +### 2. Does Not Support Specifying Fields to Query via the Return Object + +You cannot specify query fields via the structure of the returned object. Either you can only `SELECT *` or manually input query fields through additional methods, which is inefficient. + +![](/markdown/70e01c869632543b846b04a1696e9737.png) + +Common `SELECT *` operation, unable to specify query fields based on the interface object + +### 3. Unable to Automatically Filter Field Names of Input Object Properties + +Once the input and output data structures are defined, the output data structure already contains the field names we need to query. Developers define a return object with the expectation that only the fields needed will be queried, automatically filtering out unnecessary properties. + +### 4. Need to Create Intermediate Query Result Objects for Assignment and Conversion + +Query results do not support intelligent `struct` conversion, requiring an additional intermediate `model` model and using other tools to copy, which is inefficient. + +![](/markdown/05bf7722da09a27e7ca82bf6e0f89271.png) + +Existence of an intermediate temporary model object for taking query results and assigning values to the return structure object + +### 5. Need to Pre-initialize Return Objects Regardless of Whether Data is Queried + +This approach is inelegant, impacts performance, and is not `GC` friendly. Expectation is to auto-create return objects only when data is queried and do nothing if no data is found. + +![](/markdown/239f4b75b4b77e85bca523371a7dd1b4.png) + +Need to pre-initialize return objects regardless of whether data is queried + +### 6. Entire Project Uses Low-Level Bare `DB` Object Operations Without Object Encapsulation + +Most `Golang` beginners seem to prefer using a global `DB` object to create a specific table `Model` object for `CRUD` operations. This approach lacks a layered code design, **resulting in high coupling between data operations and business logic**. + +![](/markdown/d73fdaa5b76b831db0a2c1069742c218.png) + +Primitive database object operation without `DAO` encapsulation + +### 7. Ubiquitous String Hardcoding, such as Table Names and Field Hardcoding + +For instance, if the field `userId` is accidentally written as `UserId` or `userid`, and tests do not fully cover this, it could lead to a new accident under certain circumstances. + +![](/markdown/46d8aae38995327c6ce26832d21f628b.png) + +大量的字符串硬编码 + +### 8. Too Many Pointer Property Definitions Caused by the Underlying ORM + +Pointer property objects lay hidden risks for business logic processing, requiring developers to switch between pointers and properties in the code logic, especially since basic types often need to pass parameters by re-evaluation. If the input parameter is of `interface{}` type, it is easier to cause `BUG`. + +![](/markdown/620c8a9a4a47de0243748d588aa0bb51.png) + +`BUG` example, inappropriate use of pointer properties causing logical errors in address comparison. + +![](/markdown/daa08ad1e9102f4ac964a8176a80e061.png) + +This also affects the design of business model structure definitions, leading developers to form incorrect habits (upper business model pointer properties often cater to the lower-level data table entity objects for convenient data transmission). + +![](/markdown/bba716ea66e03727826ae6401ce01b2d.png) + +Notably, a common mistake is using the lower-level data entity model as the top-leve business model. This issue is particularly evident when pointer properties are used in the lower-level data entity objects. + +### 9. Support for Observability: Tracing, Metrics, Logging + +As the most critical core component of business projects, supporting observability is crucial. + +### 10. Data Set and Code Data Entity Structure Mismatch + +When data entity structures are manually maintained, there is often a risk of inconsistency between data sets and code data entity structures, resulting in high development and maintenance costs. + +## 3. Improvement Design + +1. No special tag definitions needed for query result objects, fully automatic association mapping + +2. Support automatic identification of query fields based on the specified object instead of all `SELECT *` + +3. Support automatic filtering of non-existent field contents based on the specified object + +4. Use `DAO` object encapsulation code design, operating data tables via object methods + +5. `DAO` objects encapsulate associated table and field names to avoid string hardcoding + +6. No need to predefine entity objects for receiving results, nor create intermediate entities for interface return object assignment and conversion + +7. Query result objects are not pre-initialized, automatically created only when data is queried + +8. Built-in support for `OpenTelemetry` standards, achieving observability, greatly improving maintenance efficiency, reducing costs + +9. Support for `SQL` log output capabilities, with on/off switch functionality + +10. Decoupling of data models, data operations, and business logic, supporting automated generation of `Dao` and `Model` code tools, ensuring consistency of data sets and code data structure, improving development efficiency, and facilitating the implementation of standards + +11. Etc. + +![](/markdown/90537635dc3b5623060fa9edfc49948a.png) + +Code example after improvement using `DAO` design \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..7afa2e531e2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/design/project-dao' +title: 'DAO Encapsulation' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, DAO Design, Data Access Object, Design Patterns, Software Architecture, Code Encapsulation, Database Operations, Best Development Practices, System Design] +description: "This section explores DAO encapsulation design within the GoFrame framework, discussing various patterns and methods for designing data access objects. It helps developers achieve efficient code encapsulation and database operations, enhancing the modularity and maintainability of software architecture." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..982196666bd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" @@ -0,0 +1,89 @@ +--- +slug: '/docs/design/project-layer' +title: 'Code Layering' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame Framework, Code Layering Design, MVC Design Pattern, 3-Tier Architecture, Business Logic Layer, Data Access Layer, Presentation Layer UI, Model Definition Layer, Software Architecture, Decoupling] +description: "The code layering design of the GoFrame framework, including the MVC design pattern and the 3-tier architecture design. The MVC design pattern is suitable for business scenarios requiring server-side rendered pages, while the 3-tier architecture design emphasizes the idea of high cohesion and low coupling by separating the business logic layer from the data access layer, improving the project's maintainability and flexibility." +--- +:::tip +The significance of code layering lies in further decoupling program logic, designing data flow and dependency relationships between layers as a unidirectional link, making the system architecture more flexible and extensible. +::: +## 1. Introduction + +`GoFrame`, as a fully engineered foundational development framework, has its unique framework design concepts. In this chapter, we introduce its code layering design. In the server-side business code layering design pattern, we most commonly see the `MVC` design pattern and the 3-tier architecture design pattern (`3-Tier Architecture`). + +## 2. `MVC` Design Pattern + +Let's first review the classic `MVC` design pattern. + +![MVC Design Pattern](/markdown/d90094b0f7ec2edb2220ffc0204a1c2d.png) + +Figure 1. MVC Design Pattern + +### Brief Introduction + +`M` stands for Model, indicating the encapsulation of business rules. Among the three components of `MVC`, the model carries out most of the processing tasks. The data returned by the model is neutral and unrelated to the data format, meaning a model can serve multiple views. Since code applied to the model only needs to be written once and can be reused by multiple views, it reduces code duplication. + +`V` stands for View, the interface that users see and interact with. For example, a web page composed of `HTML` elements or a software client interface. One of the merits of `MVC` is that it can handle many different views for applications. Actually, no real processing occurs in the view; it merely serves as an output data way and allows manipulation by users. + +`C` stands for Controller, which accepts user input and calls models and views to fulfill user needs. The controller itself neither outputs anything nor performs any processing. It only receives requests and decides which model component to call for handling the request, then determines which view to use to display the returned data. + +This design pattern is fairly simple and suitable for business scenarios where server-side rendered pages are required and is also friendly for `SEO`. However, with the rise of the `MVVM` development pattern and the rapid advancement of front-end technology, particularly projects with front-end frameworks like `Vue`, `React`, and `Angular`, the use scenarios for the server-side `MVC` design pattern have become less frequent. + +### Pain Points + +For business scenarios where the business logic isn't particularly complex, `MVC` can still manage quite well. However, as the business logic becomes large and complex, the increasing maintenance cost issue with the `MVC` design pattern becomes more evident. Especially with the development of microservice architectures in internet projects, the `MVC` design pattern has become more and more impractical in most internet project development. The main reasons for this are: + +- Further separation of view presentation and data operation methods, especially with the development of mobile ends and `MVVM` frameworks on the front end, in most scenarios, server-side rendered `View` is no longer needed. +- The code layering design pattern of `MVC` is actually relatively coarse-grained: + - The `Model` layer's code both maintains the data and encapsulates the business logic. As business logic becomes more complex, this layer's function logic becomes bloated and difficult to maintain. + - For team management, the boundary of responsibilities between `Controller` and `Model` is vague, placing a high demand on developers to write good code. The concept may seem simple, but adapting to the industrial-level software production is challenging. + +## 3. `3-Tier Architecture` + +The GoFrame framework recommends the 3-tier architecture design pattern for code layering (3-Tier Architecture), but with some modifications in specific implementations. 3-tier architecture design effectively embodies the design philosophy of software design being "high cohesion, low coupling." + +![](/markdown/8b93ee429f05737e03dfc58bdfe04905.png) + +Figure 2. 3-Tier Architecture Design Pattern + +The traditional 3-tier architecture design, as shown above, divides project code into three layers, each with its distinct responsibility boundary. However, in most scenarios, we often see the following layered structure, where the data structure model is further extracted for unified maintenance. + +![3-Tier Architecture Design Model](/markdown/fe9aea78ab05dc6db3b34d021a05ee76.png) + +Figure 3. Common 3-Tier Architecture Design Model + +### Presentation Layer - `UI` + +`User Interface` is at the top layer of the architecture, directly interacting with users, mainly being `WEB` pages in `B/S`, or `API` interfaces. The primary function of the presentation layer is to implement data input and output for the system. During this process, data can be transferred to the `BLL` for processing without needing logical decision-making operations, and processed results are fed back to the presentation layer. In other words, the presentation layer realizes user interface/API interface functions, communicates user requirements, and uses `BLL` or `Model` for debugging to ensure user experience. + +### Business Logic Layer - `BLL` + +The `Business Logic Layer` is responsible for making logical judgment and execution on specific issues. After receiving user instructions from the presentation layer `UI`, it connects to the data access layer `DAL`. The business logic layer in the three-tier architecture is positioned between the presentation and data layers and bridges both. It facilitates data connection and instruction transmission between the three tiers, enabling logic processing on received data to achieve functions like data creation, deletion, updating, and lookup, and feeds results back to the presentation layer `UI` to realize software functionality. + +### Data Access Layer - `DAL` + +The `Data Access Layer` is the main control system for the database, performing operations such as data creation, deletion, updating, and lookup, and feeding back operation results to the business logic layer `BLL`. During actual operation, the data access layer lacks logical judgment capability. To ensure rigorous code writing and improve code readability, developers typically encapsulate general data capabilities (e.g., via `ORM` components) in this layer to ensure the data access layer `DAL`'s data processing functionality. + +### Model Definition Layer - `Model` + +Model definition is often represented by `Entity` objects, mainly used for database table mapping objects. During the practical development of information system software, object instances must be established, using object entity methods to represent relational database tables, assisting software development in controlling various system functions. Establishing entity libraries further facilitates parameter transmission across structural layers, enhancing the readability of the code. Essentially, the entity library mainly serves the presentation layer, business logic layer, and data access layer, transmitting data parameters between the three layers and strengthening the simplicity of data representation. + +It is important to distinguish that the `Model` here is significantly different from the `Model` in the `MVC` design pattern, with completely different responsibilities. + +### Comparison between 3-Tier Architecture and `MVC` + +As `MVC` is also a three-tier structure, some might generally categorize `MVC` within the 3-tier architecture design. At face value, this may not seem problematic. However, there are distinctions between the two. + +![](/markdown/2c6cfc087687cca60b1f4d23b78705c4.png) + +Figure 4. Comparison between 3-Tier Architecture and MVC + +As can be seen, in the 3-tier architecture design, the `UI` presentation layer is equivalent to the `View` and `Controller` layers in `MVC`. Originally, in `MVC`, the logic for these two layers should be relatively "lightweight," so merging them into one layer for unified management is understandable. An essential point is that `Model` in `MVC` is split into `BLL` and `DAL`, separating business logic from data access and decoupling the originally bloated `Model`, beneficial for better project maintenance. +:::tip +The evolution of software architecture, especially of internet software architecture, is essentially a process of continuously decoupling business logic. +::: +## 4. Further Understanding + +The concept of code layering is the most fundamental aspect of engineering design. We need to implement the layering idea into practice, specifically refer to: [Project Structure🔥](工程目录设计.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..762fecda3b5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/design/project' +title: 'Engineering Design 🔥' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Engineering Development, Design Practice, Microservices Project, Modular Design, Utility Library, Out-of-the-box, Project Pitfalls, Development Team] +description: "The GoFrame framework is a modular design utility library suitable for developing business projects, especially microservices projects. This documentation provides engineering development design and practice, helping developers solve common problems with rich experience, emphasizing the understanding of design background and ideas to promote the value of the team development." +--- + +## Introduction + +Due to its modular design, the `GoFrame` framework can be used as a utility library or for complete business project development. + +Considering that most scenarios for using the framework are for developing business projects (microservices projects), the framework also provides some design and practices for engineering development for direct out-of-the-box use. + +## Precautions + +Engineering development design is a complex "discipline." Different teams have different design styles, and in some scenarios, it is often influenced by the preferences of the team `leader`. + +The engineering development design provided on the framework's official website is based on the author's experience in writing this section and through project pitfalls encountered in `Go` practice. It can help solve common engineering development problems, but we do not believe it can meet the preferences of all development teams. + +Reviewing all the design sections here and understanding the background and ideas of each piece of design itself is more valuable than directly using framework components and tools. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..df6fe44cc7f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" @@ -0,0 +1,188 @@ +--- +slug: '/docs/design/project-structure' +title: 'Project Structure🔥' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, project structure design, code layering, framework design, business logic, directory structure, project development, three-layer architecture, data access, modularization] +description: "The method of project directory design in the GoFrame framework, based on the three-layer architecture model and improved with modern engineering practices to provide a universal and flexible directory structure design for complex business projects. Directory components include modules such as api, internal, dao, and logic, supporting various business scenarios while encouraging developers to flexibly add or remove directories for specific application implementations." +--- + +Project directory design is a further implementation of code layering design. It is recommended that you read carefully first: [Code Layering](代码分层设计.md) +:::tip +This is a directory design for **business projects** with the `GoFrame` framework. The main idea originates from the three-layer architecture but has been improved and refined in practice to better fit engineering practices and modern advancements. +::: +## 1. Project Directory Structure + +The basic directory structure of `GoFrame` business projects is as follows (taking `Single Repo` as an example): + +```text +/ +├── api +├── hack +├── internal +│ ├── cmd +│ ├── consts +│ ├── controller +│ ├── dao +│ ├── logic +│ ├── model +│ | ├── do +│ │ └── entity +│ └── service +├── manifest +├── resource +├── utility +├── go.mod +└── main.go +``` +:::info +**🔥 Important Tip 🔥**: The framework's project directory adopts a **generalized design** to meet the needs of projects with varying levels of complexity, but you can **increase or decrease the default directories as needed** in actual projects. For example, in scenarios lacking `i18n/template/protobuf` requirements, you can **directly delete the corresponding directories**. Similarly, for very simple business projects (such as validation/demonstration projects) that do not require strict use of `dao/logic/model` directories and features, you can **directly delete the corresponding directories** and implement business logic directly in the `controller`. **Everything can be flexibly chosen and assembled by the developer!** +::: +| Directory/File Name | Explanation | Description | +| --------------------- | ----------- | ------------- | +| `api` | External Interface | The input/output data structure definition for providing external services. Considering version management needs, it often exists as `api/xxx/v1...`. | +| `hack` | Tool Script | Contains project development tools, scripts, etc. For example, configuration for `CLI` tools, and various `shell/bat` script files. | +| `internal` | Internal Logic | The directory for storing business logic. Hidden visibility to the outside through `Golang internal` feature. | +| `  - cmd` | Entry Command | Directory for command-line management. Can manage multiple command lines. | +| `  - consts` | Constant Definitions | Defines all constants for the project. | +| `  - controller` | Interface Handling | Entrance/interface layer for receiving and parsing user input parameters. | +| `  - dao` | Data Access | Data Access Object, an abstract object for interacting with the underlying database containing only the basic `CRUD` methods. | +| `  - logic` | Business Encapsulation | Management of business logic encapsulation, specific business logic implementation, and encapsulation, often the most complex part of the project. | +| `  - model` | Structure Model | Data structure management module, managing data entity objects and input/output data structure definitions. | +| `    - do` | Domain Object | Used for converting business models and instance models in `dao` data operations, maintained by tools, and cannot be modified by users. | +| `    - entity` | Data Model | Data model is a one-to-one relationship between the model and data collection, maintained by tools, and cannot be modified by users. | +| `  - service` | Business Interface | Interface definition layer for decoupling business modules. Specific interface implementations are injected in `logic`. | +| `manifest` | Delivery Manifest | Contains files for program compilation, deployment, operation, and configuration. Common contents are: | +| `  - config` | Configuration Management | Directory for storing configuration files. | +| `  - docker` | Image Files | Files related to `Docker` images, script files, etc. | +| `  - deploy` | Deployment Files | Files related to deployment. By default, provides a `Yaml` template for `Kubernetes` cluster deployment, managed through `kustomize`. | +| `  - protobuf` | Protocol Files | `protobuf` protocol definition files used during `GRPC` protocol, compiled protocol files are generated in `api` directory. | +| `resource` | Static Resources | Static resource files. These files can be injected into release files in the form of resource packing/image compilation. | +| `go.mod` | Dependency Management | Dependency description file using `Go Module` package management. | +| `main.go` | Entry File | Program entry file. | + +### External Interface + +The external interface includes two parts: Interface Definition (`api`) + Interface Implementation (`controller`). + +The responsibility of the service interface is similar to the `UI` representation layer in three-layer architecture design, responsible for receiving and responding to client inputs and outputs, including filtering, converting, and validating input parameters, maintaining the output data structure, and calling `service` for business logic processing. + +#### Interface Definition - `api` + +The `api` package is used for defining data structure inputs and outputs agreed with the client, often closely bound to specific business scenarios. + +#### Interface Implementation - `controller` + +The `controller` receives the input from the `api`, can directly implement business logic within `controller`, or call one or more `service` packages to implement business logic and encapsulate the execution results into an agreed `api` output data structure. + +### Business Implementation + +Business implementation includes two parts: business interface (`service`) + business encapsulation (`logic`). + +The responsibility of business implementation is similar to the `BLL` business logic layer in three-layer architecture design, responsible for implementing and encapsulating specific business logic. +:::info +In the following chapters, we will uniformly refer to business implementation as `service`, and note that it actually includes two parts. +::: +#### Business Interface - `service` + +The `service` package is used to decouple business module calls. Business modules often do not directly call the corresponding business module resources to implement business logic but do so by calling `service` interfaces. The `service` layer contains only interface definitions, with specific interface implementations injected into the business modules. + +#### Business Encapsulation - `logic` + +The `logic` package is responsible for implementing and encapsulating specific business logic. Codes from various levels of the project do not directly call the business modules of the `logic` layer but do so through the `service` interface layer. + +### Structure Model + +The `model` package serves a role similar to the `Model` definition layer in the three-layer architecture. It only contains the global, common data structure definitions for reference by all business modules in the project. + +#### Data Model - `entity` + +Defined data structures bound to the data collection, often corresponding one-to-one with data tables. + +#### Business Model - `model` + +Common data structure definitions related to business, including most method input and output definitions. + +### Data Access - `dao` + +The role of the `dao` package is similar to the `DAL` data access layer in three-layer architecture, responsible for converging all data access. + +![](/markdown/1e1bb98778823124dc5bf35c57e8f4cb.png) + +Mapping relationship between three-layer architecture design and framework code layering + +## 2. Request Layer Flow + +![](/markdown/df7dd9a93cb541a8ca126b5b051002ab.png) + +### cmd + +The `cmd` layer is responsible for guiding the program startup, its significant tasks being initialization logic, registering route objects, starting the `server` listener, and blocking the program operation until the `server` exits. + +### api + +The upper layer `server` service receives client requests, converts them to `Req` receiving objects defined in `api`, performs request parameter-to-`Req` object attribute type conversions, executes basic validation bound to `Req` objects, and hands over the `Req` request objects to the `controller` layer. + +### controller + +The `controller` layer is responsible for receiving `Req` request objects, conducting some business logic validations, can directly implement business logic within `controller`, or call one or more `services` to implement business logic, and encapsulate results into the agreed `Res` data structure objects for return. + +### model + +The `model` layer manages all common business models. + +### service + +`service` is an interface layer used for business module decoupling. `service` contains no specific business logic implementation, relying on the `logic` layer for injection of specific business logic. + +### logic + +The business logic of the `logic` layer needs to perform data operations by calling `dao`. When calling `dao`, `do` data structure objects need to be passed for delivering query conditions and input data. After `dao` execution, `Entity` data models return data results to the `service` layer. + +### dao + +The `dao` layer interacts with the underlying real database through the `ORM` abstraction component of the framework. + +## 3. FAQ + +### Does the framework support common `MVC` development model + +**Of course!** + +As a foundational development framework with modular design, `GoFrame` does not constrain code design patterns and provides a powerful template engine core component for rapid template rendering development commonly seen in `MVC` mode. Compared to the `MVC` development model, we recommend using the three-layer architecture design model in complex business scenarios. + +### How to maintain when `api` and `model` layers have duplicate data structures + +Data structures defined in `api` are **for external use**, bound to specific business scenarios (such as specific page interaction logic, single interface function), and data structures are pre-set by upper-layer display layers; data structures defined in `model` are for **internal use only**, allowing for internal modifications without affecting external `api` interface compatibility. + +**Note that data structures in `model` should not be directly exposed for external use**, and the framework's project design deliberately places the `model` directory under the `internal` directory. Nor should alias type definitions of `model` data structures be provided in the `api` layer for external access. Once the `model` data structure is applied to the `api` layer, changes to internal `model` data structures will directly affect `api` interface compatibility. + +**If duplicate data structures (or even constants, enumerations) appear in both, it is recommended to define data structures at the `api` layer**. Internal service logic can directly access data structures at the `api` layer. The `model` layer's data structures can also directly reference those from the `api` layer, but not vice versa. + +Let's see an example for better understanding: + +![](/markdown/4d95fb64e06bb72a5456fb684704b891.png)![](/markdown/36794a54e02c2be6c0edbcf07bb8821a.png) + +### How to clearly define and manage the layering responsibilities between `service` and `controller` + +The `controller` layer handles `Req/Res` external interface requests. It is responsible for receiving, validating request parameters, can directly implement business logic within `controller`, or call **one or more** `services` to implement business logic and encapsulate execution results into the agreed `api` output data structures for return. The `service` layer processes `Input/Output` internal method calls. It is responsible for internal **reusable** business logic encapsulation, with methods often being more granular. + +**Typically, when developing an interface, only implementing the business logic in the `controller` layer is needed, and when there is repetitive code logic, it is abstractly settled into the `service` layer from various `controller` interfaces**. If `Req` objects are directly passed from the `controller` layer to `service`, and `service` directly returns `Res` data structure objects, this approach is coupled with external interfaces and only serves external interface services, making it difficult to reuse, thus increasing technical debt costs. + +### How to clearly define and manage the layering responsibilities between `service` and `dao` + +This is a classic question. + +**Pain Point:** + +Commonly, developers encapsulate **business logic related to data** within `dao` code layer while `service` code layer only makes simple `dao` calls. This approach can make the `dao` layer, originally meant to maintain data, more burdensome, while the business logic `service` layer code appears light. Developers are confused, questioning where to put their business logic code, in `dao` or `service`? + +Often, business logic mostly involves `CRUD` operations on data, causing nearly all business logic to gradually accumulate in the `dao` layer. Business logic changes frequently necessitate modifications to `dao` layer code. For instance: for data query requirements, initially it may seem like a simple logic to place code in `dao`, but as query requirements increase or change complexity, inevitably, existing `dao` code requires further maintenance and modifications, possibly leading to updates in `service` code as well. Originally limited to `service` layer business logic code responsibility becomes unclear and heavily coupled with `dao` layer code responsibilities, leading to increased development and maintenance costs later in the project. + +**Suggestion:** + +Our suggestion: `dao` layer code should strive to maintain general applicability, with most scenarios not requiring additional methods, instead assembling with some generalized chain operation methods. Business logic, including what appears to be simple data operation logic, should be encapsulated within `service`. `service` contains multiple business modules, with each module managing its `dao` object independently. Ideally, `service` communicates data through method calls between `services` rather than arbitrarily calling `dao` objects of other `service` modules. + +### Why use the `internal` directory to contain business code + +The `internal` directory is a feature unique to `Golang` language that prevents references from directories outside the peer directory. The purpose of this directory in business projects is to avoid unlimited unrestricted access between multiple projects if there are multiple sub-projects (especially in large repository management mode), making it difficult to prevent coupling of packages across different projects. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..6e974e62c45 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" @@ -0,0 +1,145 @@ +--- +slug: '/docs/design/project-mono-repo' +title: 'Mono-Repo Management' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, Microservices, Single Repo Management, Code Development, Permission Management, Service Collaboration, Large Repo Management, Code Repository, Containerization Support, Framework Commands] +description: "GoFrame framework's support for microservices-mono-repo management mode, detailing how to conduct code development and service collaboration in this mode. Discusses the pros and cons of mono-repo management and how to optimize microservice collaboration by dividing repository responsibilities, managing code visibility, unifying image repositories, and more. Also, provides relevant framework commands to help developers efficiently manage and deploy microservice projects." +--- + +This article aims to introduce `GoFrame` framework's support for microservices-mono-repo management mode, guiding developers on how to conduct code development and collaboration in a microservices-mono-repo management mode. + +## 1. Pre-reading + +Before starting this chapter, it is recommended to first understand the basic concepts and respective pros and cons of monolith repositories (`monolith`), microservices-multi-repo management (`multi-repo`), and microservices-mono-repo management (`mono-repo`): [What are the advantages and disadvantages of monolith and multi-repo, and which solution is better for microservices?](https://johng.cn/management/monorepo-vs-multirepo) + +Management constraints of code repositories do not belong to the framework’s responsibilities. The `GoFrame` framework's scaffold itself also supports commands to initialize two kinds of repository projects - single repository (`mono-repo`) and multiple repositories (`monolith/multi-repo`), to meet the needs of different teams. The specific choice of code repository management mode is decided by the development team based on their own needs, scenarios, and habits. +:::tip +To simplify and clarify the description of microservices-mono-repo management (`mono-repo`), we will refer to it as **large repo management** moving forward. +::: +## 2. Large Repo Management + +### 1. Division of Repository Responsibilities + +As noted in the pre-reading article, there's no silver bullet in the world, and large repos have both advantages and disadvantages. The most apparent drawbacks are **permission control** and **repository bloating**. To better manage the code repository and avoid the higher costs associated with these two drawbacks, we recommend minimizing the scope of microservices in a large repository as much as possible. The decision of which microservices need to be maintained in the repository depends on the frequency of cooperation between services. + +#### 1) When intra-team cooperation frequency is higher than inter-team + +- A typical scenario is for **non-microservice architecture products**, where service management responsibilities can be divided according to each business team. This way, the team can maintain scattered services in a unified code repository, fully utilizing the advantages of large repo management to improve intra-team development and maintenance efficiency. +- Another scenario is when the number of business microservices is not high (for example, within `50`), merging them into a large repository for management is feasible. Note that the number of services in large repo management is not determined by the number of people in the organizational structure. + +#### 2) When cooperation frequency is high between multiple teams + +If the number of business microservices is high, and interactions and collaborations between services are frequent, merging these services into a large repository for management can significantly improve collaboration efficiency. This typically occurs when microservices are within the same product line, across teams but not across centers or departments. Since this involves collaboration across multiple teams, it requires certain managerial authorization to drive. +:::tip +Microservice management is not only about code organization but also about organizational management. +::: +### 2. How Microservices Collaborate in a Large Repo + +#### 1) Management of Code Visibility + +The only thing that can be exposed between services is the interface, namely the `API`. The internal logic of each service should not be visible externally. Go language has a beneficial `internal` feature which can satisfy visibility management requirements. As shown in the large repo code example below, several services are managed under the `app` directory, each exposing its own `api` directory for direct reference by other services (enhancing collaboration efficiency), but the internal business logic is contained in the `internal` directory, not visible (and therefore inaccessible) to other services. + +![](/markdown/f9028ffb7bc51e7496f1d55b79091f73.png) + +#### 2) Service Interface Invocation + +Protocol files should be maintained separately in each service's directory. If the protocol file requires compilation, the compiled file should also be stored in its own service directory. The caller does not need to recompile and manage the target service's protocol files separately. For instance, with `HTTP API` interface definitions, the caller can directly reference the target service's `API` interface definition (in the following screenshot, `khaos-shark` is the caller, `khaos-oss` is the service provider). + +![](/markdown/b0035d25d52202b3f1b38d18980bf3ff.png) + +The same logic applies to `RPC` interface calls between microservices (in the following screenshot, `user-api` is the caller, `user-rpc` is the service provider). + +![](/markdown/f02efd1e4c03b3cb111cb7b9015290ee.png) + +#### 3) Strict Compatibility Requirements + +As introduced above, through large repo code management, all services within the large repo maintain consistent versions. When the service `API` relied upon is updated, the caller's service (using the `SDK`) will also automatically get updated. This requires all services within the repository to **strictly ensure interface compatibility**, otherwise, there may be issues with interface invocation: at best, the caller's service compilation fails requiring code adjustments, at worst, it compiles successfully but throws runtime errors affecting the business. Publicly shared large repo base components will also be affected by compatibility issues. + +Key points for ensuring compatibility in code design: + +- **Do not arbitrarily delete or modify interface parameters, parameter names, types, or validation logic.** +- **When an interface must undergo non-compatible updates, use interface versioning management (such as `v1, v2, v3...`).** +- **Public components should rely on stable and mature external components as much as possible. If a custom component is necessary, ensure the compatibility of exposed methods.** _For example: basic functions like `json.Marshal&Unmarshal` may be wrapped by some libraries/functions, but later users may not know or trust this function, leading to redundant rewrites. Over time, these libraries/functions become unmaintained._ + +### 3. Microservice Containerization Support under Large Repos + +#### 1) Unified Image Repository Management + +Scattered image repositories can reduce efficiency in service containerization management and maintenance. To facilitate unified service containerization management, we recommend using a unified image repository for services under a large repo. The image repository address is maintained in the tool configuration files for each service: + +![](/markdown/424878f3a64d0cca7899c6fd13a8b9c7.png) + +#### 2) Unified Compile, Submit Commands + +The framework provides commonly used commands to compile programs, build images, and submit images. + +- `make build` + +Compiles the program to generate binary files. + +For more information, please refer to the documentation: [Cross-Compiling](../../开发工具/交叉编译-build.md) + +- `make image` + +Compiles the program and builds the image, generating a `Docker` image. + +Use `make image TAG=xxx` to specify the tag name of the compiled image. + +For more information, please refer to the documentation: [Image Building](../../开发工具/镜像编译-docker.md) + +- `make image.push` + +Compiles the program, builds the image, and pushes it to the configured image repository. + +Use `make image.push TAG=xxx` to specify the tag name of the compiled image. + +#### 3) Unified Deployment, Debugging Commands + +The framework offers common commands for containerized deployment of `Kubernetes` clusters, as well as integrated compile-deploy development commands. + +- `make deploy TAG=xxx` + +Deploys the current service to a `kubernetes` cluster connected via local `kubeconfig`, where `TAG` is used to specify the `overlays` directory under the `deploy` directory. Deployment `yaml` file management uses the industry-standard `kustomize` tool. For detailed documentation, please refer to: [https://kubernetes.io/zh-cn/docs/tasks/manage-kubernetes-objects/kustomization/](https://kubernetes.io/zh-cn/docs/tasks/manage-kubernetes-objects/kustomization/) + +![](/markdown/353b86069be6e3cb8834aab4aad32e84.png) + +- `make image.push deploy TAG=xxx` + +This command is a development debugging directive used for compiling binaries, building and pushing `Docker` images, deploying `Kubernetes` applications, and restarting the application with a single command. + +### 4. Other Framework Commands under Large Repos + +The framework provides a wealth of tool command support for project engineering management. These commands often need to be executed in specific service directories, such as `./app/service-name` + +#### 1) `make cli` + +Used to upgrade the local framework `CLI` to the latest stable version. + +#### 2) `make up` + +Used to upgrade the local framework to the latest stable version in the community. + +For more information, please refer to the documentation: [Version Upgrade](../../开发工具/框架升级-up.md) + +#### 3) `make dao` + +Used to generate `DAO/Entity/DO` code files. + +For more information, please refer to the documentation: [Dao/Do/Entity Generating](../../开发工具/代码生成-gen/数据规范-gen%20dao.md) + +#### 4) `make service` + +Parses the `logic` directory and automatically generates internal call interfaces. In `Goland IDE`, this command is often used in conjunction with an automated `Watcher` file change to auto-generate content; see the official documentation for details. + +For more information, please refer to the documentation: [Service Generating](../../开发工具/代码生成-gen/模块规范-gen%20service.md) + +#### 5) `make enums` + +Used to parse specified code directories (default is `api` directory) and auto-generate `enums` load code. + +For more information, please refer to the documentation: [Enums Maintenance](../../开发工具/代码生成-gen/枚举维护-gen%20enums.md) + +#### 6) More Commands + +For more command support, please refer to the framework's official tools introduction section: [CLI Tool](../../开发工具/开发工具.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..8441b7acb4a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/design/project-models' +title: 'Data and Business Models' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, Data Model, Business Model, Interface Input/Output Model, Business Input/Output Model, ORM Component, DAO Operation, CLI Tool, Entity Model, Module Call] +description: "Data models and business models in the GoFrame framework, including data models for databases like MySQL, Redis, and input/output models for interface interactions. Detailed explanation of the definition and usage of business input/output models, with a special introduction to the special business model DO integrated with the ORM component to simplify DAO data access operations." +--- + +In the previous section [Code Layering](代码分层设计.md), the concept of "**model**" was mentioned. + +In this section, we will focus on introducing the definition and management of models in `GoFrame`. + +## 1. Data Model + +**Data Model**, also known as **Entity Model**, mainly comes from the data structure of the underlying persistent databases, such as `MySQL`, `Redis`, `MongoDB`, `Kafka`, etc. These data structures are maintained by third-party systems and can be identified through tools which automatically generate corresponding program data model codes. These data model codes are located in the `/internal/model/entity` directory. Developers do not need to manually maintain data models in the program. According to the `GoFrame` framework specification, data models are uniformly maintained using `CLI` tools, and codes are automatically generated. + +![](/markdown/0126798ec8cb70d798fc2260afb2f9a9.png) + +Example of Data Model + +## 2. Business Model + +The business model mainly includes two types: **Interface Input/Output Model** and **Business Input/Output Model**. + +### Interface Input/Output Model + +Interface Input/Output Models are used for interface interaction between systems/services, defined in the `api` interface layer, and can be called by all layers of the project, such as `controller, logic, model`. However, the `api` layer is only used for interface interaction with external services, and this model cannot call or reference internal models like `model`. In the `GoFrame` framework specification, these input/output model names are named in the format `XxxReq` and `XxxRes`. + +![](/markdown/8c037d2e08ddf5b8cb758cefd706b5ea.png) + +Example of Interface Input Model + +### Business Input/Output Model + +**Business Input/Output Models** are used for method call interactions **within** service modules/components, especially calls between `controller->service` or `service->service`. These models are defined in the `model` layer. In the `GoFrame` framework specification, these input/output model names are usually in the format `XxxInput` and `XxxOutput`. + +![](/markdown/b23a0dab9a4f4ac63c51c166248d9779.png) + +Example of Business Input Model and Business Output Model + +### Special Business Model `DO` + +In `GoFrame`, there is a special business model `DO`, which is intermediate between business models and data models and is mainly used to simplify `DAO` data access operations by leveraging the framework's powerful `ORM` component. + +![](/markdown/d08e7808de1c18c306e05157dd899992.png) + +DO is mainly used for DAO data access operations + +## 3. Other Models + +There are also internal private models used for module internal calls, such as models defined within each business module in `logic`, used for internal logic and not exposed to the outside. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..d132fd7594d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" @@ -0,0 +1,69 @@ +--- +slug: '/docs/design/project-struct-parameter' +title: 'Structured Programming' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame framework, structured programming, parameter management, code design, interface optimization, automated documentation generation, code maintenance, parameter validation, productivity improvement, error mechanism] +description: "How to improve code design through structured programming in the GoFrame framework, detailed analysis of unstructured problems encountered in the controller and service layers, and provides the advantages and examples of using structs to manage parameters. By structurally managing interface input and output, it simplifies parameter reception, validation, and conversion processes, boosts productivity, reduces maintenance costs, and facilitates easier interface documentation generation and standardized error handling mechanisms." +--- + +## I. Introduction + +Structured programming can be simply understood as passing and returning parameters by defining structs. + +We recommend using structured definitions to manage input/output when necessary, especially in the code design of the `controller` and `service` layers. + +### 1. Pain Points of Unstructured `controller` + +![](/markdown/e76d9687eb2d840494ce98a644e05d95.png) + +- It is difficult to determine the data structure of interface input/output. Most scenarios involve hard-coded parameter reception names in the code, prone to errors leading to unforeseen issues. +- Interface parameters often only define an `HttpRequest/HttpContext` object pointer, making it difficult to determine if the interface succeeded or failed. +- The processes of parameter reception, validation, and conversion are tedious. +- Generating and maintaining interface documentation is extremely challenging. + +### 2. Pain Points of Unstructured `service` + +![](/markdown/f8434f1243e4d9dace23021f0f2132a4.png) + +- When there are many method parameters, the definition is awkward, and usage is cumbersome. +- When the number and type of method parameters are uncertain, any arbitrary change (like adding a parameter) is non-compatible, leading to high modification costs. +- Method parameter annotations are inconvenient, resulting in most business projects lacking method parameter annotations. + +## II. Structured Programming + +### 1. Structured Improvements for `controller` + +**Advantages of Structuring:** + +- By structurally managing interface input/output parameters, hard-coding parameter names for reception is no longer needed, reducing maintenance costs and avoiding errors due to hard-coded parameter names. +- Enables automated parameter reception, conversion, and validation, thereby improving productivity. +- Standardizes interface writing. +- Makes interface management as convenient as ordinary function management, allowing error handling by returning `error` and standardizing the unified error mechanism. +- Automates interface documentation generation, ensuring synchronized maintenance of interface structure definitions and documentation. + +**Example of Structuring:** + +Structure Definition: + +![](/markdown/686ee75e775a1076387154615c40e868.png) + +Method Usage: + +![](/markdown/6f0cd9333bb1c514a1047c0e17024997.png) + +### 2. Structured Improvements for `service` + +**Advantages of Structuring:** + +- When there are many method parameters, structs elegantly manage these parameters. +- When the number and type of method parameters are uncertain, adding parameters is compatible with method invocation. +- Provides more convenient annotations for struct properties, enhancing code maintenance quality. + +**Example of Structuring:** + +![](/markdown/37a0eecf7f1c45bf99bdd98ec205eea0.png) + +## III. Precautions + +- When using structured management for input/output parameters in the `service` layer methods, any parameter within the struct will be considered optional. It's necessary to reasonably assess feasibility based on business scenarios. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..d2ff59c79c2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/design/interface-generic' +title: 'Interface and Generic' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, Interface, Generic Design, Framework Components, Flexibility, Extensibility, Adapter, gsession, Storage Implementation, Data Type Conversion] +description: "The basic concepts and applications of interface and generic design in the GoFrame framework, emphasizing the flexibility and extensibility brought by interface design, using generics to enhance parameter flexibility while simplifying usage complexity. In practical applications, flexible component interface layer design is achieved through Adapter with multiple default implementations to choose from." +--- + +## 1. Introduction + +Interface is a higher level of abstraction. The framework components are designed using interfaces as much as possible instead of providing specific implementations. The biggest advantage of interface design is that it allows users to customize implementations to replace the component's underlying interface layer, achieving strong flexibility and extensibility. + +## 2. Component Interface Design + +The core components of the `GoFrame` framework adopt an interface design. For example, the following image shows an overview of the interface implementation for some components: + +![](/markdown/f7c64eb343963d83adee0800a7774045.png) + +Most components use `Adapter` as the name of their interface layer. The current interface implementation can be set using the `SetAdapter` method and obtained using the `GetAdapter` method. Additionally, to enhance usability, components provide some default `Adapter` implementations for users to choose from. Taking the `gsession` component as an example: + +![](/markdown/5b6e3ff29277e5e5bd32707d9a29bf4c.png) + +The underlying interface is defined using `Storage`, with four implementations available: `File/Memory/Redis/RedisHashTable`. The default implementation is `File`. + +## 3. Interface and Generics + +The interface design of components offers high extensibility but needs to be combined with generics for more flexible usage. Again, taking the `gsession` component as an example, parameters are returned using generics, which allows for conversion to the corresponding data types as needed in business applications. + +### Increasing Parameter Flexibility, Simplifying Usage Complexity + +Without generics, interfaces would either need to provide methods for various types or return `interface{}` types, both of which can be complex. By uniformly returning through generic data types, parameter type flexibility is enhanced, significantly reducing usage complexity. + +![](/markdown/b8a2950b795cf7cb987cbfa7a305ff72.png) + +Generics support conversion to various types: + +![](/markdown/76fcb2211bfb4d98a88bdb9d1288b574.png) + +Convert to the corresponding data type according to business scenario needs. Type conversion utilizes the framework’s unified type conversion component, which prioritizes using assertions for type recognition to ensure conversion efficiency. + +![](/markdown/4605ec6822024dd52fe79ea75d6497d9.png) + +### Unified Usage Method, Shielding Underlying Impact + +For some complex type interface scenarios, the underlying implementation of the interface may involve external storage situations, generating serialization/deserialization operations, which may change/lose data types. Using generics can shield the impact of the underlying implementation through a unified usage method. For example, in the following example, no matter how the underlying `Session` implementation changes, the upper layer uses generics to convert to the target object through the `Scan` method. + +![](/markdown/de4f942e624d5e30a40f7d9d087c35fc.png) + +## 4. Notes + +Although the framework provides generic design, it is not recommended to widely use generics in business applications. The data structure design in the business layer, including interfaces and business model data structures, should be accurate and definite. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..ae060fd7c3b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" @@ -0,0 +1,19 @@ +--- +slug: '/docs/design' +title: 'Framework Design' +sidebar_position: 99 +hide_title: true +keywords: [GoFrame,GoFrame framework,framework design,design philosophy,inherent knowledge,component usage,unique design,techniques framework,understanding design,usage guide] +description: "The design philosophy of the GoFrame framework is its soul and an indispensable inherent knowledge for users. Compared to simple technique frameworks and component usage, GoFrame focuses more on guiding users to understand the design philosophy. Mastering GoFrame's unique design philosophy is equivalent to mastering the essence of the entire framework." +--- + +The design philosophy is the soul of the `GoFrame` framework, and for users, it is an indispensable inherent knowledge. + +Teaching someone to fish is better than giving them a fish. Compared to technique frameworks and component usage, we want to tell everyone why you should do this, why you should do that. + +`GoFrame` has its unique design philosophy; understanding the design philosophy of `GoFrame` means you understand the entirety of `GoFrame`. + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..2a2b7396963 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,189 @@ +--- +slug: '/docs/design/modular' +title: 'Modular Design' +sidebar_position: 0 +hide_title: true +keywords: [Modular Design, Software Decoupling, Code Reuse, GoFrame Framework, Golang Modules, Software Development Principles, Module Reuse, Compiled Language, Monorepo Package Design, Module Aggregation] +description: "The principles of software modular design and reuse, with a detailed explanation of principles like Reuse/Release Equivalency, Common Closure Principle, and Common Reuse Principle. Through the GoFrame framework's modular design case study, emphasis is placed on the importance of maintainability and the strategy for balancing module design. Modular design helps improve development efficiency and code quality, ensuring software stability and ease of maintenance." +--- + +In this chapter, we will first discuss some design and reuse principles of modularity in software design, and then introduce the modular design of the `GoFrame` framework to better understand the philosophy behind `GoFrame's` modular design. + +## I. What is a Module + +A **module**, also known as a **component**, is a unit of encapsulation for reusable functionality within a software system. The concept of a module may vary slightly at different levels of software architecture. At the development framework level, a module is the smallest unit of encapsulation for a specific type of functional logic. In the `Golang` codebase, we can also refer to a `package` as a module. + +## II. Goals of Modularity + +The purpose of modular design in software is to achieve as much **decoupling** and **reuse** of software functional logic as possible, with the ultimate goal of ensuring the efficiency and quality of software development and maintenance. + +## III. Principles of Module Reuse + +### `REP` Reuse/Release Equivalency Principle + +The **Reuse/Release Equivalency Principle** (`Reuse/Release Equivalency Principle`): The smallest granularity of software reuse should be equivalent to the smallest granularity of its release. + +In simple terms, if you want to reuse a piece of code, make it a separate module. + +### `CCP` Common Closure Principle + +The **Common Closure Principle** (`Common Closure Principle`): Classes that are modified for the same purpose should be placed in the same module. + +For most applications, the importance of **maintainability** far outweighs **reusability**. Code changes caused by the same reasons are best kept within the same module. If dispersed across multiple modules, the cost of development, submission, and deployment will increase. + +### `CRP` Common Reuse Principle + +The **Common Reuse Principle** (`Common Reuse Principle`): Do not force a module to depend on things it does not need. + +You might have experienced integrating module A, but module A depends on modules B and C. Even if you don't need modules B and C at all, you have to integrate them. This is because you're only using a portion of module A's capabilities, and the additional capabilities of module A bring extra dependencies. If following the Common Reuse Principle, you need to split A, retaining only the parts you need. + +### Competition Between Reuse Principles + +There is a **competition** between the principles of `REP`, `CCP`, and `CRP`. `REP` and `CCP` are **adhesive principles**, which make modules larger, while `CRP` is an **exclusive principle**, which makes modules smaller. Adhering to `REP` and `CCP` while ignoring `CRP` will lead to dependencies on many unused modules and classes, resulting in your module undergoing too many unnecessary releases due to changes in these modules or classes; adhering to `REP` and `CRP` while ignoring `CCP`, because the modules are split too narrowly, a change request might require changing `n` modules, incurring significant costs. + +![image](/markdown/bcfbb9253aefc770b284cc0c67ae68b8.png) + +Figure 2. Tension Diagram of Competition Between Module Reuse Principles + +A competent architect should be able to locate the most suitable position within the tension triangle region for the current state of the development team. For example, in the early stages of a project, `CCP` is more important than `REP`, and as the project develops, this optimal position should be constantly adjusted. + +## IV. Framework Module Design + +After the introduction of module design principles and reuse principles, we should have a general understanding of the principles of module development and management. Let's continue with the introduction of the framework's modular design, which will be relatively easy to understand. + +### Monorepo Package Design + +According to the `REP` principle, we understand that a reusable module supports independent version management, and such is the case for monorepo package design. There are many such monorepo packages in `Golang`, where each package is an independent module. According to the `CRP` principle, a monorepo package can be further refined and decoupled. Let's take a scenario of developing complex business projects, with common package dependencies like this: + +```go +module business + +go 1.16 + +require ( + business.com/golang/strings v1.0.0 + business.com/golang/config v1.15.0 + business.com/golang/container v1.1.0 + business.com/golang/encoding v1.2.0 + business.com/golang/files v1.2.1 + business.com/golang/cache v1.7.3 + business.com/framework/utils v1.30.1 + github.com/pkg/errors v0.9.0 + github.com/goorm/orm v1.2.1 + github.com/goredis/redis v1.7.4 + github.com/gokafka/kafka v0.1.0 + github.com/gometrics/metrics v0.3.5 + github.com/gotracing/tracing v0.8.2 + github.com/gohttp/http v1.18.1 + github.com/google/grpc v1.16.1 + github.com/smith/env v1.0.2 + github.com/htbj/command v1.1.1 + github.com/kmlevel1/pool v1.1.4 + github.com/anolog/logging v1.16.2 + github.com/bgses123/session v1.5.1 + github.com/gomytmp/template v1.3.4 + github.com/govalidation/validate v1.19.2 + github.com/yetme1/goi18n v0.10.0 + github.com/convman/convert v1.20.0 + github.com/google/uuid v1.1.2 + // ... +) +``` + +The module dependencies in the example are typical universal modules, commonly found in most business projects. The module addresses are fictional for demonstration purposes and may not actually exist. + +For those who have developed slightly complex business projects using `Golang`, such scenarios should be familiar. A typical software company often has at least hundreds of such projects, and the real module dependency relationships are far more complex than those in the example. In `Golang` project development, maintaining module dependencies is a significant challenge, and we often encounter pain points, including: + +- Numerous modules achieving the same functional logic, increasing selection cost +- Excessive module dependencies affecting a project's overall stability +- Excessive module dependencies causing confusion over whether to upgrade these modules +- Modules being scattered in design, lacking a unified structure. Refer to the section: [Unified Framework Design](统一框架设计.md) + +A case in point from personal experience. + +My company has dozens of self-developed modules, widely used across hundreds of business projects. On one occasion, we submitted bug fixes for several modules, two of which were notably critical. Immediately after, we required all business projects to upgrade their corresponding module versions with utmost caution. Of course, this wasn't a one-time instance, and similar scenarios are easy to imagine. + +Alternatively, we could choose not to actively push all business projects to upgrade modules; instead, projects only upgrade when encountering these bugs. Management's response to such a solution......is best imagined harmoniously. + +The primary cause of such issues is often the instability of modules, which require continuous iterations and improvements. Projects using these modules are inherently coupled, and changes in these modules inevitably affect related projects. The more foundational a module is, the broader the dependency from top-layer modules and the greater the impact. But even if a module stabilizes, risks still exist. `Golang's` standard library, widely considered stable, is continually evolving with improvements and bug fixes—just fortunate not to encounter them—posing relatively low risk. + +Good software design isn't static but capable of rapid adaptation to changes, allowing quick improvements. Module design and management are no different. Seeking ways to promptly refine module logic and effectively maintain module dependencies is more practical and efficient than merely developing more stable functional modules. + +### Module Aggregation Design + +`GoFrame's` approach to modular management leans more towards the `CCP` principle, valuing **maintainability** more than **reusability**. Since `GoFrame` is considered from the perspective of **development frameworks**, the overall framework design is top-down rather than point-wise. As mentioned, foundational modules have a wider impact due to their extensive dependencies on top-level modules. Therefore, the framework maintains core universal modules collectively, ensuring closure and stability of foundational modules, enhancing development efficiency and maintainability, and reducing integration and maintenance costs through unified version management. + +From the standpoint of `GoFrame's` modular design, the dependency scenario in the earlier example should resemble the following: + +```go +module business + +go 1.16 + +require ( + github.com/gogf/gf v1.16.0 + github.com/goorm/orm v1.15.1 + github.com/goredis/redis v1.7.4 + github.com/gokafka/kafka v0.1.0 + github.com/google/grpc v1.16.1 + // ... +) +``` + +`GoFrame` maintains only common core modules, while non-core universal modules or those with high stability are still recommended to use as monorepo packages, as advocated by `REP` and `CRP` principles of module reuse. Under this design pattern: + +- The framework's core maintains a comprehensive set of universal foundational modules, reducing the cost of choosing foundational modules. +- We only need to maintain a unified framework version, not dozens of module versions. +- We need to understand only one framework's changes, not changes across dozens of modules. +- Only one framework version requires upgrading, not multiple module versions. +- It reduces developers' cognitive load, enhances module maintainability, and makes maintaining module version consistency easier across projects. + +## V. Common FAQs + +### 1. Although each module is designed with low coupling, even when modules can be selectively included, you still have to download the complete framework code. + +This is not an issue for compiled languages, as **source file downloads at the file level are not directly related to logical coupling between modules**. The root cause of this issue stems from differing perspectives on understanding **compiled languages** versus **interpreted languages**. In the internet age, interpreted languages have thrived, while Golang stands distinctively popular yet unique in this era. + +![image](/markdown/7b9d58a737b0340d95454312801c3c0e.png) + +- **Compiled Languages**: (taking static compilation as an example) typically start from the `main` package as the entry point. The compiler automatically analyzes the source code and compiles and processes resources of all logically dependent modules to ultimately generate static binary files for release. Source files, including those of dependent modules (logical dependencies), are used only during the compilation phase and are not directly relied upon for release, such as C/C++, Golang, Rust, etc. +- **Interpreted Languages**: typically package their own source files (or bytecode) along with the source files (or bytecode) of dependent modules for release, e.g., PHP, Java, NodeJS, Python, etc. In this case, the size of dependent module source code significantly impacts project release. Furthermore, module dependencies encoded in package configuration files result in all specified modules being included during packaging, regardless of logical dependencies. If a module contains 100,000 functions and only one function is utilized, all functions within that module are packed for release, as interpreted languages do not undergo "compile-assemble-link" stages before deployment, requiring full parsing at runtime for both source code and module dependencies. Particularly for those transitioning from PHP/Java to Go, this mindset needs adaptation. + +### 2. Will the release frequency of the framework increase if the version change of any module in the framework triggers a framework release? + +Certainly, the module design of the framework considers stability factors, organizing only **common core modules** according to `CCP` and avoiding specific business logic encapsulations, as such implementations add variance to framework modules. + +Under conditions ensuring a degree of stability, module version releases adhere to the framework's unified iterative development schedule. Aside from necessary `hot fixes`, version releases happen in fixed time windows to ensure the core framework's stability. Therefore, managing module versions via aggregation does not increase the release frequency of the framework; rather, it decreases it, making module versions within the framework more stable. + +### 3. The framework aggregates and maintains core universal modules; what constitutes a core universal module? + +First, they are **foundational modules**, typically residing at the lowest level of the module dependency chain and having the greatest stability impact on projects. + +Second, the vast majority of projects (the 80-20 rule) would rely on common foundational modules, which can be considered core modules. + +Finally, these modules do not encompass specific **business logic implementations**. Counter-examples include modules related to WeChat official accounts/weapp, CMS/CRM, blockchain, etc., which are specific business logic packages. +:::tip +A fully accurate assessment of module universality is unattainable. To keep the core concise, the framework adopts a conservative stance and iteratively adjusts based on actual needs. +::: + +Here is a reference for module layering: + +![image](/markdown/f48e08aa60bb126bb41953bcbe98b438.png) + +Reference for Module Layering + +**Business Implementation Modules**: Logic implementation for specific business projects, including further code layering of the business project. + +**Common Business Modules**: Reusable business logic encapsulations, e.g., WeChat official accounts/weapp, CMS/CRM, blockchain-related logic encapsulation modules. + +**Universal Base Modules**: Foundational modules not provided or extended based on the standard library, such as configuration, validation, caching, ORM, I18N, etc. + +**Standard Base Modules**: Golang's standard library. + +### 4. Since the framework includes many modules, with limited human resources, I believe each module couldn't be better than individual single-package projects on GitHub. + +Doing something less frequently doesn't inherently ensure better quality; there's no direct causality between the two. + +### 5. Because the framework includes numerous modules, I think the performance of each module is generally not high. + +Haha. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..216c10eb251 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" @@ -0,0 +1,104 @@ +--- +slug: '/docs/design/unified' +title: 'Unified Framework Design' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Development Framework, Software Architecture, Technical Systematization, Development Standardization, Component Unification, Version Consistency, Solution Precipitation, Resource Waste Avoidance] +description: "The unified development framework is key to software architecture and code development. Through technical systematization, development standardization, component unification, and version consistency, it achieves high coordination and maintainability, avoids resource waste, and helps development teams focus on business itself. This framework offers efficient error stack tracing capabilities and possesses strong combat power and cohesion, providing the foundation for a virtuous cycle between enterprises and the community. The GoFrame framework achieves these features and is an essential tool for modern software development." +--- + +The software industry is quite similar to the construction industry. If we say our product is a tall building, then program code is the bricks used to build it (our daily work is like "moving bricks" continuously). If software architecture is seen as the overarching plan, then program code is the key component for accurately implementing software architecture. + +![](/markdown/8f8075c3f449ab501c9d25ce5050db52.png) + +Given the importance of program code, the importance of development frameworks goes without saying. Development frameworks focus on the code level to solve general technical issues, aiming to allow developers to focus on the business itself, facilitate quick responses to business changes, and improve the overall efficiency of software development and maintenance. + +This chapter mainly introduces the significance and necessity of building a unified development framework. + +> Before starting this chapter, it is recommended that you understand the framework's: [Modular Design](模块化设计.md). Part of the inspiration and insights are from: [Xiaoma's Experience Sharing](../../community/社区投稿/Golang框架选型比较_goframe_beego_iris和gin.md). + +## 1. Technical Systematization + +![](/markdown/2b04e46ddf26d0d9233f84c9ba69c6f3.png) + +Systematization focuses more on the overall combat power of the framework rather than each module itself. + +Significant features of systematic framework design: + +- Comprehensive system, rich components +- Unified standards, consistent style +- Unified abstraction, tight design +- Efficient execution, no redundant logic + +The systematization here refers to a top-down unified design of the micro-level code development framework, making the overall framework design thought integrated rather than scattered. Technically, solving a specific problem is relatively straightforward, and developing a specific module is relatively easy. However, abstracting and consolidating common issues, organizing independent modules according to a unified design philosophy, and generating strong overall combat power is not a simple task. This requires the framework designer to have a certain technical background, experience accumulation, vision, and foresight, rather than just focusing on individual modules. + +For example, even if we have never developed a framework, we should have used one to some extent and know what modules a framework should at least include. When we need to write logs, we know this component framework must provide, so we look for it in the framework and get usage help from the official website. When we need WebServer, database access, template engines, etc., we can also expect such components to be provided by the framework, so we look for them in the framework and get usage help from the official website. + +Additionally, when using various modules in the framework, although each module is designed to be loosely coupled for selective use, we find that their configuration management methods are consistent. They use structured configuration management objects, the same configuration management module, and fixed configuration item-to-object attribute mapping rules. Getting and setting are done through methods prefixed with `Get`/`Set` (all component parameter retrievals and settings are also `Get`/`Set` methods). Global environment variables and startup parameter settings are similarly managed. This enables developers to quickly grasp the framework's behavior, facilitating quick integration and reducing learning costs. + +Furthermore, a great feature at the framework level is the full error stack feature, where all component `errors` return with error stacks, allowing top-level business to quickly locate problems through error stacks when errors occur. Currently, only the `GoFrame` framework in the `Golang` development language provides this capability. + +These are just a few simple examples. If you're interested, you can discover more intriguing points in the framework. + +Finally, we can ponder, why can we subconsciously understand the framework's behavior, and why does the framework offer high convenience and low integration costs, with modules having high organizational coordination despite a "high cohesion, low coupling" design philosophy? This phenomenon is due to whether a framework uses systematized design or is cobbled together. + +::: tip +A fitting analogy: `GoFrame` is a highly disciplined, cohesive, and effective "regular army," not a "scattered team" that's "cobbled together." +::: + +## 2. Development Standardization + +![](/markdown/5f76d7bd6d1a06dce9641fec0c497b77.png) + +Code level also requires a series of development standards, such as basic code structure, layering models, encapsulation design, etc. For specifics, please refer to: [Engineering Design 🔥](工程开发设计/工程开发设计.md). A unified framework design ensures that all business projects are coded following a unified code design, forming uniform development standards. In addition, the framework's development toolchain makes it easier to quickly promote and implement development standards: [CLI Tool](../开发工具/开发工具.md). + +## 3. Component Unification + +![](/markdown/19cac91617dc457b461391e208b675b3.png) + +> Unification here has two layers: +> +> - Multiple identical function components unified into one component. +> - Multiple different function components unified into framework management. + +Another pain point is the flourishing development of components: + +- Multiple modules implementing the same function logic, increasing choice costs +- Excessive modules dependencies, affecting project stability +- Projects struggle whether to upgrade these module versions due to numerous dependencies +- Different modules depending on different versions of the same third-party module, causing version compatibility issues +- Isolated module design, making each module's replaceability high individually; hard to establish development systems and unify standards + +![](/markdown/1c16c5ec1bae23caaf9509673f782d0a.png) + +Only a unified development framework can bring independent modules from " **each going its own way**" to " **unified management**": + +- Framework designed from top-down, forming a systematic and unified module design, facilitating the implementation of standardized development +- Core framework maintains comprehensive common basic modules, reducing costs of basic module selection +- We only need to maintain a unified framework version, not dozens of module versions +- We only need to understand changes in one framework, not dozens of modules +- During upgrades, only one framework version needs upgrading, not dozens of module versions +- Unified modular design reduces unnecessary logic implementation, improving module performance and usability +- Reduces mental load on developers, improving module maintainability, and making it easier to ensure module version consistency across business projects + +## 4. Version Consistency + +Version consistency issues mainly arise from excessive project module dependencies and versions, making it hard to maintain and upgrade versions uniformly. After the framework unifies module management, it's easier to ensure project module version consistency. However, note that this consistency is not strong consistency; it merely reduces module and version maintenance complexity, but inconsistency issues still exist. The pain points and improvements were introduced in previous chapters, and you may refer to: [Modular Design](模块化设计.md). No further details here. + +> There are some **version strong consistency** code management solutions in the industry, such as using `Monorepo` **large repository** code management. Each has its pros and cons, which you may explore yourself, and there's no further elaboration here. + +## 5. Solution Precipitation + +![](/markdown/642e90cfc4809a4f237073c7e80f25d5.png) + +Based on a unified development framework, it's easier to form solution precipitation, creating a virtuous cycle between enterprises and the community. Solution precipitation prioritizes adopting tools and code forms over documentation. + +## 6. Avoid Resource Waste + +When every team tries to create their own wheels, not only does it fail to form a unified development standard, but it also leads to significant resource waste. + +> This phenomenon is quite evident in the early days of `Golang` or when an emerging company’s technical system is underdeveloped. + +![](/markdown/fb5e4135a82ff9ca41c79db9a4c6b89c.jpeg) + +Getting project teams to focus more on business is believed to be the consensus among most tech companies. A unified development architecture abstracts common technical problems and forms general solutions, avoiding each project independently tackling the multitude of encountered technical challenges, effectively freeing up focus. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 00000000000..1a8924c01f8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/design/initialization' +title: 'Implicit and Explicit Init' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Implicit Initialization, Explicit Initialization, Golang, Server Configuration, Database Configuration, Module Initialization, Error Localization, Initialization Logic] +description: "Initialization operations that need to be performed when the program starts, including implicit and explicit initialization in the GoFrame framework. Implicit initialization is implemented through the package's init method, but may cause the program to fail to start. It is recommended to call for complex initialization explicitly. Explicit initialization is preferable in business development to ensure maintainability of the program." +--- + +We know that some "initialization" logical operations need to be executed when the program starts, such as: `Server` configuration, various database (`MySQL`, `Redis`, `Kafka`, etc.) configurations, business object configurations, etc. In most scenarios, we have two initialization methods: implicit initialization and explicit initialization. + +## 1. Implicit Initialization +:::warning +Special Note: In `Golang v1.21` and later versions, the execution order of the `init` initialization has changed and may cause problems for packages that rely on `init` to execute initialization logic. Therefore, it is not recommended to execute **complex initialization logic** in `init`. **It is recommended to implement complex module initialization logic through explicit invocation.** +::: +Implicit initialization is generally executed through the package initialization method `init`. It should be noted that if there is a possibility of errors in the initialization logic, since errors in the `init` method cannot be caught by the upper layer, if initialization fails, the program startup is often directly terminated. For example: + +![](/markdown/9190e5a8e2acf34a70442c6814a52327.png) + +When implicit initialization fails, it often directly terminates the program startup. + +The advantage of implicit initialization is that it does not require manually calling the initialization method, hiding initialization details from developers, and thus developers have no cognitive burden. However, the downside is also the same: developers do not know the initialization details, so once an error occurs, it is difficult to quickly pinpoint the cause. Therefore, when using implicit initialization, it is often required to print detailed errors and stack information when initialization errors occur to facilitate error localization. + +Many modules in the `GoFrame` framework use implicit initialization to hide module initialization details and reduce developers' cognitive burden. For example: + +![](/markdown/d019031d40a93f6318a933271d63c503.png) + +Implicit initialization design is common in modules of `GoFrame`. + +![](/markdown/b0b839a86595ee57f2c5a1b39c559df0.png) + +The `main` package using the `GoFrame` framework implicitly `imports`. + +The initialization process of the package's `init` method: + +![](/markdown/40b3b7c2b75dcb36be348c840ca0eb3e.png) + +## 2. Explicit Initialization + +Explicit initialization requires the developer to call specific methods to perform initialization operations when the program starts, such as in the `main` or `boot` module. Generally speaking, the initialization of basic components tends to use implicit initialization more, because users do not care about the initialization logic of underlying basic modules, while the initialization of business modules is mostly done using explicit initialization. For example: + +![](/markdown/0124c249f03cd1f9fd78fe0970ffbda6.png) Explicit initialization executed sequentially in the `boot` package. + +![](/markdown/8417caae0e203d44d43c6bca369b3023.png) In the `main` package, the `boot.Boot()` method is called to execute initialization. + +## 3. How to Choose + +In business scenarios, unless specially necessary, we recommend using **explicit initialization** to ensure better maintainability. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..02ac2b53bc1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/i18n' +title: 'I18N' +sidebar_position: 10 +hide_title: true +keywords: [I18N, Internationalization, Localization, Translation, Language Support, Multilingual, GoFrame, GoFrame Framework, Open Source Framework, Extensible Component] +description: "The I18N component is an important module in the GoFrame framework, providing internationalization and localization support to help developers implement multilingual websites or applications. With the I18N component, users can experience software functionality more smoothly in different language environments, enhancing the usability for global users." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" new file mode 100644 index 00000000000..cdad6a4c4d6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/i18n-gi18n' +title: 'I18N' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Internationalization, i18n, gi18n, Multilingual Support, Web Development, Framework, Software Localization, Open Source] +description: "When building a website using the GoFrame framework, we provide a powerful internationalization support module, gi18n, designed to simplify the implementation of multilingual web applications. In this way, developers can more efficiently meet the language needs of global users, enhancing user experience and product acceptability." +--- + +For details, please refer to the section: [I18N](../../核心组件/I18N国际化/I18N国际化.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" new file mode 100644 index 00000000000..280cfae6d51 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/contrib-nosql-redis' +title: 'NoSQL Redis' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame framework, gredis component, Redis client, connection pool design, command channel, Redis operations, GoFrame documentation, interface design, NoSQL Redis, community component] +description: "Implementation of NoSQL Redis client in the GoFrame framework, mainly through the gredis component for Redis operations, using connection pool design and command channel method, ensuring the component's universality and scalability. This article provides installation and reference guidelines, emphasizing gredis's notable features and linking to the relevant interface documentation. Developers can implement over 100 common methods through community components and support various advanced features, including cluster operations." +--- + +## Introduction + +The `Redis` client is implemented by the `gredis` component, which adopts a connection pool design at the bottom layer. +:::tip +To ensure universality and scalability, the `gredis` component uses the **command channel** method to execute `Redis` operations. When you are unsure how to pass parameters for the command channel, you can refer to the parameter passing of the terminal command line. In other words, all operations remain consistent with the parameter passing of command lines. +::: +**Usage:** + +Installation: + +```bash +go get -u github.com/gogf/gf/contrib/nosql/redis/v2 +``` + +Reference: + +```go +import ( + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + // other imported packages. +) +``` + +**Interface Documentation:** + +- [https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis](https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis) +- [https://github.com/gogf/gf/tree/master/contrib/nosql/redis](https://github.com/gogf/gf/tree/master/contrib/nosql/redis) + +**Brief Introduction:** + +`gredis` uses a connection pool to manage `Redis` connections. You can manage the connection pool properties through the `Config` configuration object or the `Set*` methods, and obtain pool statistics through the `Stats` method. `gredis` uses an interface-based design to decouple the underlying dependency on `redis`. It implements over `100+` common methods using a community component approach and provides a grouping method for managing interfaces. +:::warning +The `gredis.Redis` client object provides a `Close` method, which is used to close the `Redis` client (and also the client's connection pool), not the connection object. Developers generally will not use it; non-advanced users should not use it. +::: + +## Features + +`gredis` has the following notable features: + +- Easy to use, powerful features +- Unified configuration for components +- Provides community component implementation of `100+` common methods +- Supports both single instance and cluster operations +- Supports all features of `Redis` services +- Supports `OpenTelemetry` observability +- Supports singleton objects and dynamic object creation +- Interface design with high flexibility and extensibility + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..342c39aef56 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" @@ -0,0 +1,67 @@ +--- +slug: '/docs/components/contrib-nosql-redis-conn' +title: 'Redis - Conn' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Redis, Conn Object, Publish/Subscribe, Connection Pool, Long Connection, Connection Timeout, Subscription Mode, Publish Mode] +description: "Use the Conn object of Redis in the GoFrame framework for long connection operations, such as publish/subscribe functions. Obtain the connection object through a connection pool for operations, while paying attention to connection object timeout issues and closure operations after use. The example code demonstrates implementing the publish/subscribe pattern through Conn, with the program printing data obtained from the Redis Server on the terminal." +--- + +## `Conn` Object + +If you need to implement long connection operations with `Redis` (such as publish/subscribe), you can use the `Conn` method to obtain a connection object from the connection pool, and then use that connection object for operations. It's important to note that when the connection object is no longer in use, it should be explicitly closed by calling the `Close` method (returned to the connection pool). +:::warning +Since the `Conn` is a connection object, be aware that it is subject to connection timeout limits, which relate to server configuration. +::: +## Publish/Subscribe + +You can implement the publish/subscribe pattern through `Redis`'s `Conn`. + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + channel = "channel" + ) + conn, _ := g.Redis().Conn(ctx) + defer conn.Close(ctx) + _, err := conn.Subscribe(ctx, channel) + if err != nil { + g.Log().Fatal(ctx, err) + } + for { + msg, err := conn.ReceiveMessage(ctx) + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(msg.Payload) + } +} +``` + +After execution, the program will block and wait to receive data. + +Open another terminal and use the `redis-cli` command to enter the `Redis Server` to publish a message: + +```bash +$ redis-cli +127.0.0.1:6379> publish channel test +(integer) 1 +127.0.0.1:6379> +``` + +The program terminal will then immediately print the data obtained from the `Redis Server`: + +```test +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..d3f12beea85 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" @@ -0,0 +1,194 @@ +--- +slug: '/docs/components/contrib-nosql-redis-example' +title: 'Redis - Examples' +sidebar_position: 1 +hide_title: true +keywords: [Redis example, GoFrame framework, NoSQL database, Set operation, Get operation, SetEx operation, HSet operation, HMSet operation, HGetAll operation, HMGet operation] +description: "Examples of performing basic operations in Redis using the GoFrame framework, including Set/Get, SetEx, HSet/HGetAll, and HMSet/HMGet operations. These code examples demonstrate how to store and retrieve data through the Redis module of the GoFrame framework, suitable for beginners to learn how to implement Redis functions in the GoFrame framework environment. This example also reminds users that HMSET has been deprecated in Redis version 4.0.0 and above; HSET should be used." +--- + +## `Set/Get` Operation + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + _, err := g.Redis().Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +After execution, terminal output: + +```value +``` + +## `SetEx` Operation + +```go +package main + +import ( + "fmt" + "time" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + err := g.Redis().SetEX(ctx, "key", "value", 1) + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.IsNil()) + fmt.Println(value.String()) + + time.Sleep(time.Second) + + value, err = g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.IsNil()) + fmt.Println(value.Val()) +} +``` + +After execution, terminal output: + +```false +value +true + +``` + +## `HSet/HGetAll` Operation + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key = "key" + ) + _, err := g.Redis().HSet(ctx, key, g.Map{ + "id": 1, + "name": "john", + "score": 100, + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // retrieve hash map + value, err := g.Redis().HGetAll(ctx, key) + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.Map()) + + // scan to struct + type User struct { + Id uint64 + Name string + Score float64 + } + var user *User + if err = value.Scan(&user); err != nil { + g.Log().Fatal(ctx, err) + } + g.Dump(user) +} +``` + +After execution, terminal output: + +``` +map[id:1 name:john score:100] +{ + Id: 1, + Name: "john", + Score: 100, +} +``` + +## `HMSet/HMGet` Operation + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key = "key" + ) + err := g.Redis().HMSet(ctx, key, g.Map{ + "id": 1, + "name": "john", + "score": 100, + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // retrieve hash map + values, err := g.Redis().HMGet(ctx, key, "id", "name") + if err != nil { + g.Log().Fatal(ctx, err) + } + g.Dump(values.Strings()) +} +``` + +After execution, terminal output: + +``` +[ + "1", + "john", +] +``` + +As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" new file mode 100644 index 00000000000..5ca60cf40d4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" @@ -0,0 +1,115 @@ +--- +slug: '/docs/components/contrib-nosql-redis-do-and-serialization' +title: 'Redis - Do' +sidebar_position: 2 +hide_title: true +keywords: [Redis Command Interaction, Do Method, Automatic Serialization and Deserialization, GoFrame Framework, Struct Access, Map Access, gredis Library, JSON Serialization, Redis API, Go Language] +description: "In applications built with the GoFrame framework, interact with Redis commands and automatically serialize and deserialize data. First, we explain the powerful extensibility of the Do method, which allows the execution of any Redis command. Then we show how to use map and struct to access and store data and simplify programming with JSON serialization. By combining the GoFrame framework and Redis, developers can manage data more efficiently." +--- + +## `Do` Method + +`Do` is a universal command interaction method that executes synchronous instructions by sending corresponding `Redis API` commands to the `Redis Server` to utilize the `Redis Server` services. The biggest feature of the `Do` method is its strong extensibility by interacting with the server using `Redis` commands, which can implement other commands not provided by the `Redis` operation methods. Usage example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + ) + v, _ := g.Redis().Do(ctx, "SET", "k", "v") + fmt.Println(v.String()) +} +``` + +## Automatic Serialization/Deserialization + +When the given parameters are `map`, `slice`, or `struct`, `gredis` internally supports automatic `json` serialization and can use the conversion functions of `gvar.Var` for deserialization when reading data. + +### `map` Access + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + err error + result *gvar.Var + key = "user" + data = g.Map{ + "id": 10000, + "name": "john", + } + ) + _, err = g.Redis().Do(ctx, "SET", key, data) + if err != nil { + panic(err) + } + result, err = g.Redis().Do(ctx,"GET", key) + if err != nil { + panic(err) + } + fmt.Println(result.Map()) +} +``` + +### `struct` Access + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Id int + Name string + } + + var ( + ctx = gctx.New() + err error + result *gvar.Var + key = "user" + user = g.Map{ + "id": 10000, + "name": "john", + } + ) + + _, err = g.Redis().Do(ctx, "SET", key, user) + if err != nil { + panic(err) + } + result, err = g.Redis().Do(ctx, "GET", key) + if err != nil { + panic(err) + } + + var user2 *User + if err = result.Struct(&user2); err != nil { + panic(err) + } + fmt.Println(user2.Id, user2.Name) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..912f61ef142 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/components/contrib-nosql-redis-interface' +title: 'Redis - Interface' +sidebar_position: 4 +hide_title: true +keywords: [Redis, Interface Design, GoFrame, GoFrame Framework, gredis, Custom Redis Adapter, Extend Redis Methods, Redis Community Component, SetAdapter Method, GetAdapter Method] +description: "Implement an interface-designed Redis component using gredis in the GoFrame framework, which has strong flexibility and extensibility. By implementing a custom Redis Adapter, you can easily override the default implementation methods. The text provides detailed examples showing how to achieve log printing in the custom Do method and use it in business operations." +--- + +`gredis` adopts an interface-based design, offering strong flexibility and extensibility. + +## Interface Definition + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis#Adapter](https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis#Adapter) + +## Related Methods + +```go +// SetAdapter sets custom adapter for current redis client. +func (r *Redis) SetAdapter(adapter Adapter) + +// GetAdapter returns the adapter that is set in current redis client. +func (r *Redis) GetAdapter() Adapter +``` + +## Custom Redis Adapter + +The framework community component provides a default implementation of the `Redis Adapter`. If developers need to implement a custom `Redis Adapter` or want to override certain methods, they can extend based on this implementation. + +Let's look at an example where we implement a custom `Redis Adapter` and override its underlying `Do` method. To simplify the example, we print a log in the custom `Do` method, and subsequent logic follows the community `Redis Adapter` implementation. + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + ctx = gctx.New() + group = "cache" + config = gredis.Config{ + Address: "127.0.0.1:6379", + Db: 1, + } +) + +// MyRedis description +type MyRedis struct { + *redis.Redis +} + +// Do implements and overwrites the underlying function Do from Adapter. +func (r *MyRedis) Do(ctx context.Context, command string, args ...interface{}) (*gvar.Var, error) { + fmt.Println("MyRedis Do:", command, args) + return r.Redis.Do(ctx, command, args...) +} + +func main() { + gredis.RegisterAdapterFunc(func(config *gredis.Config) gredis.Adapter { + r := &MyRedis{redis.New(config)} + r.AdapterOperation = r // This is necessary. + return r + }) + gredis.SetConfig(&config, group) + + _, err := g.Redis(group).Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis(group).Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +After execution, the terminal outputs: + +``` +MyRedis Do: Set [key value] +MyRedis Do: Get [key] +value +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..6ccd20ef651 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,169 @@ +--- +slug: '/docs/components/contrib-nosql-redis-config' +title: 'Redis - Configuration' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, gredis, Redis configuration, configuration management, configuration file, singleton object, cluster configuration, modularization, configuration method] +description: "Use the gredis component in the GoFrame framework for Redis configuration management. We recommend managing Redis configurations through configuration files, supporting both single instance and cluster configurations. Additionally, detailed instructions on various configuration items are provided, along with related code examples for reference." +--- + +The `gredis` component supports two methods for managing `redis` configurations and obtaining `redis` objects: one is through the **configuration component + singleton object** approach; the other is modularized using **configuration management methods** and object creation methods. + +## Configuration File (Recommended) + +In most cases, it is recommended to use the `g.Redis` singleton approach to operate `redis`. Therefore, using configuration files to manage `Redis` configurations is also recommended. An example configuration in `config.yaml` is as follows: + +### Single Instance Configuration + +``` +# Redis Configuration Example +redis: + # Single Instance Example 1 + default: + address: 127.0.0.1:6379 + db: 1 + + # Single Instance Example 2 + cache: + address: 127.0.0.1:6379 + db: 1 + pass: 123456 + idleTimeout: 600 +``` + +Here, `default` and `cache` represent configuration group names. In the program, you can obtain the corresponding `redis` singleton object using this name. If no group name is passed, the `redis.default` configuration group is used by default to obtain the corresponding `redis` client singleton object. + +### Cluster Configuration + +``` +# Redis Configuration Example +redis: + # Cluster Mode Configuration Method + default: + address: 127.0.0.1:6379,127.0.0.1:6370 + db: 1 +``` + +### Configuration Item Description + +| Configuration Item | Required | Default | Description | +| --- | --- | --- | --- | +| `address` | Yes | - | Format: `Address:Port`
    Supports `Redis` single instance mode and cluster mode configuration, using `,` to separate multiple addresses. For example:
    `192.168.1.1:6379, 192.168.1.2:6379` | +| `db` | No | `0` | Database index | +| `user` | No | `-` | Authorized user for access | +| `pass` | No | `-` | Authorized password for access | +| `minIdle` | No | `0` | Minimum number of idle connections allowed | +| `maxIdle` | No | `10` | Maximum number of idle connections allowed (`0` means no limit) | +| `maxActive` | No | `100` | Maximum connection limit (`0` means no limit) | +| `idleTimeout` | No | `10` | Maximum idle time for connections, using a time string such as `30s/1m/1d` | +| `maxConnLifetime` | No | `30` | Maximum lifetime for connections, using a time string such as `30s/1m/1d` | +| `waitTimeout` | No | `0` | Timeout for waiting on a connection pool, using a time string such as `30s/1m/1d` | +| `dialTimeout` | No | `0` | Timeout for `TCP` connection, using a time string such as `30s/1m/1d` | +| `readTimeout` | No | `0` | Timeout for `TCP` `Read` operation, using a time string such as `30s/1m/1d` | +| `writeTimeout` | No | `0` | Timeout for `TCP` `Write` operation, using a time string such as `30s/1m/1d` | +| `masterName` | No | `-` | Used in Sentinel mode, set `MasterName` | +| `tls` | No | `false` | Whether to use `TLS` authentication | +| `tlsSkipVerify` | No | `false` | Whether to disable server name verification when connecting via `TLS` | +| `cluster` | No | `false` | Whether to force setting as cluster working mode. When `address` is a single endpoint cluster, the system will automatically determine it as single instance mode, in which case this should be set to `true`. | +| `protocol` | No | `3` | Set the `RESP` protocol version for communication with `Redis Server`. | +| `sentinelUsername` | No | | Account for `Sentinel` mode | +| `sentinelPassword` | No | | Password for `Sentinel` mode | + +Usage Example: + +`config.yaml` + +``` +# Redis Configuration Example +redis: + # Single Instance Example 1 + default: + address: 127.0.0.1:6379 + db: 1 + pass: "password" # Set password here, remove if not needed +``` + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + _, err := g.Redis().Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +After execution, the output is: + +```value +``` + +## Configuration Method (Advanced) + +Since `GoFrame` is a modular framework, apart from using the coupled and convenient `g` module to automatically parse configuration files and obtain singleton objects, it also supports modular use of the `gredis` package by capable developers. + +`gredis` provides global group configuration capabilities, with related configuration management methods as follows: + +```go +func SetConfig(config Config, name ...string) +func SetConfigByMap(m map[string]interface{}, name ...string) error +func GetConfig(name ...string) (config Config, ok bool) +func RemoveConfig(name ...string) +func ClearConfig() +``` + +Usage Example: + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + config = gredis.Config{ + Address: "127.0.0.1:6379", + Db: 1, + Pass: "password", + } + group = "cache" + ctx = gctx.New() +) + +func main() { + gredis.SetConfig(&config, group) + + _, err := g.Redis(group).Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis(group).Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" new file mode 100644 index 00000000000..2c0e569b292 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/debug' +title: 'Debugging' +sidebar_position: 9 +hide_title: true +keywords: [function debugging, debugging tools, code debugging, error troubleshooting, GoFrame, GoFrame framework, developer tools, debugging techniques, performance optimization, problem solving] +description: "Using the GoFrame framework for function debugging. By providing effective debugging tools and methods, it helps developers quickly identify and resolve errors in code, improving development efficiency and performance." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" new file mode 100644 index 00000000000..56ea3f8c0d9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/components/debug-gdebug' +title: 'Debugging' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, debugging functionality, gdebug, GoFrame debugging, gdebug component, stack analysis, call chain information, performance optimization, API documentation] +description: "The GoFrame framework provides rich debugging functionality through the gdebug component, suitable for stack and call chain analysis in the development environment. Although debugging methods are not strongly related to performance efficiency, they can help developers better understand code execution paths and call information." +--- + +The `goframe` framework offers rich debugging features implemented by the `gdebug` component. +:::warning +The so-called "debugging" methods mostly relate to the development environment, including stack and call chain information analysis, and performance is often not particularly high. +::: +**Usage:** + +```go +import "github.com/gogf/gf/v2/debug/gdebug" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/debug/gdebug](https://pkg.go.dev/github.com/gogf/gf/v2/debug/gdebug) + +**Method List:** + +```go +func BinVersion() string +func BinVersionMd5() string +func Caller(skip ...int) (function string, path string, line int) +func CallerDirectory() string +func CallerFileLine() string +func CallerFileLineShort() string +func CallerFilePath() string +func CallerFunction() string +func CallerPackage() string +func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) +func FuncName(f interface{}) string +func FuncPath(f interface{}) string +func GoroutineId() int +func PrintStack(skip ...int) +func Stack(skip ...int) string +func StackWithFilter(filter string, skip ...int) string +func StackWithFilters(filters []string, skip ...int) string +func TestDataPath(names ...string) string +``` + +> Those familiar with `PHP` might understand this better, as some of these methods are actually similar to certain [magic constants](https://www.php.net/manual/en/language.constants.predefined.php) in PHP. `CallerDirectory` corresponds to `__DIR__`, `CallerFilePath` corresponds to `__FILE__`, and `CallerFunction` corresponds to `__FUNCTION__`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" new file mode 100644 index 00000000000..c40636cc830 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/crypto-gaes' +title: 'AES' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, AES Algorithm, Encryption and Decryption, gaes, Go Language, Data Encoding, base64, Encryption and Decryption Guide, GoFrame Tutorial] +description: "Use AES algorithm for data encryption and decryption in the GoFrame framework. By importing the go package and calling related functional functions, users can achieve secure data transmission and storage. Pay special attention to accurately decoding and encoding if the data is encoded in other forms such as base64 during the encryption and decryption process to ensure data integrity and security." +--- + +AES Algorithm. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/crypto/gaes" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gaes](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gaes) + +**Kind Reminder:** + +If the data to be decrypted has been encoded in other forms, it should be decoded first before decryption, such as base64.decode + +The same applies in reverse + +If you wish to encode the encrypted data, you can simply encode the result, such as base64.encode \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" new file mode 100644 index 00000000000..97d7ca9e7d3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gcrc32' +title: 'CRC32' +sidebar_position: 4 +hide_title: true +keywords: [CRC32,gcrc32,GoFrame,GoFrame framework,cryptography,goframe crypto,Golang,Checksum,data verification,encoding] +description: "Use the CRC32 algorithm in the GoFrame framework for data verification and encryption, including instructions on importing the library and links to relevant interface documentation, helping developers effectively use the gcrc32 module for data integrity verification." +--- + +CRC32 Algorithm. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/crypto/gcrc32" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gcrc32](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gcrc32) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" new file mode 100644 index 00000000000..3ff406966e9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/crypto-gdes' +title: 'DES' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, DES algorithm, gdes, crypto, encryption, PKCS5PADDING, NOPADDING, Triple DES, key] +description: "The usage of the DES algorithm in the GoFrame framework, demonstrating how to perform encryption operations through the gdes package. Links to the official API documentation to allow developers to obtain more technical details. The package supports two padding methods and provides special instructions for the use of keys in the Triple DES algorithm to ensure data security." +--- + +DES Algorithm. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/crypto/gdes" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gdes](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gdes) + +**About Padding in the `gdes` Package:** + +**The `gdes` package supports padding methods: `PKCS5PADDING` and `NOPADDING`. When using `NOPADDING`, a custom padding method is required.** + +**About Keys in the gdes Package:** + +**When using the Triple DES algorithm, with a 16-byte key, key3 is equal to key1.** \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" new file mode 100644 index 00000000000..461c48c8237 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gmd5' +title: 'MD5' +sidebar_position: 0 +hide_title: true +keywords: [MD5 Algorithm, GoFrame, gmd5, Cryptography, Encryption Algorithm, Hash Function, Data Security, GoFrame Framework, GoFrame Encryption, Go Development] +description: "Using the MD5 algorithm in the GoFrame framework by importing the gmd5 library for data encryption. MD5 is a commonly used hash function to ensure data integrity and security. In GoFrame, data can be conveniently encrypted using MD5, achieving simple and efficient data security protection." +--- + +MD5 algorithm. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/crypto/gmd5" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gmd5](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gmd5) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" new file mode 100644 index 00000000000..13c7ba9b8d9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gsha1' +title: 'SHA1' +sidebar_position: 3 +hide_title: true +keywords: [SHA1,gsha1,GoFrame,GoFrame framework,crypto,algorithm,Go language,encryption,data security,gogf] +description: "The use of the SHA1 algorithm in the GoFrame framework, providing specific import package methods and related interface documentation links to help users effectively apply the SHA1 algorithm when using Go language for encryption and data security." +--- + +SHA1 algorithm. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/crypto/gsha1" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gsha1](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gsha1) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" new file mode 100644 index 00000000000..0ca31c083a8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/crypto' +title: 'Crypto' +sidebar_position: 5 +hide_title: true +keywords: [Encryption, Decryption, GoFrame, Encryption Technology, Decryption Technology, Data Security, GoFrame Framework, Information Protection, Security Component, Data Encryption] +description: "Routine methods for encryption and decryption using the GoFrame framework, including how to achieve data security and protection within GoFrame. Ensure the confidentiality and integrity of information through effective encryption technology, providing solid data security assurance for your applications." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" new file mode 100644 index 00000000000..5c49e15dced --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" @@ -0,0 +1,103 @@ +--- +slug: '/docs/components/test-gtest' +title: 'Unit Testing' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, unit testing, gtest, test assertions, GoFrame framework, test framework, test cases, Assert, goconvey, testify] +description: "The usage of the gtest module under the GoFrame framework, providing convenient and lightweight unit testing methods. gtest extends the functionality of the standard library testing, adding multiple testing features such as test case isolation and common assertion methods. It is suitable for most unit testing scenarios and can be combined with third-party testing frameworks like testify and goconvey for more complex testing." +--- + +## Introduction +The `gtest` module offers simplified, lightweight, and commonly used unit testing methods. It is an extension encapsulated on the standard library `testing`, mainly adding the following features: + +- Isolation of multiple test items within unit test cases. +- Addition of a series of commonly used test assertion methods. +- Assertion methods support various common format assertions, increasing usability. +- Unified formatting of error messages when test failures occur. +:::tip +`gtest` is designed to be simple and easy to use, capable of meeting the vast majority of unit testing scenarios. For more complex testing scenarios, consider third-party testing frameworks such as `testify` and `goconvey`. +::: +**Usage**: + +```go +import "github.com/gogf/gf/v2/test/gtest" +``` + +**API Documentation**: +This chapter may not be updated in a timely manner; for a more comprehensive introduction to the API, please refer to the API documentation. +[https://pkg.go.dev/github.com/gogf/gf/v2/test/gtest](https://pkg.go.dev/github.com/gogf/gf/v2/test/gtest) + +```go +func C(t *testing.T, f func(t *T)) +func Assert(value, expect interface{}) +func AssertEQ(value, expect interface{}) +func AssertGE(value, expect interface{}) +func AssertGT(value, expect interface{}) +func AssertIN(value, expect interface{}) +func AssertLE(value, expect interface{}) +func AssertLT(value, expect interface{}) +func AssertNE(value, expect interface{}) +func AssertNI(value, expect interface{}) +func Error(message ...interface{}) +func Fatal(message ...interface{}) +``` + +**Brief Explanation**: + +1. Use the `C` method to create a `Case`, representing a unit test case. A unit test method can contain multiple `C`, where each `C` often represents one of the possible tests of the method. +2. The assertion method `Assert` supports comparison of variables of any type. When performing assertion comparison with `AssertEQ`, it also compares types, i.e., it performs strict assertions. +3. When using size comparison assertion methods such as `AssertGE`, the parameters support both string and number comparison, where string comparison is case-sensitive. +4. Inclusion assertion methods `AssertIN` and `AssertNI` support parameters of the `slice` type but do not currently support `map` type parameters. + +The package name for unit testing can either be `package_name_test` or directly use `package_name` (the same name as the test package). Both approaches are quite common and are also both involved in the Go official standard library. However, it should be noted that when you need to test private methods/private variables of the package, you must use the `package_name` naming form. Also, when using the `package_name` naming method, ensure that methods related to unit testing (non-`Test*` test methods) are generally defined as private and not publicly exposed. + +## Usage Example + +For example, one of the unit test cases of the `gstr` module: + +```go +package gstr_test + +import ( + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" + "testing" +) + +func Test_Trim(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.Trim(" 123456\n "), "123456") + t.Assert(gstr.Trim("#123456#;", "#;"), "123456") + }) +} +``` + +It can also be used like this: + +```go +package gstr_test + +import ( + . "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" + "testing" +) + +func Test_Trim(t *testing.T) { + C(t, func() { + Assert(gstr.Trim(" 123456\n "), "123456") + Assert(gstr.Trim("#123456#;", "#;"), "123456") + }) +} +``` + +A unit test case can contain multiple `C`, and a `C` can perform multiple assertions. If assertions pass, it directly proceeds with a PASS; however, if assertions fail, the following error information is output, and the current unit test case's execution is terminated (subsequent other unit test cases will not be terminated). + +```text +=== RUN Test_Trim +[ASSERT] EXPECT 123456#; == 123456 +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/text/gstr/gstr_z_unit_trim_test.go:20 +2. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/text/gstr/gstr_z_unit_trim_test.go:18 +--- FAIL: Test_Trim (0.00s) +FAIL +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..60626fd01f8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/test' +title: 'Testing' +sidebar_position: 7 +hide_title: true +keywords: [Unit Testing,GoFrame,GoFrame Framework,Testing Framework,Automated Testing,Code Testing,Software Development,Development Tools,Performance Optimization,Error Detection] +description: "Using the GoFrame framework for unit testing, covering basic concepts and practical methods of testing frameworks. By using automated testing tools and techniques, developers can effectively improve code quality and software performance, and promptly detect and correct errors during the software development process. This document aims to help developers make full use of the GoFrame framework for efficient unit testing." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" new file mode 100644 index 00000000000..ae5cb3e73e7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/components/util-gmeta' +title: 'Metadata' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, gmeta, metadata, tag, dynamic retrieval, struct, API documentation, data method, retrieve method] +description: "Use the gmeta package in the GoFrame framework to add metadata tags to user-defined structs and dynamically retrieve these tags at runtime using specific methods, including how to use the Data method and Get method to obtain metadata tag information of a specified object." +--- + +## Introduction + +Mainly used for embedding into user-defined structs, and through the form of tags to attach custom tag content (metadata) to the struct of the `gmeta` package, and dynamically retrieve these custom tag contents at runtime through specific methods. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/util/gmeta" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gmeta](https://pkg.go.dev/github.com/gogf/gf/v2/util/gmeta) + +**Method List:** + +```go +func Data(object interface{}) map[string]interface{} +func Get(object interface{}, key string) *gvar.Var +``` + +## Usage Example + +### `Data` Method + +The `Data` method is used to obtain the metadata tags of a specified `struct` object and return them as a `map`. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gmeta" +) + +func main() { + type User struct { + g.Meta `orm:"user" db:"mysql"` + Id int + Name string + } + g.Dump(gmeta.Data(User{})) +} +``` + +:::tip +Most of the time, in struct definitions, we use the alias `g.Meta` for `gmeta.Meta`. +::: + +After execution, the terminal outputs: + +```json +{ + "db": "mysql", + "orm": "user" +} +``` + +### `Get` Method + +The `Get` method is used to retrieve metadata tag information of a specified name from a specified `struct` object. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/gmeta" +) + +func main() { + type User struct { + g.Meta `orm:"user" db:"mysql"` + Id int + Name string + } + user := User{} + fmt.Println(gmeta.Get(user, "orm").String()) + fmt.Println(gmeta.Get(user, "db").String()) +} +``` + +After execution, the terminal outputs: + +```text +user +mysql +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" new file mode 100644 index 00000000000..811fb304459 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gpage' +title: 'Pagination' +sidebar_position: 5 +hide_title: true +description: "This document explains how to use the gpage module of the GoFrame framework to achieve efficient pagination management. By reading this document, developers can learn the specific steps and optimization techniques for utilizing the gpage module in WEB service development." +keywords: [GoFrame, GoFrame framework, gpage module, pagination management, WEB service development, gpage functionality, programming guide, module usage, developer tools, code optimization] +--- + +Pagination management is implemented by the `gpage` module. For more details, please refer to the [Pagination](../../WEB服务开发/分页管理/分页管理.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" new file mode 100644 index 00000000000..6ab3b06f4c8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" @@ -0,0 +1,120 @@ +--- +slug: '/docs/components/util-guid' +title: 'Unique Number' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, GUID, Unique Identification, High Performance, Easy to Use, Global Unique Number, Unique Number Generation, Go Language, Development Tool] +description: "Generate high-performance and easy-to-use global unique numbers with the GUID module of the GoFrame framework. The string generated by GUID consists of numbers and lowercase English characters, with a fixed length of 32 bytes. The document details the mechanism of GUID generation, its usage, and its advantages in various scenarios." +--- + +`guid` provides a more convenient and higher performance global unique number generation feature. The generated `uid` string only includes **numbers and lowercase English characters**. + +- **Advantages**: High performance, easy to use. +- **Disadvantages**: Limited character range, fixed length of `32` bytes. + +> The purpose of designing the `guid` module is to provide a more convenient, higher performance unique number generation that can meet the requirements of most business scenarios. The design of `guid` is relatively simple, and details can be found in the implementation source code. + +**Characters**: + +```text +Type Characters +Numerical 0123456789 +English abcdefghijklmnopqrstuvwxyz +``` + +**Usage**: + +```go +import "github.com/gogf/gf/v2/util/guid" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/guid](https://pkg.go.dev/github.com/gogf/gf/v2/util/guid) + +### Introduction + +`guid` generates a `32` byte unique number through the `S` method, which is defined as follows: + +```go +func S(data ...[]byte) string +``` + +1. When used without any parameters, the unique number generated by this method will be composed as follows: + +`MACHash(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)` + +Where: + + - `MAC` represents the MAC address hash of the current machine, consisting of `7` bytes; + - `PID` represents the process ID of the current machine, consisting of `4` bytes; + - `TimestampNano` represents the current nanosecond timestamp, consisting of `12` bytes; + - `Sequence` represents the concurrent safe sequence number of the current process, consisting of `3` bytes; + - `RandomString` represents a random string, consisting of `6` bytes; +2. When using any custom parameters, the unique number generated by this method will be composed as follows: + +`DataHash(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)` + +Main Points: + + - `Data` represents custom parameters, with a type of `[]byte`, supporting up to `2` input parameters, consisting of `7` or `14` bytes; + - Note that the input custom parameters need to have some unique identification in the business context, making the generated unique number more valuable; + - Regardless of the length of each `[]byte` parameter, they will eventually generate a `7` byte hash value through a hash method. + - `TimestampNano` represents the current nanosecond timestamp, consisting of `12` bytes; + - `Sequence` represents the concurrent safe sequence number of the current process, consisting of `3` bytes; + - `RandomString` represents a random string, consisting of `3` or `10` bytes, that is: + - If `1` custom parameter is given, the remaining bytes will be filled with random numbers, with a length of `10` bytes; + - If `2` custom parameters are given, the remaining bytes will be filled with random numbers, with a length of `3` bytes; + +### Benchmark + +``` +goos: darwin +goarch: amd64 +pkg: github.com/gogf/gf/v2/util/guid +cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz +Benchmark_S +Benchmark_S-12 2665587 423.8 ns/op +Benchmark_S_Data_1 +Benchmark_S_Data_1-12 2027568 568.2 ns/op +Benchmark_S_Data_2 +Benchmark_S_Data_2-12 4352824 275.5 ns/op +PASS +``` + +### Example 1, Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/guid" +) + +func main() { + fmt.Printf("TraceId: %s", guid.S()) +} +``` + +After execution, the output will be: + +``` +TraceId: oa9sdw03dk0c35q9bdwcnz42p00trwfr +``` + +### Example 2, Custom Parameters + +Our `SessionId` generation needs to have good uniqueness and prevent easy collisions, so the following method can be used: + +```go +func CreateSessionId(r *ghttp.Request) string { + var ( + address = request.RemoteAddr + header = fmt.Sprintf("%v", request.Header) + ) + return guid.S([]byte(address), []byte(header)) +} +``` + +As you can see, `SessionId` relies on two custom input parameters `RemoteAddr` and `Header` to generate, and these two parameters have a certain unique identification in the business context. The design of the `guid.S` method ensures that the generated unique number will be extremely random and unique, meeting business needs and ensuring safety. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" new file mode 100644 index 00000000000..7a2538e9c11 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/util' +title: 'Utilities' +sidebar_position: 6 +hide_title: true +keywords: [Utilities, GoFrame, GoFrame Framework, Components, Development, Efficiency, Documentation, Tool Library, Web Framework, Open Source Projects] +description: "This page introduces the utility components in the GoFrame framework, which help developers improve development efficiency. The GoFrame framework provides a range of high-efficiency utility tools to help developers complete projects faster and more easily, offering a convenient documentation development experience for developers." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" new file mode 100644 index 00000000000..b34b40cfc28 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" @@ -0,0 +1,251 @@ +--- +slug: '/docs/components/util-gutil' +title: 'Utility Functions' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gutil, utility methods, Go language, development components, data formatting, API documentation, Dump function, DumpWithType] +description: "The gutil component is a module in the GoFrame framework used to encapsulate commonly used development utility methods. It provides a series of convenient functions to support friendly output of data structures, such as Dump and DumpWithType. Developers can introduce the gutil component through the GitHub repository to improve Go language project development efficiency." +--- + +## Introduction + +The `gutil` component encapsulates some commonly used utility methods in development. + +Usage: + +```go +import "github.com/gogf/gf/v2/util/gutil" +``` + +API documentation: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gutil](https://pkg.go.dev/github.com/gogf/gf/v2/util/gutil) + +## Common Methods + +### `Dump` + +- Description: `Dump` outputs `values` to the standard output in a more readable way. + +- Format: + +```go +Dump(values ...interface{}) +``` + +- Example: + +```go +type User struct { + Name string + Age int +} + +type Location struct { + Province string + City string +} + +type UserInfo struct { + U User + L Location +} + +func main() { + userList := make([]UserInfo, 0) + userList = append(userList, UserInfo{ + U: User{ + Name: "郭强", + Age: 18, + }, + L: Location{ + Province: "四川", + City: "成都", + }, + }) + userList = append(userList, UserInfo{ + U: User{ + Name: "黄骞", + Age: 18, + }, + L: Location{ + Province: "江苏", + City: "南京", + }, + }) + + gutil.Dump(userList) +} + +// Output: +[ + { + U: { + Name: "郭强", + Age: 18, + }, + L: { + Province: "四川", + City: "成都", + }, + }, + { + U: { + Name: "黄骞", + Age: 18, + }, + L: { + Province: "江苏", + City: "南京", + }, + }, +] +``` + + +### `DumpWithType` + +- Description: `DumpWithType` is similar to `Dump` but includes type information. + +- Format: + +```go +DumpWithType(values ...interface{}) +``` + +- Example: + +```go +type User struct { + Name string + Age int +} + +type Location struct { + Province string + City string +} + +type UserInfo struct { + U User + L Location +} + +func main() { + userList := make([]UserInfo, 0) + userList = append(userList, UserInfo{ + U: User{ + Name: "郭强", + Age: 18, + }, + L: Location{ + Province: "四川", + City: "成都", + }, + }) + userList = append(userList, UserInfo{ + U: User{ + Name: "黄骞", + Age: 18, + }, + L: Location{ + Province: "江苏", + City: "南京", + }, + }) + + gutil.DumpWithType(userList) +} + +// Output: +[]main.UserInfo(2) [ + main.UserInfo(2) { + U: main.User(2) { + Name: string(6) "郭强", + Age: int(18), + }, + L: main.Location(2) { + Province: string(6) "四川", + City: string(6) "成都", + }, + }, + main.UserInfo(2) { + U: main.User(2) { + Name: string(6) "黄骞", + Age: int(18), + }, + L: main.Location(2) { + Province: string(6) "江苏", + City: string(6) "南京", + }, + }, +] +``` + + +### `DumpTo` + +- Description: `DumpTo` writes `value` to `writer` in a customized output format. + +- Format: + +```go +DumpTo(writer io.Writer, value interface{}, option DumpOption) +``` + +- Example: + +```go +package main + +import ( + "bytes" + "fmt" + "github.com/gogf/gf/v2/util/gutil" + "io" +) + +type UserInfo struct { + Name string + Age int + Province string + City string +} + +type DumpWriter struct { + Content string +} + +func (d *DumpWriter) Write(p []byte) (n int, err error) { + buffer := bytes.NewBuffer(nil) + buffer.WriteString("I'm Start!\n") + buffer.WriteString(string(p)) + buffer.WriteString("\nI'm End!\n") + + d.Content = buffer.String() + + return buffer.Len(), nil +} + +func main() { + u := UserInfo{ + "a", 18, "b", "c", + } + + var dw io.Writer = &DumpWriter{} + + gutil.DumpTo(dw, u, gutil.DumpOption{}) + + fmt.Println(dw.(*DumpWriter).Content) +} + +// Output: +I'm Start! +{ + Name: "a", + Age: 18, + Province: "b", + City: "c", +} +I'm End! +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" new file mode 100644 index 00000000000..c2ef4a25dc6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gvalid' +title: 'Data Validation' +sidebar_position: 4 +hide_title: true +description: "GoFrame framework's gvalid module, which is the core component for implementing data and form validation. gvalid plays an important role in GoFrame, providing powerful data validation capabilities suitable for a variety of application scenarios." +keywords: [GoFrame, GoFrame framework, data validation, gvalid module, data verification, form validation, core component, gvalid usage, GoFrame data validation, form validation tool] +--- + +Data/form validation is implemented by the `gvalid` module. Please refer to the [Data Validation](../../核心组件/数据校验/数据校验.md) section for details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" new file mode 100644 index 00000000000..9e7a7dee4e4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gconv' +title: 'Type Conversion' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gconv Module, Type Conversion, GoFrame Type Conversion, gconv Type Conversion, GoFrame gconv, GoFrame Documentation, GoFrame Components, Core Features] +description: "The type conversion feature in the GoFrame framework is implemented by the gconv module. By referring to specific sections, users can gain an in-depth understanding of how to perform type conversion in GoFrame to enhance development efficiency and code reliability." +--- + +The type conversion feature is implemented by the `gconv` module. For details, please refer to the [Type Conversion](../../核心组件/类型转换/类型转换.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" new file mode 100644 index 00000000000..a66a98099de --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/components/util-grand' +title: 'Random' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Random Number, grand Module, Random Generation, Performance Optimization, API Documentation, Character List, Probability Calculation, Random String] +description: "The grand module in the GoFrame framework provides optimized encapsulation for random number operations, offering high performance and versatile random generation methods, including integers, strings, and probability calculations. With practical methods like Intn and Str, you can easily generate various types of random data to meet different development needs." +--- + +The `grand` module implements encapsulation and improvements for random number operations, achieving extremely high random number generation performance, and provides rich methods related to random numbers. + +Usage: + +```go +import "github.com/gogf/gf/v2/util/grand" +``` + +API Documentation: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/grand](https://pkg.go.dev/github.com/gogf/gf/v2/util/grand) + +Common Methods: + +```go +func N(min, max int) int +func B(n int) []byte +func S(n int, symbols ...bool) string +func Str(s string, n int) string +func Intn(max int) int +func Digits(n int) string +func Letters(n int) string +func Meet(num, total int) bool +func MeetProb(prob float32) bool +func Perm(n int) []int +func Symbols(n int) string +``` + +### Characters + +```text +Type Characters +Numeric 0123456789 +English abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ +Special !"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ +``` + +### Random Integers + +1. The `Intn` method returns a random integer greater than or equal to `0` and less than `max`, i.e., `[0, max)`. +2. The `N` method returns a random integer between `min` and `max`, supports negative numbers, and includes boundaries, i.e., `[min, max]`. + +### Random Strings + +1. The `B` method is used to return a binary `[]byte` data of specified length. +2. The `S` method is used to return a string of specified length consisting of numbers and characters. The second parameter `symbols` specifies whether the random string should include special characters. +3. The `Str` method is a more advanced method that selects a random string of specified length from a given character list and supports `unicode` characters, such as Chinese. For example, `Str("中文123abc", 3)` may return a random string like `1a文`. +4. The `Digits` method returns a random numeric string of specified length. +5. The `Letters` method returns a random English string of specified length. +6. The `Symbols` method returns a random special character string of specified length. + +### Probability Calculation + +1. `Meet` is used to specify a number `num` and a total `total`, often with `num<=total`, and randomly calculate whether the probability of `num/total` is met. For example, `Meet(1, 100)` will randomly calculate whether a one percent probability is met. +2. `MeetProb` is used to provide a probability float number `prob`, often with `prob<=1.0`, and randomly calculate whether this probability is met. For example, `MeetProb(0.005)` will randomly calculate whether a five per thousand probability is met. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" new file mode 100644 index 00000000000..f289e771579 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/database-gredis' +title: 'Redis Client' +sidebar_position: 1 +hide_title: true +description: "The Redis client - gredis module aims to provide efficient database caching operations through the GoFrame framework. Users can explore how to optimize Redis-related applications under the GoFrame framework to achieve high-performance Redis functionality." +keywords: [Redis client, gredis, GoFrame, database caching, NoSQL Redis, Redis functionality, caching operations, efficient, module, application optimization] +--- + +The functionality of `Redis` is implemented by the `gredis` module. For details, please refer to the [NoSQL Redis](../NoSQL%20Redis/NoSQL%20Redis.md) chapter. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" new file mode 100644 index 00000000000..907eaca3840 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/database-gdb' +title: 'Database ORM' +sidebar_position: 0 +hide_title: true +description: "The gdb module in the GoFrame framework is the core component for implementing database ORM functionality, responsible for efficient data operations and management. In the GoFrame framework, gdb plays a crucial role in simplifying database interaction and management." +keywords: [GoFrame, GoFrame framework, database, ORM, gdb module, data interaction, data management, database operations, core component, gdb] +--- + +The `ORM` functionality is implemented by the `gdb` module. For details, please refer to the [Database ORM🔥](../../核心组件/数据库ORM/数据库ORM.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..78c4d22b87c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/database' +title: 'Database' +sidebar_position: 4 +hide_title: true +keywords: [Data Management, Database Component, Database Operation, Data Storage, Data Query, Data Analysis, Data Synchronization, GoFrame, GoFrame Framework, Data Security] +description: "Manage data in the GoFrame framework, implement data storage, query, and analysis through the database component, and ensure data security and reliability." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" new file mode 100644 index 00000000000..68c50dd1987 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" @@ -0,0 +1,40 @@ +--- +slug: '/docs/components/container-gmap' +title: 'Map' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gmap,map container,HashMap,TreeMap,ListMap,concurrent safety,data structure,associative array] +description: "Basic methods and considerations for using the gmap dictionary type in the GoFrame framework. The gmap module provides various concurrent-safe map data structure options, including HashMap, TreeMap, and ListMap. Suitable for any scenario involving concurrent access and hash table operations in Go applications, and details the performance and characteristics of each type." +--- + +## Introduction + +A `map` container with a concurrent safety switch option, the most commonly used data structure. This module includes `map` containers with multiple data structures: `HashMap`, `TreeMap`, and `ListMap`. + +| Type | Data Structure | Average Complexity | Supports Sorting | Ordered Traversal | Description | +| --- | --- | --- | --- | --- | --- | +| `HashMap` | Hash Table | `O(1)` | No | No | High performance read/write operation, high memory usage, random traversal | +| `ListMap` | Hash Table + Doubly Linked List | `O(2)` | No | Yes | Supports traversal in the order of insertion, high memory usage | +| `TreeMap` | Red Black Tree | `O(log N)` | Yes | Yes | Compact memory usage, supports key name sorting and ordered traversal | +:::tip +Additionally, the `gmap` module supports defining common types of `map` with hash table as the underlying data structure: `IntIntMap`, `IntStrMap`, `IntAnyMap`, `StrIntMap`, `StrStrMap`, `StrAnyMap`. +::: +**Usage Scenarios**: + +Any `map`/hash table/associative array usage scenario, particularly in concurrent safety scenarios. + +**Usage Method**: + +```go +import "github.com/gogf/gf/v2/container/gmap" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..360d93a29c2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,392 @@ +--- +slug: '/docs/components/container-gmap-example' +title: 'Map - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gmap, Concurrent Safety, Data Structure, Key-Value Operations, Ordered Traversal, JSON Serialization, Dictionary Type, Switch Parameter] +description: "Basic usage of the gmap module under the GoFrame framework, including switching operations of concurrent security features, setting, querying, and deleting key-value pairs, as well as ordered traversal, serialization and deserialization of data structures, with detailed code examples and execution results." +--- + +## Concurrent Safety + +`gmap` supports a concurrency-safe option switch, which is `not concurrency-safe` by default. Developers can choose to enable the concurrency-safe feature of `gmap` (passing the initialization switch parameter `safe` value as `true`, it must be set during initialization and cannot be dynamically set at runtime). For example: + +```go +m := gmap.New(true) +``` + +Not only the `gmap` module, but other concurrent-safe data structures of the `goframe` framework also support the concurrent safety feature switch. + +## Usage Example + +### Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + // Create a default gmap object, + // By default, this gmap object does not support concurrency safety features, + // A true parameter can be given during initialization to enable concurrent safety features. + m := gmap.New() + + // Set key-value pairs + for i := 0; i < 10; i++ { + m.Set(i, i) + } + // Query size + fmt.Println(m.Size()) + // Batch set key-value pairs (different data type objects have different parameters) + m.Sets(map[interface{}]interface{}{ + 10: 10, + 11: 11, + }) + fmt.Println(m.Size()) + + // Check if it exists + fmt.Println(m.Contains(1)) + + // Query value + fmt.Println(m.Get(1)) + + // Delete item + m.Remove(9) + fmt.Println(m.Size()) + + // Batch delete + m.Removes([]interface{}{10, 11}) + fmt.Println(m.Size()) + + // Current key list (random order) + fmt.Println(m.Keys()) + // Current value list (random order) + fmt.Println(m.Values()) + + // Query key name, and write the given default value when the key value does not exist + fmt.Println(m.GetOrSet(100, 100)) + + // Delete key-value pair, returning the corresponding key value + fmt.Println(m.Remove(100)) + + // Traverse map + m.Iterator(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + + // Custom write lock operation + m.LockFunc(func(m map[interface{}]interface{}) { + m[99] = 99 + }) + + // Custom read lock operation + m.RLockFunc(func(m map[interface{}]interface{}) { + fmt.Println(m[99]) + }) + + // Clear map + m.Clear() + + // Check if the map is empty + fmt.Println(m.IsEmpty()) +} +``` + +After execution, the output is: + +```10 +12 +true +1 +11 +9 +[0 1 2 4 6 7 3 5 8] +[3 5 8 0 1 2 4 6 7] +100 +100 +3:3 5:5 8:8 7:7 0:0 1:1 2:2 4:4 6:6 99 +true +``` + +### Ordered Traversal + +Let's take a look at examples of ordered traversal for three different types of `map`. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + array := g.Slice{2, 3, 1, 5, 4, 6, 8, 7, 9} + hashMap := gmap.New(true) + listMap := gmap.NewListMap(true) + treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true) + for _, v := range array { + hashMap.Set(v, v) + } + for _, v := range array { + listMap.Set(v, v) + } + for _, v := range array { + treeMap.Set(v, v) + } + fmt.Println("HashMap Keys:", hashMap.Keys()) + fmt.Println("HashMap Values:", hashMap.Values()) + fmt.Println("ListMap Keys:", listMap.Keys()) + fmt.Println("ListMap Values:", listMap.Values()) + fmt.Println("TreeMap Keys:", treeMap.Keys()) + fmt.Println("TreeMap Values:", treeMap.Values()) +} +``` + +After execution, the output is: + +``` +HashMap Keys: [4 6 8 7 9 2 3 1 5] +HashMap Values: [6 8 4 3 1 5 7 9 2] +ListMap Keys: [2 3 1 5 4 6 8 7 9] +ListMap Values: [2 3 1 5 4 6 8 7 9] +TreeMap Keys: [1 2 3 4 5 6 7 8 9] +TreeMap Values: [1 2 3 4 5 6 7 8 9] +``` + +### `FilterEmpty/FilterNil` Empty Value Filtering + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + m1 := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m2 := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m1.FilterEmpty() + m2.FilterNil() + fmt.Println(m1.Map()) + fmt.Println(m2.Map()) + + // Output: + // map[k4:1] + // map[k1: k3:0 k4:1] +} +``` + +### `Flip` Key-Value Pair Reversal + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + }) + m.Flip() + fmt.Println(m.Map()) + + // May Output: + // map[v1:k1 v2:k2] +} +``` + +### `Keys/Values` Key/Value Lists + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Keys()) + fmt.Println(m.Values()) + + // May Output: + // [k1 k2 k3 k4] + // [v2 v3 v4 v1] +} +``` + +### `Pop/Pops` Random Pop + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pop()) + fmt.Println(m.Pops(2)) + fmt.Println(m.Size()) + + // May Output: + // k1 v1 + // map[k2:v2 k4:v4] + // 1 +} +``` + +### `SetIfNotExist*` Conditional Setting + +Conditional setting means writing to the map only when the specified key does not exist, and the method returns `true`; otherwise, it ignores the write and the method returns `false`. Relevant methods include: + +- `SetIfNotExist` +- `SetIfNotExistFunc` +- `SetIfNotExistFuncLock` + +For detailed descriptions, please refer to the interface documentation or source code comments. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + var m gmap.Map + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + +### `Merge` Dictionary Merge + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + var m1, m2 gmap.Map + m1.Set("key1", "val1") + m2.Set("key2", "val2") + m1.Merge(&m2) + fmt.Println(m1.Map()) + + // May Output: + // map[key1:val1 key2:val2] +} +``` + +### `JSON` Serialization/Deserialization + +All container types under the `gmap` module implement the standard library `json` data format serialization/deserialization interfaces. + +1\. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + m := gmap.New() + m.Sets(g.MapAnyAny{ + "name": "john", + "score": 100, + }) + b, _ := json.Marshal(m) + fmt.Println(string(b)) +} +``` + +After execution, the output is: + +``` +{"name":"john","score":100} +``` + +2. `Unmarshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + m := gmap.Map{} + s := []byte(`{"name":"john","score":100}`) + json.Unmarshal(s, &m) + fmt.Println(m.Map()) +} +``` + +After execution, the output is: + +``` +map[name:john score:100] +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..cf3a7402f83 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/components/container-gmap-benchmark' +title: 'Map - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Dictionary Type, Performance Testing, Concurrent Safe, Non-Concurrent Safe, map performance, gmap, sync.Map, Benchmarking] +description: "Detailed testing and analysis of the performance of dictionary types. By comparing the performance of gmap in the GoFrame framework with the standard library's sync.Map, the efficiency in different scenarios of concurrent safety and non-concurrent safety is revealed. Includes performance benchmarking of different types of maps such as HashMap, ListMap, and TreeMap, providing developers with real-time reference for optimizing applications." +--- + +## Performance Testing + +### Concurrent Safety + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_safe\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_safe_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_IntIntMap_Set-4 10000000 202 ns/op 15 B/op 0 allocs/op +Benchmark_IntAnyMap_Set-4 10000000 262 ns/op 29 B/op 1 allocs/op +Benchmark_IntStrMap_Set-4 10000000 241 ns/op 22 B/op 0 allocs/op +Benchmark_AnyAnyMap_Set-4 5000000 359 ns/op 40 B/op 2 allocs/op +Benchmark_StrIntMap_Set-4 5000000 305 ns/op 26 B/op 1 allocs/op +Benchmark_StrAnyMap_Set-4 5000000 354 ns/op 40 B/op 2 allocs/op +Benchmark_StrStrMap_Set-4 5000000 338 ns/op 32 B/op 1 allocs/op +Benchmark_IntIntMap_Get-4 20000000 86.6 ns/op 0 B/op 0 allocs/op +Benchmark_IntAnyMap_Get-4 30000000 69.7 ns/op 0 B/op 0 allocs/op +Benchmark_IntStrMap_Get-4 30000000 69.6 ns/op 0 B/op 0 allocs/op +Benchmark_AnyAnyMap_Get-4 20000000 74.4 ns/op 0 B/op 0 allocs/op +Benchmark_StrIntMap_Get-4 20000000 116 ns/op 7 B/op 0 allocs/op +Benchmark_StrAnyMap_Get-4 20000000 92.3 ns/op 7 B/op 0 allocs/op +Benchmark_StrStrMap_Get-4 20000000 91.9 ns/op 7 B/op 0 allocs/op +``` + +### Non-Concurrent Safety + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_unsafe\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_unsafe_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_Unsafe_IntIntMap_Set-4 10000000 318 ns/op 62 B/op 0 allocs/op +Benchmark_Unsafe_IntAnyMap_Set-4 5000000 282 ns/op 57 B/op 1 allocs/op +Benchmark_Unsafe_IntStrMap_Set-4 5000000 332 ns/op 82 B/op 1 allocs/op +Benchmark_Unsafe_AnyAnyMap_Set-4 3000000 471 ns/op 73 B/op 2 allocs/op +Benchmark_Unsafe_StrIntMap_Set-4 5000000 429 ns/op 82 B/op 1 allocs/op +Benchmark_Unsafe_StrAnyMap_Set-4 3000000 424 ns/op 73 B/op 2 allocs/op +Benchmark_Unsafe_StrStrMap_Set-4 2000000 515 ns/op 96 B/op 2 allocs/op +Benchmark_Unsafe_IntIntMap_Get-4 10000000 133 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntAnyMap_Get-4 20000000 134 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntStrMap_Get-4 10000000 126 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnyAnyMap_Get-4 10000000 166 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_StrIntMap_Get-4 5000000 246 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrAnyMap_Get-4 10000000 238 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrStrMap_Get-4 5000000 229 ns/op 7 B/op 0 allocs/op +``` + +### Performance of Different Types of Maps + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_maps\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_maps_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_HashMap_Set-4 5000000 349 ns/op 40 B/op 2 allocs/op +Benchmark_ListMap_Set-4 3000000 455 ns/op 87 B/op 3 allocs/op +Benchmark_TreeMap_Set-4 3000000 481 ns/op 28 B/op 2 allocs/op +Benchmark_HashMap_Get-4 30000000 67.8 ns/op 0 B/op 0 allocs/op +Benchmark_ListMap_Get-4 20000000 74.5 ns/op 0 B/op 0 allocs/op +Benchmark_TreeMap_Get-4 20000000 189 ns/op 8 B/op 1 allocs/op +``` + +### Performance Comparison of `gmap` and `sync.Map` + +The `sync.Map` was introduced in Go language from version `1.9` as a concurrent-safe map, but `gmap` offers better performance compared to the standard library's `sync.Map`, and has richer functionalities. + +Let's take a look at the benchmark comparison results: [https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_syncmap\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_syncmap_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_GMapSet-4 10000000 209 ns/op 15 B/op 0 allocs/op +Benchmark_SyncMapSet-4 3000000 451 ns/op 67 B/op 3 allocs/op +Benchmark_GMapGet-4 30000000 66.4 ns/op 0 B/op 0 allocs/op +Benchmark_SyncMapGet-4 30000000 36.0 ns/op 0 B/op 0 allocs/op +Benchmark_GMapRemove-4 10000000 207 ns/op 0 B/op 0 allocs/op +Benchmark_SyncMapRmove-4 30000000 42.4 ns/op 0 B/op 0 allocs/op +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..09973cf4bd0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1371 @@ +--- +slug: '/docs/components/container-gmap-funcs' +title: 'Map - Methods' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, AnyAnyMap, gmap, map operations, concurrency safety, map methods, GoFrame Documentation, map iteration, map copy] +description: "Various methods of the AnyAnyMap in the GoFrame framework, including operations such as creation, cloning, iteration, setting, deletion, and merging. Code examples are provided to help understand the use of these methods to ensure concurrency safety. For more detailed information, refer to the GoFrame Framework documentation." +--- +:::tip +The following list of common methods may lag behind the features of the code in the documentation update. Please refer to the code documentation for more methods and examples: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) +::: +## `New` + +- Description: `New` creates and returns an empty `AnyAnyMap`. The parameter `safe` is used to specify whether to use a concurrency-safe `map`, which is `false` by default. + +- Format: + +```go +New(safe ...bool) *Map +``` + +- Example: + +```go +func ExampleNew() { + m := gmap.New() + + // Add data. + m.Set("key1", "val1") + + // Print size. + fmt.Println(m.Size()) + + addMap := make(map[interface{}]interface{}) + addMap["key2"] = "val2" + addMap["key3"] = "val3" + addMap[1] = 1 + + fmt.Println(m.Values()) + + // Batch add data. + m.Sets(addMap) + + // Gets the value of the corresponding key. + fmt.Println(m.Get("key3")) + + // Get the value by key, or set it with given key-value if not exist. + fmt.Println(m.GetOrSet("key4", "val4")) + + // Set key-value if the key does not exist, then return true; or else return false. + fmt.Println(m.SetIfNotExist("key3", "val3")) + + // Remove key + m.Remove("key2") + fmt.Println(m.Keys()) + + // Batch remove keys. + m.Removes([]interface{}{"key1", 1}) + fmt.Println(m.Keys()) + + // Contains checks whether a key exists. + fmt.Println(m.Contains("key3")) + + // Flip exchanges key-value of the map, it will change key-value to value-key. + m.Flip() + fmt.Println(m.Map()) + + // Clear deletes all data of the map. + m.Clear() + + fmt.Println(m.Size()) + + // May Output: + // 1 + // [val1] + // val3 + // val4 + // false + // [key4 key1 key3 1] + // [key4 key3] + // true + // map[val3:key3 val4:key4] + // 0 +} +``` + + +## `NewFrom` + +- Description: `NewFrom` creates and returns an `AnyAnyMap` with the data of the given `map`. + +- Note: The input parameter `map` will be set as the underlying data mapping (without deep copy), so external changes to the `map` may introduce some safety issues simultaneously. Optional argument `safe` specifies whether to use this structure in concurrency safety, default is `false`. + +- Format: + +```go +NewFrom(data map[interface{}]interface{}, safe ...bool) *Map +``` + +- Example: + +```go +func ExampleNewFrom() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + n := gmap.NewFrom(m.MapCopy(), true) + fmt.Println(n) + + // Output: + // {"key1":"val1"} + // {"key1":"val1"} +} +``` + + +## `Iterator` + +- Description: `Iterator` iterates through the `hashmap` in a read-only manner using a custom callback function `f`. If `f` returns `true`, it continues iterating, if it returns `false`, it stops. + +- Format: + +```go +Iterator(f func(k interface{}, v interface{}) bool) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Iterator() { + m := gmap.New() + for i := 0; i < 10; i++ { + m.Set(i, i*2) + } + + var totalKey, totalValue int + m.Iterator(func(k interface{}, v interface{}) bool { + totalKey += k.(int) + totalValue += v.(int) + + return totalKey < 10 + }) + + fmt.Println("totalKey:", totalKey) + fmt.Println("totalValue:", totalValue) + + // May Output: + // totalKey: 11 + // totalValue: 22 +} +``` + + +## `Clone` + +- Description: `Clone` returns a new `AnyAnyMap`, which contains a copy of the current `map` data. + +- Format: + +```go +Clone(safe ...bool) *AnyAnyMap +``` + +- Example: + +```go +func ExampleAnyAnyMap_Clone() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + n := m.Clone() + fmt.Println(n) + + // Output: + // {"key1":"val1"} + // {"key1":"val1"} +} +``` + + +## `Map` + +- Description: `Map` returns the underlying data `map`. + +- Note: If in concurrency safety, it returns a copy of the underlying data, otherwise it returns a pointer pointing to the underlying data. + +- Format: + +```go +Map() map[interface{}]interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_Map() { + // non concurrent-safety, a pointer to the underlying data + m1 := gmap.New() + m1.Set("key1", "val1") + fmt.Println("m1:", m1) + + n1 := m1.Map() + fmt.Println("before n1:", n1) + m1.Set("key1", "val2") + fmt.Println("after n1:", n1) + + // concurrent-safety, copy of underlying data + m2 := gmap.New(true) + m2.Set("key1", "val1") + fmt.Println("m1:", m2) + + n2 := m2.Map() + fmt.Println("before n2:", n2) + m2.Set("key1", "val2") + fmt.Println("after n2:", n2) + + // Output: + // m1: {"key1":"val1"} + // before n1: map[key1:val1] + // after n1: map[key1:val2] + // m1: {"key1":"val1"} + // before n2: map[key1:val1] + // after n2: map[key1:val1] +} +``` + + +## `MapCopy` + +- Description: `MapCopy` returns a copy of the data in the `map`. + +- Format: + +```go +MapCopy() map[interface{}]interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_MapCopy() { + m := gmap.New() + + m.Set("key1", "val1") + m.Set("key2", "val2") + fmt.Println(m) + + n := m.MapCopy() + fmt.Println(n) + + // Output: + // {"key1":"val1","key2":"val2"} + // map[key1:val1 key2:val2] +} +``` + + +## MapStrAny + +- Description: `MapStrAny` returns a copy of the `map`'s data in the form of `map[string]interface{}`. + +- Format: + +```go +MapStrAny() map[string]interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_MapStrAny() { + m := gmap.New() + m.Set(1001, "val1") + m.Set(1002, "val2") + + n := m.MapStrAny() + fmt.Println(n) + + // Output: + // map[1001:val1 1002:val2] +} +``` + + +## `FilterEmpty` + +- Description: `FilterEmpty` removes all key-value pairs with empty values. Values such as `0`, `nil`, `false`, `""`, `len(slice/map/chan) == 0` are considered empty. + +- Format: + +```go +FilterEmpty() +``` + +- Example: + +```go +func ExampleAnyAnyMap_FilterEmpty() { + m := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m.FilterEmpty() + fmt.Println(m.Map()) + + // Output: + // map[k4:1] +} +``` + + +## FilterNil + +- Description: `FilterNil` removes all key-value pairs where the value is `nil`. + +- Format: + +```go +FilterNil() +``` + +- Example: + +```go +func ExampleAnyAnyMap_FilterNil() { + m := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m.FilterNil() + fmt.Println(m.Map()) + + // May Output: + // map[k1: k3:0 k4:1] +} +``` + + +## Set + +- Description: `Set` sets the `key/value` for the `map`. + +- Format: + +```go +Set(key interface{}, value interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Set() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + // Output: + // {"key1":"val1"} +} +``` + + +## Sets + +- Description: `Sets` sets the `key/` `value` batch for the `map`. + +- Format: + +```go +Sets(data map[interface{}]interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Sets() { + m := gmap.New() + + addMap := make(map[interface{}]interface{}) + addMap["key1"] = "val1" + addMap["key2"] = "val2" + addMap["key3"] = "val3" + + m.Sets(addMap) + fmt.Println(m) + + // Output: + // {"key1":"val1","key2":"val2","key3":"val3"} +} +``` + + +## `Search` + +- Description: `Search` searches the `map` using the parameter `key`. If the `key` is found, it returns its corresponding value and the parameter `found` as `true`, otherwise as `false`. + +- Format: + +```go +Search(key interface{}) (value interface{}, found bool) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Search() { + m := gmap.New() + + m.Set("key1", "val1") + + value, found := m.Search("key1") + if found { + fmt.Println("find key1 value:", value) + } + + value, found = m.Search("key2") + if !found { + fmt.Println("key2 not find") + } + + // Output: + // find key1 value: val1 + // key2 not find +} + + +``` + + +## `Get` + +- Description: `Get` returns the value corresponding to the parameter `key`. If the `key` does not exist, it returns `Nil`. + +- Format: + +```go +Get(key interface{}) (value interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Get() { + m := gmap.New() + + m.Set("key1", "val1") + + fmt.Println("key1 value:", m.Get("key1")) + fmt.Println("key2 value:", m.Get("key2")) + + // Output: + // key1 value: val1 + // key2 value: +} +``` + + +## `Pop` + +- Description: `Pop` randomly retrieves and returns a key-value pair from `map` and deletes the key-value pair internally. + +- Format: + +```go +Pop() (key, value interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Pop() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + fmt.Println(m.Pop()) + + // May Output: + // k1 v1 +} +``` + + +## Pops + +- Description: `Pops` randomly retrieves and deletes `size` number of key-value pairs from `map`. If `size == -1`, it deletes and returns all key-value pairs. + +- Format: + +```go +Pops(size int) map[interface{}]interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_Pops() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pops(-1)) + fmt.Println("size:", m.Size()) + + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pops(2)) + fmt.Println("size:", m.Size()) + + // May Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] + // size: 0 + // map[k1:v1 k2:v2] + // size: 2 +} +``` + + +## GetOrSet + +- Description: `GetOrSet` returns `value` if `key` exists. If `key` does not exist, it sets the key-value to the `map` and then returns the value. + +- Format: + +```go +GetOrSet(key interface{}, value interface{}) interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetOrSet() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSet("key1", "NotExistValue")) + fmt.Println(m.GetOrSet("key2", "val2")) + + // Output: + // val1 + // val2 +} +``` + + +## `GetOrSetFunc` + +- Description: `GetOrSetFunc` returns `value` if `key` exists. If `key` does not exist, it sets the key-value for `map` using the return value of `func f` and then returns the value. + +- Format: + +```go +GetOrSetFunc(key interface{}, f func() interface{}) interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetOrSetFunc() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSetFunc("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetOrSetFunc("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetOrSetFuncLock + +- Description: `GetOrSetFunc` returns `value` if `key` exists. If `key` does not exist, it sets the key-value for `map` using the return value of `func f` and then returns the value. + +- Note: The difference between `GetOrSetFuncLock` and `GetOrSetFunc` is that it executes the function `f` in a write lock. + +- Format: + +```go +GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetOrSetFuncLock() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSetFuncLock("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetOrSetFuncLock("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetVar + +- Description: `GetVar` queries and returns the key-value corresponding to the key name `key`, with the key value returned using the generic type `*gvar.Var`. + +- Format: + +```go +GetVar(key interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetVar() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVar("key1")) + fmt.Println(m.GetVar("key2").IsNil()) + + // Output: + // val1 + // true +} +``` + + +## `GetVarOrSet` + +- Description: `GetVarOrSet` queries and returns the key-value corresponding to the key name `key`. If the corresponding key-value does not exist, it uses `value` to set that key-value and returns the queried/settled key-value. The key-value is returned using the generic type `*gvar.Var`. + +- Format: + +```go +GetVarOrSet(key interface{}, value interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetVarOrSet() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSet("key1", "NotExistValue")) + fmt.Println(m.GetVarOrSet("key2", "val2")) + + // Output: + // val1 + // val2 +} +``` + + +## GetVarOrSetFunc + +- Description: `GetVarOrSetFunc` queries and returns the key-value corresponding to the key name `key`. If the corresponding key-value does not exist, it uses the return value of `func f` to set that key-value and returns the queried/settled key-value. The key-value is returned using the generic type `*gvar.Var`. + +- Format: + +```go +GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetVarOrSetFunc() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSetFunc("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetVarOrSetFunc("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetVarOrSetFuncLock + +- Description: `GetVarOrSetFuncLock` queries and returns the key-value corresponding to the key name `key`. If the corresponding key-value does not exist, it uses the return value of `func f` to set that key-value and returns the queried/settled key-value. The key-value is returned using the generic type `*gvar.Var`. + +- Note: The difference between `GetVarOrSetFuncLock` and `GetVarOrSetFunc` is that it executes the function `f` in a write lock before execution when multiple `goroutine`s call this method simultaneously. + +- Format: + +```go +GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleAnyAnyMap_GetVarOrSetFuncLock() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSetFuncLock("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetVarOrSetFuncLock("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## `SetIfNotExist` + +- Description: If the `key` does not exist, `SetIfNotExist` sets the key-value pair `key/value` for the `map` and returns `true`. If the `key` exists, it returns `false`, and the `value` will be ignored. + +- Format: + +```go +SetIfNotExist(key interface{}, value interface{}) bool +``` + +- Example: + +```go +func ExampleAnyAnyMap_SetIfNotExist() { + var m gmap.Map + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## SetIfNotExistFunc + +- Description: If the `key` does not exist, `SetIfNotExistFunc` sets the value for the `map` to the return value of the function `f` and returns `true`. If the `key` exists, it returns `false`, and the `value` will be ignored. + +- Format: + +```go +SetIfNotExistFunc(key interface{}, f func() interface{}) bool +``` + +- Example: + +```go +func ExampleAnyAnyMap_SetIfNotExistFunc() { + var m gmap.Map + fmt.Println(m.SetIfNotExistFunc("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.SetIfNotExistFunc("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## `SetIfNotExistFuncLock` + +- Description: If the `key` does not exist, `SetIfNotExistFunc` sets the value for the `map` to the return value of `func f` and then returns `true`. If the `key` exists, it returns `false`, and the `value` will be ignored. + +- Note: The difference between `SetIfNotExistFuncLock` and `SetIfNotExistFunc` is that it executes the function `f` in `mutex.Lock`. + +- Format: + +```go +SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool +``` + +- Example: + +```go +func ExampleAnyAnyMap_SetIfNotExistFuncLock() { + var m gmap.Map + fmt.Println(m.SetIfNotExistFuncLock("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.SetIfNotExistFuncLock("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## Remove + +- Description: Delete `value` from the `map` by the given `key`, and return the deleted `value`. + +- Format: + +```go +Remove(key interface{}) (value interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Remove() { + var m gmap.Map + m.Set("k1", "v1") + + fmt.Println(m.Remove("k1")) + fmt.Println(m.Remove("k2")) + + // Output: + // v1 + // +} +``` + + +## Removes + +- Description: `Removes` batch deletes the `value` of the `map` by the given `key`. + +- Format: + +```go +Removes(keys []interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Removes() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + removeList := make([]interface{}, 2) + removeList = append(removeList, "k1") + removeList = append(removeList, "k2") + + m.Removes(removeList) + + fmt.Println(m.Map()) + + // Output: + // map[k3:v3 k4:v4] +} +``` + + +## Keys + +- Description: `Keys` returns all the `key` of the `map` as `slice`. + +- Format: + +```go +Keys() []interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_Keys() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Keys()) + + // Output: + // [k1 k2 k3 k4] +} +``` + + +## Values + +- Description: `Values` returns all the `value` of the `map` as `slice`. + +- Format: + +```go +Values() []interface{} +``` + +- Example: + +```go +func ExampleAnyAnyMap_Values() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Values()) + + // May Output: + // [v1 v2 v3 v4] +} +``` + + +## Contains + +- Description: `Contains` checks whether `key` exists. If `key` exists, it returns `true`, otherwise it returns `false`. + +- Note: The key name type is `interface{}`, so the match judgment needs to ensure that the type and value are identical. +- Format: + +```go +Contains(key interface{}) bool +``` + +- Example: + +```go +func ExampleAnyAnyMap_Contains() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Contains("k1")) + fmt.Println(m.Contains("k5")) + + // Output: + // true + // false +} +``` + + +## `Size` + +- Description: `Size` returns the size of the `map`. + +- Format: + +```go +Size() int +``` + +- Example: + +```go +func ExampleAnyAnyMap_Size() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + fmt.Println(m.Size()) + + // Output: + // 4 +} +``` + + +## `IsEmpty` + +- Description: `IsEmpty` checks whether the `map` is empty. If the `map` is empty, it returns `true`, otherwise it returns `false`. + +- Format: + +```go +IsEmpty() bool +``` + +- Example: + +```go +func ExampleAnyAnyMap_IsEmpty() { + var m gmap.Map + fmt.Println(m.IsEmpty()) + + m.Set("k1", "v1") + fmt.Println(m.IsEmpty()) + + // Output: + // true + // false +} +``` + + +## Clear + +- Description: `Clear` deletes all data of the `map`. + +- Format: + +```go +Clear() +``` + +- Example: + +```go +func ExampleAnyAnyMap_Clear() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + m.Clear() + + fmt.Println(m.Map()) + + // Output: + // map[] +} +``` + + +## `Replace` + +- Description: `Replace` completely replaces the `map`'s `value` with the given `data`. + +- Format: + +```go +Replace(data map[interface{}]interface{}) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Replace() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + + var n gmap.Map + n.Sets(g.MapAnyAny{ + "k2": "v2", + }) + + fmt.Println(m.Map()) + + m.Replace(n.Map()) + fmt.Println(m.Map()) + + n.Set("k2", "v1") + fmt.Println(m.Map()) + + // Output: + // map[k1:v1] + // map[k2:v2] + // map[k2:v1] +} +``` + + +## `LockFunc` + +- Description: `LockFunc` executes the function `f` in the write lock. + +- Format: + +```go +LockFunc(f func(m map[interface{}]interface{})) +``` + +- Example: + +```go +func ExampleAnyAnyMap_LockFunc() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": 1, + "k2": 2, + "k3": 3, + "k4": 4, + }) + + m.LockFunc(func(m map[interface{}]interface{}) { + totalValue := 0 + for _, v := range m { + totalValue += v.(int) + } + fmt.Println("totalValue:", totalValue) + }) + + // Output: + // totalValue: 10 +} +``` + + +## `RLockFunc` + +- Description: `RLockFunc` executes the function `f` in the read lock. + +- Format: + +```go +RLockFunc(f func(m map[interface{}]interface{})) +``` + +- Example: + +```go +func ExampleAnyAnyMap_RLockFunc() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": 1, + "k2": 2, + "k3": 3, + "k4": 4, + }) + + m.RLockFunc(func(m map[interface{}]interface{}) { + totalValue := 0 + for _, v := range m { + totalValue += v.(int) + } + fmt.Println("totalValue:", totalValue) + }) + + // Output: + // totalValue: 10 +} +``` + + +## `Flip` + +- Description: `Flip` exchanges the `key` and `value` of the `map`. + +- Format: + +```go +Flip() +``` + +- Example: + +```go +func ExampleAnyAnyMap_Flip() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + m.Flip() + fmt.Println(m.Map()) + + // Output: + // map[v1:k1] +} +``` + + +## `Merge` + +- Description: `Merge` merges two AnyAnyMaps. The input parameter `map` will be merged into the original `map`. + +- Format: + +```go +Merge(other *AnyAnyMap) +``` + +- Example: + +```go +func ExampleAnyAnyMap_Merge() { + var m1, m2 gmap.Map + m1.Set("key1", "val1") + m2.Set("key2", "val2") + m1.Merge(&m2) + fmt.Println(m1.Map()) + + // May Output: + // map[key1:val1 key2:val2] +} +``` + + +## `String` + +- Description: `String` returns the `map` in string form. + +- Format: + +```go +String() string +``` + +- Example: + +```go +func ExampleAnyAnyMap_String() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + + fmt.Println(m.String()) + + // Output: + // {"k1":"v1"} +} +``` + + +## `MarshalJSON` + +- Description: `MarshalJSON` implements the `json.Marshal` interface. + +- Format: + +```go +MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleAnyAnyMap_MarshalJSON() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + bytes, err := m.MarshalJSON() + if err == nil { + fmt.Println(gconv.String(bytes)) + } + + // Output: + // {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"} +} +``` + + +## `UnmarshalJSON` + +- Description: `UnmarshalJSON` implements the `json.Unmarshal` interface. + +- Format: + +```go +UnmarshalJSON(b []byte) error +``` + +- Example: + +```go +func ExampleAnyAnyMap_UnmarshalJSON() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + var n gmap.Map + + err := n.UnmarshalJSON(gconv.Bytes(m.String())) + if err == nil { + fmt.Println(n.Map()) + } + + // Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] +} +``` + + +## `UnmarshalValue` + +- Description: `UnmarshalValue` is an interface implementation that initializes the current `map` through a variable of any type. + +- Format: + +```go +UnmarshalValue(value interface{}) (err error) +``` + +- Example: + +```go +func ExampleAnyAnyMap_UnmarshalValue() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + var n gmap.Map + err := n.UnmarshalValue(m.String()) + if err == nil { + fmt.Println(n.Map()) + } + // Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" new file mode 100644 index 00000000000..83e9fbcc9a1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" @@ -0,0 +1,36 @@ +--- +slug: '/docs/components/container-gtype' +title: 'Safe Type' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, safe type, concurrent programming, concurrency safety, gtype, basic type, atomic operations, performance optimization, data type, lock mechanism] +description: "The safe type gtype in the GoFrame framework is suitable for any scenario requiring concurrency safety. By providing concurrency safety support for the most commonly used basic data types, gtype has higher performance than mutex locks, simplifies concurrency control using atomic operations, and facilitates developers in efficient concurrent programming in complex scenarios." +--- + +## Introduction + +Concurrency-safe basic types. + +**Use Scenarios**: + +`gtype` is used very frequently and is applicable in any scenario requiring concurrency safety. + +In common concurrency-safe scenarios, a variable of a basic type, especially a `struct` containing several attributes, often uses mutex (read/write) locks or multiple (read/write) locks for safe management. However, in such usage, the operation performance of `variables/struct/attributes` is **very low**, and the presence of the mutex lock mechanism often makes the operation quite complex, requiring careful maintenance of `variable/attribute` concurrency safety control (especially `(RW)Mutex`). + +`gtype` provides corresponding concurrency-safe data types for the most commonly used basic data types, making it easier to maintain variables/attributes in concurrency-safe scenarios. Developers no longer need to create and maintain cumbersome `(RW)Mutex` in `struct`. Since `gtype` maintains concurrency safety for basic types, it mostly uses `atomic` operations internally to maintain concurrency safety, often resulting in efficiency that is tens of times higher than `(RW)Mutex` mutex locks. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/gtype" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gtype](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtype) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..37d4635cbef --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/components/container-gtype-example' +title: 'Safe Type - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame framework,gtype,concurrent safety,JSON serialization,Go language,basic usage,thread safety,container type,data manipulation] +description: "Implement concurrent safe basic type operations using the gtype module in the GoFrame framework. The example code demonstrates how to create and operate on thread-safe basic types, such as increment and decrement operations on integer types, as well as JSON serialization and deserialization functions of gtype container types, helping developers manage data conveniently." +--- + +The use of `gtype` concurrent safe basic types is very simple, often similar to the following methods (taking the `gtype.Int` type as an example): + +```go +func NewInt(value ...int) *Int +func (v *Int) Add(delta int) (new int) +func (v *Int) Cas(old, new int) bool +func (v *Int) Clone() *Int +func (v *Int) Set(value int) (old int) +func (v *Int) String() string +func (v *Int) Val() int +``` + +## Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gtype" + "fmt" +) + +func main() { + // Create a concurrently safe basic type object for Int + i := gtype.NewInt() + + // Set the value + fmt.Println(i.Set(10)) + + // Get the value + fmt.Println(i.Val()) + + // Decrement by 1, and return the modified value + fmt.Println(i.Add(-1)) +} +``` + +After execution, the output result is: + +```0 +10 +9 +``` + +## `JSON` Serialization/Deserialization + +All container types under the `gtype` module implement the serialization/deserialization interface of the standard library `json` data format. + +1. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gtype" +) + +func main() { + type Student struct { + Id *gtype.Int + Name *gtype.String + Scores *gtype.Interface + } + s := Student{ + Id: gtype.NewInt(1), + Name: gtype.NewString("john"), + Scores: gtype.NewInterface([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +After execution, the output result: + +``` +{"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gtype" +) + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id *gtype.Int + Name *gtype.String + Scores *gtype.Interface + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +After execution, the output result: + +``` +{1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..c3b8f217ee9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/components/container-gtype-benchmark' +title: 'Safe Type - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, benchmark, performance testing, safe types, concurrent programming, data types, Go language, code optimization, test results] +description: "The performance benchmark results of safe types in the gtype package of the GoFrame framework are presented, analyzing the execution efficiency of different data type methods through detailed test data comparison, providing a reference for Go language developers to enhance data processing capabilities in concurrent programming." +--- + +The benchmark test results are as follows: + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/container/gtype$ go test -bench=".*" -benchmem +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/gtype +BenchmarkInt_Set-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkInt_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkInt_Add-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Set-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Val-4 2000000000 0.47 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Add-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Set-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Add-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Set-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Add-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Set-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Val-4 2000000000 0.50 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Add-4 200000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Set-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Val-4 2000000000 0.47 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Add-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkBool_Set-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkBool_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkString_Set-4 20000000 90.1 ns/op 23 B/op 1 allocs/op +BenchmarkString_Val-4 2000000000 1.58 ns/op 0 B/op 0 allocs/op +BenchmarkBytes_Set-4 20000000 76.2 ns/op 35 B/op 2 allocs/op +BenchmarkBytes_Val-4 2000000000 1.58 ns/op 0 B/op 0 allocs/op +BenchmarkInterface_Set-4 50000000 30.7 ns/op 8 B/op 0 allocs/op +BenchmarkInterface_Val-4 2000000000 0.74 ns/op 0 B/op 0 allocs/op +BenchmarkAtomicValue_Store-4 50000000 27.3 ns/op 8 B/op 0 allocs/op +BenchmarkAtomicValue_Load-4 2000000000 0.73 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/gogf/gf/v2/container/gtype 49.454s +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" new file mode 100644 index 00000000000..61311cd68de --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/components/container-gpool' +title: 'Pool' +sidebar_position: 8 +hide_title: true +keywords: [object reuse, GoFrame framework, gpool, expiration time, creation method, destruction method, concurrency safety, sync.Pool, GC pressure, cache reuse] +description: "The basic functions and use cases of the object reuse pool gpool in the GoFrame framework, including providing object cache reuse, expiration time, and the definition of creation and destruction methods. The difference between gpool design and sync.Pool lies in the support of expiration time and the different functions of alleviating GC pressure, it is concurrency safe." +--- + +## Introduction + +Object reuse pool (concurrency safe). Provides cached reuse of objects with support for defining `expiration time`, `creation method`, and `destruction method`. + +**Use Cases**: + +Any object reuse scenario that needs to support timed expiration. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/gpool" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gpool](https://pkg.go.dev/github.com/gogf/gf/v2/container/gpool) + +Two points to note: + +1. The expiration time type of the `New` method is `time.Duration`. +2. The object `creation method` (`newFunc NewFunc`) return value includes an `error` return, which can provide feedback on the reason for failure when object creation fails. +3. The object `destruction method` (`expireFunc...ExpireFunc`) is an optional parameter for automatically invoking custom methods to destroy objects when they timeout/pool closure. + +## `gpool` vs `sync.Pool` + +`gpool` and `sync.Pool` both achieve object reuse, but their design intentions and use cases are quite different. + +The object lifecycle in `sync.Pool` does not support customizable expiration time because `sync.Pool` is not a `Cache`; the original intention of `sync.Pool` is to alleviate GC `pressure`, and the objects in `sync.Pool` are all cleared before `GC` starts; furthermore, `sync.Pool` does not support object creation and destruction methods. + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..0627d879a58 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/components/container-gpool-example' +title: 'Pool - Usage' +sidebar_position: 0 +hide_title: true +keywords: [object reuse,GoFrame,GoFrame framework,gpool,object pool,Go language,golang,network connection,resource management,programming tutorial] +description: "Using gpool for object reuse in the GoFrame framework. In the example, we create and operate an object pool, demonstrating methods for obtaining, returning, and handling expired objects. This method is very effective for managing short-lived resources and can significantly improve program performance and resource utilization." +--- + +## Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gpool" + "fmt" + "time" +) + +func main () { + // Create an object pool with an expiration time of 1 second + p := gpool.New(time.Second, nil) + + // Get an object from the pool, return nil and error information + fmt.Println(p.Get()) + + // Put an object into the pool + p.Put(1) + + // Get an object from the pool again, return 1 + fmt.Println(p.Get()) + + // After waiting for 2 seconds, try again and find the object has expired, return nil and error information + time.Sleep(2*time.Second) + fmt.Println(p.Get()) +} +``` + +## Create and Destroy Methods + +We can specify dynamic create and destroy methods. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gpool" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "time" +) + +func main() { + // Create an object reuse pool with an object expiration time of 3 seconds, and specify create and destroy methods + p := gpool.New(3*time.Second, func() (interface{}, error) { + return gtcp.NewConn("www.baidu.com:80") + }, func(i interface{}) { + glog.Println("expired") + i.(*gtcp.Conn).Close() + }) + conn, err := p.Get() + if err != nil { + panic(err) + } + result, err := conn.(*gtcp.Conn).SendRecv([]byte("HEAD / HTTP/1.1\n\n"), -1) + if err != nil { + panic(err) + } + fmt.Println(string(result)) + // Put back into the pool for reuse + p.Put(conn) + // Wait for a while to observe the expiration method call + time.Sleep(4*time.Second) +} +``` + +After execution, the terminal outputs: + +``` +HTTP/1.1 302 Found +Connection: Keep-Alive +Content-Length: 17931 +Content-Type: text/html +Date: Wed, 29 May 2019 11:23:20 GMT +Etag: "54d9749e-460b" +Server: bfe/1.0.8.18 + +2019-05-29 19:23:24.732 expired +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..3d1e37f47a6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/container' +title: 'Container' +sidebar_position: 0 +hide_title: true +keywords: [data structures, GoFrame, GoFrame framework, container components, web components, development guide, project structure, code structure, programming framework, technical documentation] +description: "How to create and manage data structures in the GoFrame framework. With container components, users can efficiently organize and optimize the code structure of projects. We also provide detailed development guides to help developers better understand and utilize the powerful features of the GoFrame framework." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" new file mode 100644 index 00000000000..557f6df99d5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" @@ -0,0 +1,41 @@ +--- +slug: '/docs/components/container-garray' +title: 'Array' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, garray, array container, concurrent safe, sorted array, interface documentation, data type, toolkit, data item uniqueness] +description: "Array type garray in the GoFrame framework and its basic functions. Through the garray module, users can use concurrency-safe array containers, supporting regular arrays and sorted arrays, providing data item uniqueness correction, int/string/interface{} data type support, and detailed interface documentation." +--- + +## Introduction + +Array container that provides regular arrays and sorted arrays, supporting data item uniqueness correction and concurrency-safe toggle control. + +**Application Scenarios**: + +Array operations. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/garray" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) + +Brief Description: + +1. There are many objects and methods under the `garray` module, so it is recommended to look carefully at the interface documentation. +2. `garray` supports the three common data types: `int`, `string`, `interface{}`. +3. `garray` supports both regular arrays and sorted arrays. The structure name of a regular array is defined in the format `*Array`, and the structure name of a sorted array is defined in the format `Sorted*Array`, as follows: + - `Array`, `intArray`, `StrArray` + - `SortedArray`, `SortedIntArray`, `SortedStrArray` + - Among them, the sorted array `SortedArray` requires a given sorting comparison method, and many `Comparator*` comparison methods are also defined in the toolkit `gutil`. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..c3fdfa410c9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,489 @@ +--- +slug: '/docs/components/container-garray-example' +title: 'Array - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, array, Go language, concurrency-safe, sorted array, array traversal, pop, random access, containment check, null value filtering] +description: "Using array types in the GoFrame framework, including creating concurrency-safe arrays, sorted arrays, array traversal and modification, as well as random access and popping operations. The basic array operations are demonstrated through example code, providing advanced usages such as sorting, filtering, and reversing arrays, helping developers better understand and master the array handling capabilities in the GoFrame framework." +--- + +### Ordinary Array + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main () { + // Create a concurrency-safe int type array + a := garray.NewIntArray(true) + + // Add data items + for i := 0; i < 10; i++ { + a.Append(i) + } + + // Get the current array length + fmt.Println(a.Len()) + + // Get the current list of data items + fmt.Println(a.Slice()) + + // Get the item at a specified index + fmt.Println(a.Get(6)) + + // Insert a data item after a specified index + a.InsertAfter(9, 11) + // Insert a data item before a specified index + a.InsertBefore(10, 10) + fmt.Println(a.Slice()) + + // Modify the data item at a specified index + a.Set(0, 100) + fmt.Println(a.Slice()) + + // Search for a data item, return the found index position + fmt.Println(a.Search(5)) + + // Remove the data item at a specified index + a.Remove(0) + fmt.Println(a.Slice()) + + // Concurrency-safe, write-lock operation + a.LockFunc(func(array []int) { + // Change the last item to 100 + array[len(array) - 1] = 100 + }) + + // Concurrency-safe, read-lock operation + a.RLockFunc(func(array []int) { + fmt.Println(array[len(array) - 1]) + }) + + // Clear the array + fmt.Println(a.Slice()) + a.Clear() + fmt.Println(a.Slice()) +} +``` + +After execution, the output is: + +```10 +[0 1 2 3 4 5 6 7 8 9] +6 true +[0 1 2 3 4 5 6 7 8 9 10 11] +[100 1 2 3 4 5 6 7 8 9 10 11] +5 +[1 2 3 4 5 6 7 8 9 10 11] +100 +[1 2 3 4 5 6 7 8 9 10 100] +[] +``` + +### Sorted Array + +The methods of sorted arrays are similar to ordinary arrays, but with automatic sorting and uniqueness filtering features. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main () { + // Customize sorted array, descending order (SortedIntArray manages data in ascending order) + a := garray.NewSortedArray(func(v1, v2 interface{}) int { + if v1.(int) < v2.(int) { + return 1 + } + if v1.(int) > v2.(int) { + return -1 + } + return 0 + }) + + // Add data + a.Add(2) + a.Add(3) + a.Add(1) + fmt.Println(a.Slice()) + + // Add duplicate data + a.Add(3) + fmt.Println(a.Slice()) + + // Retrieve data, return the last comparison index position, retrieval result + // Retrieval result: 0: match; <0: argument less than comparison value; >0: argument greater than comparison value + fmt.Println(a.Search(1)) + + // Set unique + a.SetUnique(true) + fmt.Println(a.Slice()) + a.Add(1) + fmt.Println(a.Slice()) +} +``` + +After execution, the output is: + +``` +[3 2 1] +[3 3 2 1] +3 0 +[3 2 1] +[3 2 1] +``` + +### `Iterate*` Array Traversal + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + // Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order + // with given callback function . + // If returns true, then it continues iterating; or false to stop. + array.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + // IteratorDesc iterates the array readonly in descending order with given callback function . + // If returns true, then it continues iterating; or false to stop. + array.IteratorDesc(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c + // 2 c + // 1 b + // 0 a +} +``` + +### `Pop*` Array Item Pop + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Any Pop* functions pick, delete and return the item from the array. + + fmt.Println(array.PopLeft()) + fmt.Println(array.PopLefts(2)) + fmt.Println(array.PopRight()) + fmt.Println(array.PopRights(2)) + + // Output: + // 1 true + // [2 3] + // 9 true + // [7 8] +} +``` + +### `Rand/PopRand` Random Access/Pop + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Randomly retrieve and return 2 items from the array. + // It does not delete the items from the array. + fmt.Println(array.Rands(2)) + + // Randomly pick and return one item from the array. + // It deletes the picked item from the array. + fmt.Println(array.PopRand()) +} +``` + +### `Contains/ContainsI` Containment Check + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + var array garray.StrArray + array.Append("a") + fmt.Println(array.Contains("a")) + fmt.Println(array.Contains("A")) + fmt.Println(array.ContainsI("A")) + + // Output: + // true + // false + // true +} +``` + +### `FilterEmpty/FilterNil` Null Value Filtering + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array1 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"}) + array2 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"}) + fmt.Printf("%#v\n", array1.FilterNil().Slice()) + fmt.Printf("%#v\n", array2.FilterEmpty().Slice()) + + // Output: + // []interface {}{0, 1, 2, "", []interface {}{}, "john"} + // []interface {}{1, 2, "john"} +} +``` + +### `Reverse` Array Reversal + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 5, 3}) + + // Reverse makes array with elements reverse. + fmt.Println(array.Reverse().Slice()) + + // Output: + // [3 5 1] +} +``` + +### `Shuffle` Random Sorting + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Shuffle randomly shuffles the array. + fmt.Println(array.Shuffle().Slice()) +} +``` + +### `Walk` Traversal Modification + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var array garray.StrArray + tables := g.SliceStr{"user", "user_detail"} + prefix := "gf_" + array.Append(tables...) + // Add prefix for given table names. + array.Walk(func(value string) string { + return prefix + value + }) + fmt.Println(array.Slice()) + + // Output: + // [gf_user gf_user_detail] +} +``` + +### `Join` Array Item Join + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{"a", "b", "c", "d"}) + fmt.Println(array.Join(",")) + + // Output: + // a,b,c,d +} +``` + +### `Chunk` Array Chunking + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Chunk splits an array into multiple arrays, + // the size of each array is determined by . + // The last chunk may contain less than size elements. + fmt.Println(array.Chunk(2)) + + // Output: + // [[1 2] [3 4] [5 6] [7 8] [9]] +} +``` + +### `Merge` Array Merging + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array1 := garray.NewFrom(g.Slice{1, 2}) + array2 := garray.NewFrom(g.Slice{3, 4}) + slice1 := g.Slice{5, 6} + slice2 := []int{7, 8} + slice3 := []string{"9", "0"} + fmt.Println(array1.Slice()) + array1.Merge(array1) + array1.Merge(array2) + array1.Merge(slice1) + array1.Merge(slice2) + array1.Merge(slice3) + fmt.Println(array1.Slice()) + + // Output: + // [1 2] + // [1 2 1 2 3 4 5 6 7 8 9 0] +} +``` + +### `JSON` Serialization/Deserialization + +All container types under the `garray` module implement the standard library `json` data format serialization/deserialization interface. + +1. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + type Student struct { + Id int + Name string + Scores *garray.IntArray + } + s := Student{ + Id: 1, + Name: "john", + Scores: garray.NewIntArrayFrom([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +After execution, the output is: + +``` + {"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *garray.IntArray + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +After execution, the output is: + +``` +{1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..43e38a77771 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1397 @@ +--- +slug: '/docs/components/container-garray-funcs' +title: 'Array - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, array manipulation, method introduction, string processing, data structures, array optimization, programming tips, code examples, Go programming, tutorial examples] +description: "Methods for handling array types using the GoFrame framework, including commonly used methods such as Append, At, Chunk, etc. You will learn how to add and access elements in arrays, split arrays, clear data, clone arrays, and determine array states. Each method comes with specific examples to aid understanding and application." +--- +:::tip +The following list of common methods may lag behind new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) +::: +## `Append` + +- Description: Append data to the end of the array; you can add an arbitrary number of strings. The `Append` method is an alias for `PushRight`. +- Format: + +```go +Append(value ...string) *StrArray +``` + +- Example: Create an empty array, set data, and append new data to the end of the array. + +```go +func ExampleStrArray_Append() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans"}) + s.Append("a", "b", "c") + fmt.Println(s) + + // Output: + // ["We","are","GF","fans","a","b","c"] +} +``` + +## `At` + +- Description: Return the data at the specified index of the array. +- Format: + +```go +At(index int) (value string) +``` + +- Example: Create an array and find the data at `index` 2. + +```go +func ExampleStrArray_At() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + sAt := s.At(2) + fmt.Println(sAt) + + // Output: + // GF +} +``` + +## `Chunk` + +- Description: Split the specified array into multiple arrays of a specified size `Size`, returning a value of `[][]string`. The last array may contain fewer elements than `Size`. +- Format: + +```go +Chunk(size int) [][]string +``` + +- Example: Create an array and split it into 3 arrays. + +```go +func ExampleStrArray_Chunk() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Chunk(3) + fmt.Println(r) + + // Output: + // [[a b c] [d e f] [g h]] +} +``` + +## `Clear` + +- Description: Delete all data in the current array. +- Format: + +```go +Clear() *StrArray +``` + +- Example: Create an empty array, set values, and then clear the data in the array. + +```go +func ExampleStrArray_Clear() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s) + fmt.Println(s.Clear()) + fmt.Println(s) + + // Output: + // ["a","b","c","d","e","f","g","h"] + // [] + // [] +} +``` + +## `Clone` + +- Description: Clone the current array. Returns a copy of the array that is identical to the current array. +- Format: + +```go +Clone() (newArray *StrArray) +``` + +- Example: Create an empty array, set values, and then clone it into a new array. + +```go +func ExampleStrArray_Clone() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Clone() + fmt.Println(r) + fmt.Println(s) + + // Output: + // ["a","b","c","d","e","f","g","h"] + // ["a","b","c","d","e","f","g","h"] +} +``` + +## `Contains` + +- Description: Determine if an array contains the given `String` value. The strings are case-sensitive. Returns a boolean value. +- Format: + +```go +Contains(value string) bool +``` + +- Example: Create an empty array, set values, and determine if it contains specific data `e` and `z`. + +```go +func ExampleStrArray_Contains() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Contains("e")) + fmt.Println(s.Contains("z")) + + // Output: + // true + // false +} +``` + +## `ContainsI` + +- Description: Determine if an array contains the given `String` value. The strings are case-insensitive. Returns a boolean value. +- Format: + +```go +ContainsI(value string) bool +``` + +- Example: Create an empty array, set values, and determine if it contains specific data `E` and `z`. + +```go +func ExampleStrArray_ContainsI() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.ContainsI("E")) + fmt.Println(s.ContainsI("z")) + + // Output: + // true + // false +} +``` + +## `CountValues` + +- Description: Count the occurrences of each value in the array. Returns a value of `map[string]int`. +- Format: + +```go +CountValues() map[string]int +``` + +- Example: Create an array and count the number of occurrences of each string in the array. + +```go +func ExampleStrArray_CountValues() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "c", "c", "d", "d"}) + fmt.Println(s.CountValues()) + + // Output: + // map[a:1 b:1 c:3 d:2] +} +``` + +## `Fill` + +- Description: Fill the array with the specified `value` starting at the given start position `startIndex`. Returns an error if any. +- Format: + +```go +Fill(startIndex int, num int, value string) error +``` + +- Example: Create an array and fill 3 data entries from the array starting at position `index` 2 with the string `here`. + +```go +func ExampleStrArray_Fill() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + s.Fill(2, 3, "here") + fmt.Println(s) + + // Output: + // ["a","b","here","here","here","f","g","h"] +} +``` + +## `FilterEmpty` + +- Description: Filter empty strings from the specified array. +- Format: + +```go +FilterEmpty() *StrArray +``` + +- Example: Create an array, assign a value, and filter out empty strings from the array. + +```go +func ExampleStrArray_FilterEmpty() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "", "c", "", "", "d"}) + fmt.Println(s.FilterEmpty()) + + // Output: + // ["a","b","c","d"] +} +``` + +## `Get` + +- Description: Return the value at the specified `index` in the array. The return value has two parameters: the `value` to be returned and whether the specified position data is found `found`; returns `true` if found, and `false` if not found. +- Format: + +```go +Get(index int) (value string, found bool) +``` + +- Example: Create an array, assign a value, and get the value at `index` 3 of the array. + +```go +func ExampleStrArray_Get() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + sGet, sBool := s.Get(3) + fmt.Println(sGet, sBool) + + // Output: + // fans true +} +``` + +## `InsertAfter` + +- Description: Insert the value `value` after the specified `index` location in the array. Returns an error if any. +- Format: + +```go +InsertAfter(index int, value string) error +``` + +- Example: Create an array and insert the string `here` after the value at `index` 1. + +```go +func ExampleStrArray_InsertAfter() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.InsertAfter(1, "here") + fmt.Println(s.Slice()) + + // Output: + // [a b here c d] +} +``` + +## `InsertBefore` + +- Description: Insert the value `value` before the specified `index` location in the array. Returns an error if any. +- Format: + +```go +InsertBefore(index int, value string) error +``` + +- Example: Create and initialize an array, and insert the string `here` before the value at `index` 1. + +```go +func ExampleStrArray_InsertBefore() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.InsertBefore(1, "here") + fmt.Println(s.Slice()) + + // Output: + // [a here b c d] +} +``` + +## `Interfaces` + +- Description: Return the current array as `[]interface{}`. +- Format: + +```go +Interfaces() []interface{} +``` + +- Example: Create and initialize an array, and print the contents of the returned `[]interface{}`. + +```go +func ExampleStrArray_Interfaces() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Interfaces() + fmt.Println(r) + + // Output: + // [a b c d e f g h] +} +``` + +## `IsEmpty` + +- Description: Determine whether the current array is an empty array. If it is an empty array, return `true`. If it is not an empty array, return `false`. +- Format: + +```go +IsEmpty() bool +``` + +- Example: Create and initialize two arrays, and determine whether they are empty arrays. + +```go +func ExampleStrArray_IsEmpty() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "", "c", "", "", "d"}) + fmt.Println(s.IsEmpty()) + s1 := garray.NewStrArray() + fmt.Println(s1.IsEmpty()) + + // Output: + // false + // true +} +``` + +## `Iterator` + +- Description: Array traversal. +- Format: + +```go +Iterator(f func(k int, v string) bool) +``` + +- Example: Create an array and traverse it. + +```go +func ExampleStrArray_Iterator() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c +} +``` + +## `IteratorAsc` + +- Description: Traverse the array in ascending order according to the given callback function `f`. If `f` returns `true`, continue traversing; otherwise, stop traversing. +- Format: + +```go +IteratorAsc(f func(k int, v string) bool) +``` + +- Example: Create an array and perform ascending traversal according to a custom function. + +```go +func ExampleStrArray_Iterator() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c +} +``` + +## `IteratorDesc` + +- Description: Traverse the array in descending order according to the given callback function `f`. If `f` returns `true`, continue traversing; otherwise, stop traversing. +- Format: + +```go +IteratorAsc(f func(k int, v string) bool) +``` + +- Example: Create an array and perform descending traversal according to a custom function. + +```go +func ExampleStrArray_IteratorDesc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.IteratorDesc(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 2 c + // 1 b + // 0 a +} +``` + +## `Join` + +- Description: Concatenate array elements using the given string delimiter `glue`. +- Format: + +```go +Join(glue string) string +``` + +- Example: Use the delimiter `','` to concatenate the strings in the array. + +```go +func ExampleStrArray_Join() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + fmt.Println(s.Join(",")) + + // Output: + // a,b,c +} +``` + +## `Len` + +- Description: Obtain the length of the array. +- Format: + +```go +Len() int +``` + +- Example: Create a new array, initialize it, and obtain its length. + +```go +func ExampleStrArray_Len() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Len()) + + // Output: + // 8 +} +``` + +## `LockFunc` + +- Description: Perform a write lock on the array with the callback function `f`. +- Format: + +```go +LockFunc(f func(array []string)) *StrArray +``` + +- Example: Create a new array, and modify the last data of the array while it is locked for writing. + +```go +func ExampleStrArray_LockFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.LockFunc(func(array []string) { + array[len(array)-1] = "GF fans" + }) + fmt.Println(s) + + // Output: + // ["a","b","GF fans"] +} +``` + +## `MarshalJSON` + +- Description: Implement the `JSON` serialization interface for `json.Marshal`. +- Format: + +```go +MarshalJSON() ([]byte, error) +``` + +- Example: Create new `JSON` format data and perform serialization operations, then print the result. + +```go +func ExampleStrArray_MarshalJSON() { + type Student struct { + Id int + Name string + Lessons []string + } + s := Student{ + Id: 1, + Name: "john", + Lessons: []string{"Math", "English", "Music"}, + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // Output: + // {"Id":1,"Name":"john","Lessons":["Math","English","Music"]} +} +``` + +## `Merge` + +- Description: Merge arrays by combining the contents of the specified array into the current array. The parameter `array` can be any type such as `garray` or `slice`. The main difference between `Merge` and `Append` is that `Append` only supports `slice` types, while `Merge` supports more parameter types. +- Format: + +```go +Merge(array interface{}) *StrArray +``` + +- Example: Create two new arrays `s1` and `s2`, and merge the data from `s2` into `s1`. + +```go +func ExampleStrArray_Merge() { + s1 := garray.NewStrArray() + s2 := garray.NewStrArray() + s1.SetArray(g.SliceStr{"a", "b", "c"}) + s2.SetArray(g.SliceStr{"d", "e", "f"}) + s1.Merge(s2) + fmt.Println(s1) + + // Output: + // ["a","b","c","d","e","f"] +} +``` + +## `NewStrArray` + +- Description: Create a new array. `safe` is an optional parameter, boolean, which is a concurrent safety switch with the default value being `False`. +- Format: + +```go +NewStrArray(safe ...bool) *StrArray +``` + +- Example: Create an empty array and add data. At this time, no `Safe` parameter is specified, and it defaults to non-concurrent safety settings. + +```go +func ExampleNewStrArray() { + s := garray.NewStrArray() + s.Append("We") + s.Append("are") + s.Append("GF") + s.Append("Fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF Fans] +} +``` + +## `NewStrArrayFrom` + +- Description: Create a new array based on the given array content. `safe` is an optional parameter, boolean, which is a concurrent safety switch with the default value being `False`. +- Format: + +```go +NewStrArrayFrom(array []string, safe ...bool) *StrArray +``` + +- Example: Create an empty array and add data based on the specified content. At this time, no `Safe` parameter is specified, and it defaults to non-concurrent safety settings. + +```go +func ExampleNewStrArrayFrom() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF fans !] 5 5 +} +``` + +## `NewStrArrayFromCopy` + +- Description: Create a new array based on a copy of the given array content. `safe` is an optional parameter, boolean, which is a concurrent safety switch with the default value being `False`. +- Format: + +```go +NewStrArrayFrom(array []string, safe ...bool) *StrArray +``` + +- Example: Create an empty array and add data based on the specified content. At this time, no `Safe` parameter is specified, and it defaults to non-concurrent safety settings. + +```go +func ExampleNewStrArrayFromCopy() { + s := garray.NewStrArrayFromCopy(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF fans !] 5 5 +} +``` + +## `NewStrArraySize` + +- Description: Create a new array according to the given `size` and `cap`. `safe` is an optional parameter, boolean, which is a concurrent safety switch with the default value being `False`. +- Format: + +```go +NewStrArraySize(size int, cap int, safe ...bool) *StrArray +``` + +- Example: Create an empty array with `Size` set to 3 and `Cap` set to 5, and add data. Print the relevant content. At this time, no `Safe` parameter is specified, and it defaults to non-concurrent safety settings. + +```go +func ExampleNewStrArraySize() { + s := garray.NewStrArraySize(3, 5) + s.Set(0, "We") + s.Set(1, "are") + s.Set(2, "GF") + s.Set(3, "fans") + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF] 3 5 +} +``` + +## `Pad` + +- Description: Fill the array with the specified size `size` and value `value`. If the size `size` is positive, it is filled from the right side of the array. If the size `size` is negative, it is filled from the left side of the array. If the size `size` is exactly equal to the length of the array, no data will be filled. +- Format: + +```go +Pad(size int, value string) *StrArray +``` + +- Example: Create a new array and fill it from the left with the specified string `here` to size 7, then fill the array with the specified string `there` to size 10. + +```go +func ExampleStrArray_Pad() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Pad(7, "here") + fmt.Println(s) + s.Pad(-10, "there") + fmt.Println(s) + + // Output: + // ["a","b","c","here","here","here","here"] + // ["there","there","there","a","b","c","here","here","here","here"] +} +``` + +## `PopLeft` + +- Description: Pop a string from the left side of the array. `value` is the popped string. The updated array data is the remaining data. When the array is empty, `found` is `false`. +- Format: + +```go +PopLeft() (value string, found bool) +``` + +- Example: Create a new array, pop the leftmost data, and print the remaining data. + +```go +func ExampleStrArray_PopLeft() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PopLeft() + fmt.Println(s.Slice()) + + // Output: + // [b c d] +} +``` + +## `PopLefts` + +- Description: Multiple string data is popped from the left side of the array. The return value is the popped string data, and the number of popped data is `size`. If `size` is greater than the `size` of the array, the method will return all the data in the array. If `size <= 0 or empty`, then it will return `nil`. +- Format: + +```go +PopLefts(size int) []string +``` + +- Example: Create a new array, pop the leftmost 2 data, and print the popped data and the remaining data of the original array. + +```go +func ExampleStrArray_PopLefts() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopLefts(2) + fmt.Println(r) + fmt.Println(s) + + // Output: + // [a b] + // ["c","d","e","f","g","h"] +} +``` + +## `PopRand` + +- Description: Randomly pop 1 data from the array. The return value is the popped string data. If the array is `empty`, then `found` will return `false`. +- Format: + +```go +PopRand() (value string, found bool) +``` + +- Example: Create a new array and randomly pop 1 data from the array, then print the popped data. + +```go +func ExampleStrArray_PopRand() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r, _ := s.PopRand() + fmt.Println(r) + + // May Output: + // e +} +``` + +## `PopRands` + +- Description: Randomly pop `size` data from the array. The return value is the popped string data. If `size <= 0 or empty`, then it will return `nil` . +- Format: + +```go +PopRands(size int) []string +``` + +- Example: Create a new array and randomly pop 2 data from the array, then print the popped data. + +```go +func ExampleStrArray_PopRands() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopRands(2) + fmt.Println(r) + + // May Output: + // [e c] +} +``` + +## `PopRight` + +- Description: Pop a string from the right side of the array. `value` is the popped string. The updated array data is the remaining data. When the array is empty, `found` is `false`. +- Format: + +```go +PopRight() (value string, found bool) +``` + +- Example: Create a new array, pop the rightmost data, and print the remaining data. + +```go +func ExampleStrArray_PopRight() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PopRight() + fmt.Println(s.Slice()) + + // Output: + // [a b c] +} +``` + +## `PopRights` + +- Description: Multiple string data is popped from the right side of the array. The return value is the popped string data, and the number of popped data is `size`. If `size` is greater than the `size` of the array, the method will return all the data in the array. If `size <= 0 or empty`, then it will return `nil`. +- Format: + +```go +PopRights(size int) []string +``` + +- Example: Create a new array, pop the rightmost 2 data, and print the popped data and the remaining data of the original array. + +```go +func ExampleStrArray_PopRights() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopRights(2) + fmt.Println(r) + fmt.Println(s) + + // Output: + // [g h] + // ["a","b","c","d","e","f"] +} +``` + +## `PushLeft` + +- Description: Push one or more strings onto the left side of the array. +- Format: + +```go +PushLeft(value ...string) *StrArray +``` + +- Example: Create a new array, push multiple strings onto the left side of the array, and print the updated data. + +```go +func ExampleStrArray_PushLeft() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PushLeft("We", "are", "GF", "fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans a b c d] +} +``` + +## `PushRight` + +- Description: Push one or more strings onto the right side of the array. +- Format: + +```go +PushRight(value ...string) *StrArray +``` + +- Example: Create a new array, push multiple strings onto the right side of the array, and print the updated data. + +```go +func ExampleStrArray_PushRight() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PushRight("We", "are", "GF", "fans") + fmt.Println(s.Slice()) + + // Output: + // [a b c d We are GF fans] +} +``` + +## `Rand` + +- Description: Randomly select 1 string from the array (non-destructive). +- Format: + +```go +Rand() (value string, found bool) +``` + +- Example: Create a new array, randomly select one string from the array. + +```go +func ExampleStrArray_Rand() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Rand()) + + // May Output: + // c true +} +``` + +## `Rands` + +- Description: Randomly select `size` strings from the array (non-destructive). +- Format: + +```go +Rands(size int) []string +``` + +- Example: Create a new array, randomly select 3 strings from the array. + +```go +func ExampleStrArray_Rands() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Rands(3)) + + // May Output: + // [e h e] +} +``` + +## `Range` + +- Description: Retrieve data in the specified range of the array. If used in concurrent safety mode, this method returns a `slice` copy. +- Format: + +```go +Range(start int, end ...int) []string +``` + +- Example: Create a new array, and retrieve data from `index` 2 to 5. + +```go +func ExampleStrArray_Range() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Range(2, 5) + fmt.Println(r) + + // Output: + // [c d e] +} +``` + +## `Remove` + +- Description: Remove data from the array at the position `index`. If `index` is out of bounds, `found` returns `false`. +- Format: + +```go +Remove(index int) (value string, found bool) +``` + +- Example: Create a new array, remove data at `index` 1. + +```go +func ExampleStrArray_Remove() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.Remove(1) + fmt.Println(s.Slice()) + + // Output: + // [a c d] +} +``` + +## `RemoveValue` + +- Description: Remove the specified data `value` from the array. If `value` is found in the array, `found` returns `true`; otherwise, `found` returns `false`. +- Format: + +```go +RemoveValue(value string) bool +``` + +- Example: Create a new array, remove the value `b` from the array. + +```go +func ExampleStrArray_RemoveValue() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.RemoveValue("b") + fmt.Println(s.Slice()) + + // Output: + // [a c d] +} +``` + +## `Replace` + +- Description: Replace the original string array with the specified string array, starting from the beginning of the original array. +- Format: + +```go +Replace(array []string) *StrArray +``` + +- Example: Create a new array and replace it with the specified string array. + +```go +func ExampleStrArray_Replace() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice()) + s.Replace(g.SliceStr{"Happy", "coding"}) + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans !] + // [Happy coding GF fans !] +} +``` + +## `Reverse` + +- Description: Reverse the entire array, for example: ["qaz","wsx","edc","rfv"] => ["rfv","edc","wsx","qaz"]. +- Format: + +```go +Replace(array []string) *StrArray +``` + +- Example: Create a new array, initialize it, reverse it, and print it. + +```go +func ExampleStrArray_Reverse() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "m", "c"}) + fmt.Println(s.Reverse()) + + // Output: + // ["c","m","a"] +} +``` + +## `RLockFunc` + +- Description: Perform a read lock on the array with a custom callback function `f`. +- Format: + +```go +RLockFunc(f func(array []string)) *StrArray +``` + +- Example: Create a new array, traverse the array and print its elements in the callback function `f`. + +```go +func ExampleStrArray_RLockFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e"}) + s.RLockFunc(func(array []string) { + for i := 0; i < len(array); i++ { + fmt.Println(array[i]) + } + }) + + // Output: + // a + // b + // c + // d + // e +} +``` + +## `Search` + +- Description: Search for the specified string in the array and return the `index` in the array. If not found, return `-1`. +- Format: + +```go +Search(value string) int +``` + +- Example: Create a new array and search for the strings `e` and `z` in the array. + +```go +func ExampleStrArray_Search() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Search("e")) + fmt.Println(s.Search("z")) + + // Output: + // 4 + // -1 +} +``` + +## `Set` + +- Description: Set the `index` position in the array to value `value`. If `index < 0` or `index` is out of bounds, an error `error` is returned. +- Format: + +```go +Set(index int, value string) error +``` + +- Example: Create a new array with a length of 3. Set the array value, but the value is only set in order to `index` 2, because of the array's length constraint, the last value isn’t set successfully. + +```go +func ExampleStrArray_Set() { + s := garray.NewStrArraySize(3, 5) + s.Set(0, "We") + s.Set(1, "are") + s.Set(2, "GF") + s.Set(3, "fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF] +} +``` + +## `SetArray` + +- Description: Assign value to the array according to the given `slice` array content +- Format: + +```go +SetArray(array []string) *StrArray +``` + +- Example: Create a new array, assign a value to it, and print it. + +```go +func ExampleStrArray_SetArray() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans !] +} +``` + +## `Shuffle` + +- Description: Randomly shuffle the contents of the array +- Format: + +```go +Shuffle() *StrArray +``` + +- Example: Create a new array, assign a value, shuffle it, and print it. + +```go +func ExampleStrArray_Shuffle() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Shuffle()) + + // May Output: + // ["a","c","e","d","b","g","f","h"] +} +``` + +## `Slice` + +- Description: Get the `slice` data of the array. Note that if it is in a concurrent safety mode, a copy is returned data, otherwise a pointer to the data is returned. +- Format: + +```go +Shuffle() *StrArray +``` + +- Example: Create a new array, assign a value, and print the slice data of the array. + +```go +func ExampleStrArray_Slice() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Slice()) + + // Output: + // [a b c d e f g h] +} +``` + +## `Sort` + +- Description: Sort array contents in ascending order. `reverse` controls the sort direction, with `true` for ascending order and `false` for descending order. +- Format: + +```go +Sort(reverse ...bool) *StrArray +``` + +- Example: Create a new array, assign a value, and sort it in ascending order. + +```go +func ExampleStrArray_Sort() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"b", "d", "a", "c"}) + a := s.Sort() + fmt.Println(a) + + // Output: + // ["a","b","c","d"] +} +``` + +## `SortFunc` + +- Description: Sort the content of the array using a custom function `less`. +- Format: + +```go +SortFunc(less func(v1, v2 string) bool) *StrArray +``` + +- Example: Create a new array, assign a value, first sort it in descending order using a custom function, then sort it in ascending order using a custom function, and print the results. + +```go +func ExampleStrArray_SortFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"b", "c", "a"}) + fmt.Println(s) + s.SortFunc(func(v1, v2 string) bool { + return gstr.Compare(v1, v2) > 0 + }) + fmt.Println(s) + s.SortFunc(func(v1, v2 string) bool { + return gstr.Compare(v1, v2) < 0 + }) + fmt.Println(s) + + // Output: + // ["b","c","a"] + // ["c","b","a"] + // ["a","b","c"] +} +``` + +## `String` + +- Description: Convert the current array to `string`. +- Format: + +```go +String() string +``` + +- Example: Create a new array, assign a value, convert the array to `string`, and print the result. + +```go +func ExampleStrArray_String() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + fmt.Println(s.String()) + + // Output: + // ["a","b","c"] +} +``` + +## `Subslice` + +- Description: Obtain a slice of the array according to the given offset `offset` and length parameters `length`. Note that if it is used in concurrent safety mode, copy data is returned, otherwise a pointer to the data is returned. If the offset `offset` is a non-negative number, the slice is made from the beginning of the array; otherwise, if it is negative, the slice is made from the end of the array. +- Format: + +```go +SubSlice(offset int, length ...int) +``` + +- Example: Create a new array, assign a value, convert the array to `string`, and print the result. + +```go +func ExampleStrArray_SubSlice() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.SubSlice(3, 4) + fmt.Println(r) + + // Output: + // [d e f g] +} +``` + +## `Sum` + +- Description: Sum the integer values in the array. +- Format: + +```go +Sum() (sum int) +``` + +- Example: Create a new array, assign a value, and sum the integer values in the array. + +```go +func ExampleStrArray_Sum() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"3", "5", "10"}) + a := s.Sum() + fmt.Println(a) + + // Output: + // 18 +} +``` + +## `Unique` + +- Description: Deduplicate data in the array. +- Format: + +```go +Unique() *StrArray +``` + +- Example: Create a new array, assign a value, and deduplicate data in the array. + +```go +func ExampleStrArray_Unique() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "c", "c", "d", "d"}) + fmt.Println(s.Unique()) + + // Output: + // ["a","b","c","d"] +} +``` + +## `UnmarshalJSON` + +- Description: Implement the `UnmarshalJSON` interface of `json.Unmarshal`. +- Format: + +```go +UnmarshalJSON(b []byte) error +``` + +- Example: Create a `byte` slice, assign it to the structure, perform deserialization operations, and print the corresponding content. + +```go +func ExampleStrArray_UnmarshalJSON() { + b := []byte(`{"Id":1,"Name":"john","Lessons":["Math","English","Sport"]}`) + type Student struct { + Id int + Name string + Lessons *garray.StrArray + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // Output: + // {1 john ["Math","English","Sport"]} +} +``` + +## `UnmarshalValue` + +- Description: Implement the deserialization interface for arbitrary type values. +- Format: + +```go +UnmarshalValue(value interface{}) error +``` + +- Example: Create a structure, perform deserialization operations on its values, and print the corresponding content. + +```go +func ExampleStrArray_UnmarshalValue() { + type Student struct { + Name string + Lessons *garray.StrArray + } + var s *Student + gconv.Struct(g.Map{ + "name": "john", + "lessons": []byte(`["Math","English","Sport"]`), + }, &s) + fmt.Println(s) + + var s1 *Student + gconv.Struct(g.Map{ + "name": "john", + "lessons": g.SliceStr{"Math", "English", "Sport"}, + }, &s1) + fmt.Println(s1) + + // Output: + // &{john ["Math","English","Sport"]} + // &{john ["Math","English","Sport"]} +} +``` + +## `Walk` + +- Description: Traverse and modify the contents of the array with the custom function `f`. +- Format: + +```go +Walk(f func(value string) string) *StrArray +``` + +- Example: Create an array, traverse and modify the contents of the array by appending prefixes to each string, and print the relevant content. + +```go +func ExampleStrArray_Walk() { + var array garray.StrArray + tables := g.SliceStr{"user", "user_detail"} + prefix := "gf_" + array.Append(tables...) + // Add prefix for given table names. + array.Walk(func(value string) string { + return prefix + value + }) + fmt.Println(array.Slice()) + + // Output: + // [gf_user gf_user_detail] +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" new file mode 100644 index 00000000000..3d415a05aeb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/container-gtree' +title: 'Tree' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame, tree container, gtree, concurrency-safe, Red-black tree, AVL tree, BTree, sorting, big data processing, data structure] +description: "This document introduces the tree container components provided by the GoFrame framework, including data structures such as RedBlackTree, AVLTree, and BTree. Tree containers support concurrency safety and ordered traversal, making them suitable for large data storage needs. With the GoFrame framework, developers can efficiently implement scenarios such as associative arrays, sorted key-value pairs, and large data volume memory CRUD." +--- + +## Introduction + +Concurrent-safe feature support for tree containers, with the characteristics of tree data structures supporting ordered traversal, low memory consumption, stable complexity, and suitability for large data storage. This module includes tree containers for multiple data structures: `RedBlackTree`, `AVLTree`, and `BTree`. + +| Type | Data Structure | Average Complexity | Supports Sorting | Ordered Traversal | Description | +| --- | --- | --- | --- | --- | --- | +| `RedBlackTree` | Red-black tree | `O(log N)` | Yes | Yes | Good write performance | +| `AVLTree` | Height-balanced tree | `O(log N)` | Yes | Yes | Good search performance | +| `BTree` | B-tree/B-tree | `O(log N)` | Yes | Yes | Commonly used for external storage | + +> Reference link: [https://en.wikipedia.org/wiki/Binary\_tree](https://en.wikipedia.org/wiki/Binary_tree) + +**Usage Scenarios**: + +Associative array scenarios, sorted key-value pair scenarios, large data volume memory CRUD scenarios, etc. + +**Usage Method**: + +```go +import "github.com/gogf/gf/v2/container/gtree" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree) + +The API methods for several containers are very similar, characterized by needing to provide methods for sorting during initialization. + +The `gutil` module provides some common basic type comparison methods that can be directly used in the program, and examples are also available later. + +```go +func ComparatorByte(a, b interface{}) int +func ComparatorFloat32(a, b interface{}) int +func ComparatorFloat64(a, b interface{}) int +func ComparatorInt(a, b interface{}) int +func ComparatorInt16(a, b interface{}) int +func ComparatorInt32(a, b interface{}) int +func ComparatorInt64(a, b interface{}) int +func ComparatorInt8(a, b interface{}) int +func ComparatorRune(a, b interface{}) int +func ComparatorString(a, b interface{}) int +func ComparatorTime(a, b interface{}) int +func ComparatorUint(a, b interface{}) int +func ComparatorUint16(a, b interface{}) int +func ComparatorUint32(a, b interface{}) int +func ComparatorUint64(a, b interface{}) int +func ComparatorUint8(a, b interface{}) int +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..030bf0596b9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,162 @@ +--- +slug: '/docs/components/container-gtree-example' +title: 'Tree - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Tree Structure,RedBlackTree,AVL Tree,gtree,Data Structure,Traversal,Basic Usage,Example] +description: "Basic operations using tree-type data structures in the GoFrame framework. Demonstrates how to create and operate on RedBlackTree and AVL trees through example code, including adding, deleting, and traversing nodes, enabling developers to easily achieve efficient data storage and lookup." +--- + +## Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gtree" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + m := gtree.NewRedBlackTree(gutil.ComparatorInt) + + // Set key-value pairs + for i := 0; i < 10; i++ { + m.Set(i, i*10) + } + // Query size + fmt.Println(m.Size()) + // Batch set key-value pairs (different data type objects have different parameters) + m.Sets(map[interface{}]interface{}{ + 10: 10, + 11: 11, + }) + fmt.Println(m.Size()) + + // Check existence + fmt.Println(m.Contains(1)) + + // Query key-value + fmt.Println(m.Get(1)) + + // Remove item + m.Remove(9) + fmt.Println(m.Size()) + + // Batch remove + m.Removes([]interface{}{10, 11}) + fmt.Println(m.Size()) + + // Current list of keys (random order) + fmt.Println(m.Keys()) + // Current list of values (random order) + fmt.Println(m.Values()) + + // Query key, if key does not exist, write the given default value + fmt.Println(m.GetOrSet(100, 100)) + + // Remove key-value pair and return the corresponding value + fmt.Println(m.Remove(100)) + + // Iterate over map + m.IteratorAsc(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + fmt.Println() + + // Clear map + m.Clear() + + // Check if map is empty + fmt.Println(m.IsEmpty()) +} +``` + +After execution, the output is: + +```10 +12 +true +10 +11 +9 +[0 1 2 3 4 5 6 7 8] +[0 10 20 30 40 50 60 70 80] +100 +100 +0:0 1:10 2:20 3:30 4:40 5:50 6:60 7:70 8:80 +true +``` + +## Pre-order/Post-order Traversal + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gtree" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + tree := gtree.NewAVLTree(gutil.ComparatorInt) + for i := 0; i < 10; i++ { + tree.Set(i, i*10) + } + // Print tree + tree.Print() + // Pre-order traversal + fmt.Println("ASC:") + tree.IteratorAsc(func(key, value interface{}) bool { + fmt.Println(key, value) + return true + }) + // Post-order traversal + fmt.Println("DESC:") + tree.IteratorDesc(func(key, value interface{}) bool { + fmt.Println(key, value) + return true + }) +} +``` + +After execution, the output is: + +```AVLTree +│ ┌── 9 +│ ┌── 8 +│ ┌── 7 +│ │ │ ┌── 6 +│ │ └── 5 +│ │ └── 4 +└── 3 + │ ┌── 2 + └── 1 + └── 0 + +ASC: +0 0 +1 10 +2 20 +3 30 +4 40 +5 50 +6 60 +7 70 +8 80 +9 90 +DESC: +9 90 +8 80 +7 70 +6 60 +5 50 +4 40 +3 30 +2 20 +1 10 +0 0 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..6b7c9d99619 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1301 @@ +--- +slug: '/docs/components/container-gtree-funcs' +title: 'Tree - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, tree structure, BTree, node management, concurrency safety, key-value operations, height calculation, search, tree iteration, data manipulation] +description: "A variety of operations for tree structures in the GoFrame framework, including NewBTree, Clone, Set, Get, etc. Detailed examples demonstrate how to use these methods for node addition, search, deletion, and more, while supporting concurrency-safe settings. Users can quickly master the techniques for using tree structures through this documentation." +--- +:::tip +The following is a list of commonly used methods; documentation updates may lag behind new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree) +::: +## `NewBTree` + +- Description: `NewBTree` creates a `BTree` using `m` (maximum number of child nodes) and a custom comparator. The `safe` parameter specifies whether to use a concurrency-safe `tree`, with a default value of `false`. + +- Note: The `m` parameter must be greater than or equal to `3`, otherwise it will `panic`. +- Format: + +```go +NewBTree(m int, comparator func(v1, v2 interface{}) int, safe ...bool) *BTree +``` + +- Example: + +```go +func ExampleNewBTree() { + bTree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + bTree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + fmt.Println(bTree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `NewBTreeFrom` + +- Description: `NewBTreeFrom` creates a `BTree` using `m` (maximum number of child nodes), a custom comparator, and data of type `map[interface{}]interface{}`. The `safe` parameter specifies whether to use a concurrency-safe `tree`, with a default value of `false`. + +- Note: The `m` parameter must be greater than or equal to `3`, otherwise it will `panic`. +- Format: + +```go +NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, safe ...bool) *BTree +``` + +- Example: + +```go +func ExampleNewBTreeFrom() { + bTree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + bTree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + otherBTree := gtree.NewBTreeFrom(3, gutil.ComparatorString, bTree.Map()) + fmt.Println(otherBTree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `Clone` + +- Description: `Clone` returns a new `BTree`, which is a copy of the current `tree`. + +- Format: + +```go +Clone() *BTree +``` + +- Example: + +```go +func ExampleBTree_Clone() { + b := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + b.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + tree := b.Clone() + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // 6 +} +``` + + +## `Set` + +- Description: `Set` sets the `key/value` for the `tree`. + +- Format: + +```go +Set(key interface{}, value interface{}) +``` + +- Example: + +```go +func ExampleBTree_Set() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // 6 +} +``` + + +## `Sets` + +- Description: `Sets` sets `key/value` for the `tree` in batches. + +- Format: + +```go +Sets(data map[interface{}]interface{}) +``` + +- Example: + +```go +func ExampleBTree_Sets() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + tree.Sets(map[interface{}]interface{}{ + "key1": "val1", + "key2": "val2", + }) + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key1:val1 key2:val2] + // 2 +} +``` + + +## `Get` + +- Description: `Get` returns the value `value` corresponding to the parameter `key`. If `key` does not exist, it returns `Nil`. + +- Format: + +```go +Get(key interface{}) (value interface{}) +``` + +- Example: + +```go +func ExampleBTree_Get() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Get("key1")) + fmt.Println(tree.Get("key10")) + + // Output: + // val1 + // +} +``` + + +## `GetOrSet` + +- Description: `GetOrSet` returns `value` if `key` exists; if `key` does not exist, it sets the key-value with `key` and `value`, then returns the value. + +- Format: + +```go +GetOrSet(key interface{}, value interface{}) interface{} +``` + +- Example: + +```go +func ExampleBTree_GetOrSet() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSet("key1", "newVal1")) + fmt.Println(tree.GetOrSet("key6", "val6")) + + // Output: + // val1 + // val6 +} +``` + + +## `GetOrSetFunc` + +- Description: `GetOrSetFunc` returns `value` if `key` exists; if `key` does not exist, it sets the key-value with `key` and the return value of `func f`, then returns the value. + +- Format: + +```go +GetOrSetFunc(key interface{}, f func() interface{}) interface{} +``` + +- Example: + +```go +func ExampleBTree_GetOrSetFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSetFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetOrSetFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `GetOrSetFuncLock` + +- Description: `GetOrSetFunc` returns `value` if `key` exists; if `key` does not exist, it sets the key-value with `key` and the return value of `func f`, then returns the value. + +- Note: The difference between `GetOrSetFuncLock` and `GetOrSetFunc` is that it executes the function `f` in a write lock. +- Format: + +```go +GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} +``` + +- Example: + +```go +func ExampleBTree_GetOrSetFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSetFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetOrSetFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## GetVar + +- Description: `GetVar` searches and returns the key value corresponding to the key name `key`, the type is `*gvar.Var`. + +- Note: The returned `gvar.Var` is not concurrent safe. +- Format: + +```go +GetVar(key interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleBTree_GetVar() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVar("key1").String()) + + // Output: + // val1 +} +``` + + +## `GetVarOrSet` + +- Description: `GetVarOrSet` returns the result of `GetOrSet`, the type is `*gvar.Var`. + +- Note: The returned `gvar.Var` is not concurrent safe. +- Format: + +```go +GetVarOrSet(key interface{}, value interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleBTree_GetVarOrSet() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSet("key1", "newVal1")) + fmt.Println(tree.GetVarOrSet("key6", "val6")) + + // Output: + // val1 + // val6 +} +``` + + +## GetVarOrSetFunc + +- Description: `GetVarOrSetFunc` returns the result of `GetOrSetFunc`, the type is `*gvar.Var`. +- Note: The returned `gvar.Var` is not concurrent safe. +- Format: + +```go +GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleBTree_GetVarOrSetFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSetFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetVarOrSetFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `GetVarOrSetFuncLock` + +- Description: `GetVarOrSetFuncLock` returns the result of `GetOrSetFuncLock`, the type is `*gvar.Var`. + +- Note: The returned `gvar.Var` is not concurrent safe. +- Format: + +```go +GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleBTree_GetVarOrSetFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSetFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetVarOrSetFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `SetIfNotExist` + +- Description: If `key` does not exist, `SetIfNotExist` sets the key-value pair `key/value` for the map and returns `true`. If `key` exists, it returns `false`, and `value` will be ignored. + +- Format: + +```go +SetIfNotExist(key interface{}, value interface{}) bool +``` + +- Example: + +```go +func ExampleBTree_SetIfNotExist() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExist("key1", "newVal1")) + fmt.Println(tree.SetIfNotExist("key6", "val6")) + + // Output: + // false + // true +} +``` + + +## `SetIfNotExistFunc` + +- Description: If `key` does not exist, `SetIfNotExistFunc` sets the value to the return value of the function `f` and returns `true`. If `key` exists, it returns `false`, and `value` will be ignored. + +- Format: + +```go +SetIfNotExistFunc(key interface{}, f func() interface{}) bool +``` + +- Example: + +```go +func ExampleBTree_SetIfNotExistFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExistFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.SetIfNotExistFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // false + // true +} +``` + + +## `SetIfNotExistFuncLock` + +- Description: If `key` does not exist, `SetIfNotExistFunc` sets the value to the return value of `func c`, then returns `true`. If `key` exists, it returns `false`, and `value` will be ignored. + +- The difference between `SetIfNotExistFuncLock` and `SetIfNotExistFunc` is that it executes the function `f` within `mutex.Lock`. +- Format: + +```go +SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool +``` + +- Example: + +```go +func ExampleBTree_SetIfNotExistFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExistFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.SetIfNotExistFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // false + // true +} +``` + + +## `Contains` + +- Description: `Contains` checks if `key` exists in the `tree`. If `key` exists, it returns `true`; otherwise, it returns `false`. + +- Format: + +```go +Contains(key interface{}) bool +``` + +- Example: + +```go +func ExampleBTree_Contains() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Contains("key1")) + fmt.Println(tree.Contains("key6")) + + // Output: + // true + // false +} +``` + + +## `Remove` + +- Description: Delete the `value` from `tree` according to the given `key` and return this deleted `value`. + +- Format: + +```go +Remove(key interface{}) (value interface{}) +``` + +- Example: + +```go +func ExampleBTree_Remove() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Remove("key1")) + fmt.Println(tree.Remove("key6")) + fmt.Println(tree.Map()) + + // Output: + // val1 + // + // map[key0:val0 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `Removes` + +- Description: `Removes` deletes `value` of `tree` in batches according to the given `key`. + +- Format: + +```go +Removes(keys []interface{}) +``` + +- Example: + +```go +func ExampleBTree_Removes() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + removeKeys := make([]interface{}, 2) + removeKeys = append(removeKeys, "key1") + removeKeys = append(removeKeys, "key6") + + tree.Removes(removeKeys) + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## IsEmpty + +- Description: `IsEmpty` checks if `tree` is empty. If `tree` is empty, it returns `true`; otherwise, it returns `false`. + +- Format: + +```go +IsEmpty() bool +``` + +- Example: + +```go +func ExampleBTree_IsEmpty() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + fmt.Println(tree.IsEmpty()) + + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.IsEmpty()) + + // Output: + // true + // false +} +``` + + +## `Size` + +- Description: `Size` returns the size of the `tree`. + +- Format: + +```go +Size() int +``` + +- Example: + +```go +func ExampleBTree_Size() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + fmt.Println(tree.Size()) + + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Size()) + + // Output: + // 0 + // 6 +} +``` + + +## `Keys` + +- Description: `Keys` returns all `key`s in ascending order. + +- Format: + +```go +Keys() []interface{} +``` + +- Example: + +```go +func ExampleBTree_Keys() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 6; i > 0; i-- { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Keys()) + + // Output: + // [key1 key2 key3 key4 key5 key6] +} +``` + + +## `Values` + +- Description: `Values` returns all `value`s in ascending order of `key`. + +- Format: + +```go +Values() []interface{} +``` + +- Example: + +```go +func ExampleBTree_Values() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 6; i > 0; i-- { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Values()) + + // Output: + // [val1 val2 val3 val4 val5 val6] +} +``` + + +## `Map` + +- Description: `Map` returns all `key/value` pairs in the form of a `map`. + +- Format: + +```go +Map() map[interface{}]interface{} +``` + +- Example: + +```go +func ExampleBTree_Map() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `MapStrAny` + +- Description: `MapStrAny` returns all `key/value` pairs in the form of `map[string]interface{}`. + +- Format: + +```go +MapStrAny() map[string]interface{} +``` + +- Example: + +```go +func ExampleBTree_MapStrAny() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set(1000+i, "val"+gconv.String(i)) + } + + fmt.Println(tree.MapStrAny()) + + // Output: + // map[1000:val0 1001:val1 1002:val2 1003:val3 1004:val4 1005:val5] +} +``` + + +## `Clear` + +- Description: `Clear` deletes all data in the `tree`. + +- Format: + +```go +Clear() +``` + +- Example: + +```go +func ExampleBTree_Clear() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set(1000+i, "val"+gconv.String(i)) + } + fmt.Println(tree.Size()) + + tree.Clear() + fmt.Println(tree.Size()) + + // Output: + // 6 + // 0 +} +``` + + +## `Replace` + +- Description: `Replace` replaces the `key/value` in the `tree` with data of type `map[interface{}]interface{}`. + +- Format: + +```go +Replace(data map[interface{}]interface{}) +``` + +- Example: + +```go +func ExampleBTree_Replace() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + + data := map[interface{}]interface{}{ + "newKey0": "newVal0", + "newKey1": "newVal1", + "newKey2": "newVal2", + } + + tree.Replace(data) + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // map[newKey0:newVal0 newKey1:newVal1 newKey2:newVal2] +} +``` + + +## `Height` + +- Description: `Height` returns the height of the `tree`. + +- Format: + +```go +Height() int +``` + +- Example: + +```go +func ExampleBTree_Height() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 0; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Height()) + + // Output: + // 6 +} +``` + + +## `Left` + +- Description: `Left` returns the leftmost (smallest) `node` of type `*BTreeEntry`, or `nil` if the `tree` is empty. + +- Format: + +```go +Left() *BTreeEntry +``` + +- Example: + +```go +func ExampleBTree_Left() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 1; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Left().Key, tree.Left().Value) + + emptyTree := gtree.NewBTree(3, gutil.ComparatorInt) + fmt.Println(emptyTree.Left()) + + // Output: + // 1 1 + // +} +``` + + +## `Right` + +- Description: `Right` returns the rightmost (largest) `node` of type `*BTreeEntry`, or `nil` if the `tree` is empty. + +- Format: + +```go +Right() *BTreeEntry +``` + +- Example: + +```go +func ExampleBTree_Right() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 1; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Right().Key, tree.Right().Value) + + emptyTree := gtree.NewBTree(3, gutil.ComparatorInt) + fmt.Println(emptyTree.Left()) + + // Output: + // 99 99 + // +} +``` + + +## `String` + +- Description: `String` returns a display (for debugging) of the `node` in the `tree`. + +- Format: + +```go +String() string +``` + +- Example: + +```go +func ExampleBTree_String() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.String()) + + // Output: + // key0 + // key1 + // key2 + // key3 + // key4 + // key5 +} +``` + + +## `Search` + +- Description: `Search` searches the `tree` using the parameter `key`. If the `key` is found, it returns its corresponding key-value and returns the parameter `found` as `true`, otherwise `false`. + +- Format: + +```go +Search(key interface{}) (value interface{}, found bool) +``` + +- Example: + +```go +func ExampleBTree_Search() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Search("key0")) + fmt.Println(tree.Search("key6")) + + // Output: + // val0 true + // false +} +``` + + +## `Print` + +- Description: `Print` prints the `tree` to standard output. + +- Format: + +```go +Print() +``` + +- Example: + +```go +func ExampleBTree_Print() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + tree.Print() + + // Output: + // key0 + // key1 + // key2 + // key3 + // key4 + // key5 +} +``` + + +## `Iterator` + +- Description: `Iterator` is equivalent to `IteratorAsc`. + +- Format: + +```go +Iterator(f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_Iterator() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + var totalKey, totalValue int + tree.Iterator(func(key, value interface{}) bool { + totalKey += key.(int) + totalValue += value.(int) + + return totalValue < 20 + }) + + fmt.Println("totalKey:", totalKey) + fmt.Println("totalValue:", totalValue) + + // Output: + // totalKey: 3 + // totalValue: 27 +} +``` + + +## `IteratorFrom` + +- Description: `IteratorFrom` is equivalent to `IteratorAscFrom`. + +- Format: + +```go +IteratorFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_IteratorFrom() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorFrom(1, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + + +## `IteratorAsc` + +- Description: `IteratorAsc` iterates over the `tree` in ascending order using a custom callback function `f` in read-only mode. If `f` returns `true`, it continues iterating; if it returns `false`, it stops. + +- Format: + +```go +IteratorAsc(f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_IteratorAsc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + tree.IteratorAsc(func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 0 , value: 10 + // key: 1 , value: 9 + // key: 2 , value: 8 + // key: 3 , value: 7 + // key: 4 , value: 6 + // key: 5 , value: 5 + // key: 6 , value: 4 + // key: 7 , value: 3 + // key: 8 , value: 2 + // key: 9 , value: 1 +} +``` + + +## `IteratorAscFrom` + +- Description: `IteratorAscFrom` iterates over the `tree` in ascending order using a custom callback function `f` in read-only mode. The parameter `key` specifies from which `key` to start iterating. When `match` is `true`, iteration starts from the complete match of `key`; otherwise, iteration uses index searching. If `f` returns `true`, it continues iterating; if it returns `false`, it stops. + +- Format: + +```go +IteratorAscFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_IteratorAscFrom_Normal() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(1, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + +```go +func ExampleBTree_IteratorAscFrom_NoExistKey() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(0, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: +} +``` + +```go +func ExampleBTree_IteratorAscFrom_NoExistKeyAndMatchFalse() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(0, false, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + + +## `IteratorDesc` + +- Description: `IteratorDesc` iterates over the `tree` in descending order using a custom callback function `f` in read-only mode. If `f` returns `true`, it continues iterating; if it returns `false`, it stops. + +- Format: + +```go +IteratorDesc(f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_IteratorDesc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + tree.IteratorDesc(func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 9 , value: 1 + // key: 8 , value: 2 + // key: 7 , value: 3 + // key: 6 , value: 4 + // key: 5 , value: 5 + // key: 4 , value: 6 + // key: 3 , value: 7 + // key: 2 , value: 8 + // key: 1 , value: 9 + // key: 0 , value: 10 +} +``` + + +## `IteratorDescFrom` + +- Description: `IteratorDescFrom` iterates over the `tree` in descending order using a custom callback function `f` in read-only mode. The parameter `key` specifies from which `key` to start iterating. When `match` is `true`, iteration starts from the complete match of `key`; otherwise, iteration uses index searching. If `f` returns `true`, it continues iterating; if it returns `false`, it stops. + +- Format: + +```go +IteratorDescFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- Example: + +```go +func ExampleBTree_IteratorDescFrom() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorDescFrom(5, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 5 , value: 50 + // key: 4 , value: 40 + // key: 3 , value: 30 + // key: 2 , value: 20 + // key: 1 , value: 10 +} +``` + + +## `MarshalJson` + +- Description: `MarshalJSON` implements the `json.Marshal` interface. + +- Format: + +```go +MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleBTree_MarshalJSON() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + bytes, err := json.Marshal(tree) + if err == nil { + fmt.Println(gconv.String(bytes)) + } + + // Output: + // {"key0":"val0","key1":"val1","key2":"val2","key3":"val3","key4":"val4","key5":"val5"} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" new file mode 100644 index 00000000000..df2329156e9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" @@ -0,0 +1,35 @@ +--- +slug: '/docs/components/container-gvar' +title: 'Generic' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gvar, Generic Type, Runtime Generics, Concurrent Safe, Data Type Conversion, g.Var, Development Efficiency] +description: "The gvar type in the GoFrame framework is a runtime generic implementation designed to enhance development convenience and efficiency. gvar supports built-in data type conversion and can serve as an alternative to interface{}, with its concurrency-safe feature making it excellent in scenarios requiring frequent data conversion. Additionally, it introduces the usage of gvar types and related interface documentation." +--- + + +## Introduction + +`gvar` is a **runtime generic** implementation that enhances development convenience and efficiency with minimal runtime overhead. It supports various built-in data type conversions and can be used as an alternative to the `interface{}` type. This type supports concurrency safety toggles. +:::tip +The framework also provides the `g.Var` data type, which is essentially an alias for the `gvar.Var` data type. +::: +**Usage Scenarios**: + +Scenarios where `interface{}` is used, various data formats that are not fixed, or scenarios that require frequent data type conversion. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/gvar" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar](https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..f59e0fb4231 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,116 @@ +--- +slug: '/docs/components/container-gvar-example' +title: 'Generic - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Generic Type,JSON Serialization,JSON Deserialization,Basic Type Conversion,slice Conversion,gvar.Var,Marshal,Unmarshal] +description: "When using the GoFrame framework, utilize the gvar.Var container for the basic usage of generic types, including basic type conversion and slice conversion. It also demonstrates how to perform JSON format data serialization and deserialization operations. With the interfaces provided by the GoFrame framework, users can easily manipulate complex data structures to achieve efficient data processing." +--- + +### Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "fmt" +) + +func main() { + var v g.Var + + v.Set("123") + + fmt.Println(v.Val()) + + // Basic type conversion + fmt.Println(v.Int()) + fmt.Println(v.Uint()) + fmt.Println(v.Float64()) + + // Slice conversion + fmt.Println(v.Ints()) + fmt.Println(v.Floats()) + fmt.Println(v.Strings()) +} +``` + +After execution, the output is: + +```html +123 +123 +123 +123 +[123] +[123] +[123] +``` + +### `JSON` Serialization/Deserialization + +The `gvar.Var` container implements the serialization/deserialization interface of the standard library `json` data format. + +1. `Marshal` + +```go + package main + + import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + ) + + func main() { + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + s := Student{ + Id: g.NewVar(1), + Name: g.NewVar("john"), + Scores: g.NewVar([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + } +``` + +After execution, the output is: + +```bash + {"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go + package main + + import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + ) + + func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + } +``` + +After execution, the output is: + +```bash + {1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..791871897ff --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1483 @@ +--- +slug: '/docs/components/container-gvar-funcs' +title: 'Generic - Methods' +sidebar_position: 1 +hide_title: true +keywords: [Method Introduction, Generic Type, GoFrame, Method Usage, gvar Package, Data Type Conversion, Example Code, Variable Operation, GoFrame Framework, GoFrame Application] +description: "Common methods in the GoFrame framework, including operations such as creating new variables, cloning variables, setting variables, and getting variable values. The usage of each method is explained through example code to help users better understand and apply these methods." +--- +:::tip +The following list of common methods may become outdated compared to new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar](https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar) +::: +### `New` + +- Description: `New` creates and returns a new `Var` with the given `value`. The optional parameter `safe` specifies whether to use `Var` in a concurrent-safe manner, with a default value of `false`. +- Format: + +```go +func New(value interface{}, safe ...bool) *Var +``` + +- Example: + +```go +// New +func ExampleVarNew() { + v := gvar.New(400) + g.Dump(v) + + // Output: + // "400" +} +``` + +### `Clone` + +- Description: `Clone` performs a shallow copy of the current `Var` and returns a pointer to this `Var`. +- Format: + +```go +func (v *Var) Clone() *Var +``` + +- Example + +```go +// Clone +func ExampleVar_Clone() { + tmp := "fisrt hello" + v := gvar.New(tmp) + g.DumpWithType(v.Clone()) + fmt.Println(v == v.Clone()) + + // Output: + // *gvar.Var(11) "fisrt hello" + // false +} +``` + +### `Set` + +- Description: `Set` sets the value of `v` to `value` and returns the old value of `v`. +- Format: + +```go +func (v *Var) Set(value interface{}) (old interface{}) +``` + +- Example: + +```go +// Set +func ExampleVar_Set() { + var v = gvar.New(100.00) + g.Dump(v.Set(200.00)) + g.Dump(v) + + // Output: + // 100 + // "200" +} +``` + +### `Val` + +- Description: `Val` returns the current value of `v`, with a type of `interface{}`. +- Format: + +```go +func (v *Var) Val() interface{} +``` + +- Example: + +```go +// Val +func ExampleVar_Val() { + var v = gvar.New(100.00) + g.DumpWithType(v.Val()) + + // Output: + // float64(100) +} +``` + +### `Interface` + +- Description: `Interface` is an alias for `Val`. +- Format: + +```go +func (v *Var) Interface() interface{} +``` + +- Example: + +```go +// Interface +func ExampleVar_Interface() { + var v = gvar.New(100.00) + g.DumpWithType(v.Interface()) + + // Output: + // float64(100) +} +``` + +### `Bytes` + +- Description: `Bytes` converts `v` to a byte array. +- Format: + +```go +func (v *Var) Bytes() []byte +``` + +- Example: + +```go +// Bytes +func ExampleVar_Bytes() { + var v = gvar.New("GoFrame") + g.DumpWithType(v.Bytes()) + + // Output: + // []byte(7) "GoFrame" +} +``` + +### `String` + +- Description: `String` converts `v` to a string. +- Format: + +```go +func (v *Var) String() string +``` + +- Example: + +```go +// String +func ExampleVar_String() { + var v = gvar.New("GoFrame") + g.DumpWithType(v.String()) + + // Output: + // string(7) "GoFrame" +} +``` + +### `Bool` + +- Description: `Bool` converts `v` to a boolean value. +- Format: + +```go +func (v *Var) Bool() bool +``` + +- Example: + +```go +// Bool +func ExampleVar_Bool() { + var v = gvar.New(true) + g.DumpWithType(v.Bool()) + + // Output: + // bool(true) +} +``` + +### `Int` + +- Description: `Int` converts `v` to an integer type. +- Format: + +```go +func (v *Var) Int() int +``` + +- Example: + +```go +// Int +func ExampleVar_Int() { + var v = gvar.New(-1000) + g.DumpWithType(v.Int()) + + // Output: + // int(-1000) +} +``` + +### `Uint` + +- Description: `Uint` converts `v` to an unsigned integer type. +- Format: + +```go +func (v *Var) Uint() uint +``` + +- Example: + +```go +// Uint +func ExampleVar_Uint() { + var v = gvar.New(1000) + g.DumpWithType(v.Uint()) + + // Output: + // uint(1000) +} +``` + +### `Float32` + +- Description: `Float32` converts `v` to a `32-bit` float type. +- Format: + +```go +func (v *Var) Float32() float32 +``` + +- Example: + +```go +// Float32 +func ExampleVar_Float32() { + var price = gvar.New(100.00) + g.DumpWithType(price.Float32()) + + // Output: + // float32(100) +} +``` + +### `Float64` + +- Description: `Float64` converts `v` to a `64-bit` float type. +- Format: + +```go +func (v *Var) Float64() float64 +``` + +- Example: + +```go +// Float32 +func ExampleVar_Float64() { + var price = gvar.New(100.00) + g.DumpWithType(price.Float64()) + + // Output: + // float64(100) +} +``` + +### `Time` + +- Description: `Time` converts `v` to `time.Time`. The `format` parameter is used to specify the time string format with `gtime`, e.g., `Y-m-d H:i:s`. +- Format: + +```go +func (v *Var) Time(format ...string) time.Time +``` + +- Example: + +```go +// Time +func ExampleVar_Time() { + var v = gvar.New("2021-11-11 00:00:00") + g.DumpWithType(v.Time()) + + // Output: + // time.Time(29) "2021-11-11 00:00:00 +0800 CST" +} +``` + +### `GTime` + +- Description: `G``Time` converts `v` to `*gtime.Time`. The `format` parameter is used to specify the time string format with `gtime`, e.g., `Y-m-d H:i:s`. +- Format: + +```go +func (v *Var) GTime(format ...string) *gtime.Time +``` + +- Example: + +```go +// GTime +func ExampleVar_GTime() { + var v = gvar.New("2021-11-11 00:00:00") + g.DumpWithType(v.GTime()) + + // Output: + // *gtime.Time(19) "2021-11-11 00:00:00" +} +``` + +### `Duration` + +- Description: `Duration` converts `v` to `time.Duration`. If the value of `v` is a string, it is converted using `time.ParseDuration`. +- Format: + +```go +func (v *Var) Duration() time.Duration +``` + +- Example: + +```go +// Duration +func ExampleVar_Duration() { + var v = gvar.New("300s") + g.DumpWithType(v.Duration()) + + // Output: + // time.Duration(4) "5m0s" +} +``` + +### `MarshalJSON` + +- Description: `MarshalJSON` implements the `MarshalJSON` method of the `json` interface. +- Format: + +```go +func (v *Var) MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +// MarshalJSON +func ExampleVar_MarshalJSON() { + testMap := g.Map{ + "code": "0001", + "name": "Golang", + "count": 10, + } + + var v = gvar.New(testMap) + res, err := json.Marshal(&v) + if err != nil { + panic(err) + } + g.DumpWithType(res) + + // Output: + // []byte(42) "{"code":"0001","count":10,"name":"Golang"}" +} +``` + +### `UnmarshalJSON` + +- Description: `UnmarshalJSON` implements the `UnmarshalJSON` method of the `json` interface. +- Format: + +```go +func (v *Var) UnmarshalJSON(b []byte) error +``` + +- Example: + +```go +// UnmarshalJSON +func ExampleVar_UnmarshalJSON() { + tmp := []byte(`{ + "Code": "0003", + "Name": "Golang Book3", + "Quantity": 3000, + "Price": 300, + "OnSale": true + }`) + var v = gvar.New(map[string]interface{}{}) + if err := json.Unmarshal(tmp, &v); err != nil { + panic(err) + } + + g.Dump(v) + + // Output: + // "{\"Code\":\"0003\",\"Name\":\"Golang Book3\",\"OnSale\":true,\"Price\":300,\"Quantity\":3000}" +} +``` + +### `UnmarshalValue` + +- Description: `UnmarshalValue` is an interface implementation that sets any type of value for `Var`. +- Format: + +```go +func (v *Var) UnmarshalValue(value interface{}) error +``` + +- Example: + +```go +// UnmarshalValue +func ExampleVar_UnmarshalValue() { + tmp := g.Map{ + "code": "00002", + "name": "GoFrame", + "price": 100, + "sale": true, + } + + var v = gvar.New(map[string]interface{}{}) + if err := v.UnmarshalValue(tmp); err != nil { + panic(err) + } + g.Dump(v) + + // Output: + // "{\"code\":\"00002\",\"name\":\"GoFrame\",\"price\":100,\"sale\":true}" +} +``` + +### `IsNil` + +- Description: `IsNil` checks if `v` is `nil`, returning `true` if it is `nil`, and `false` otherwise. +- Format: + +```go +func (v *Var) IsNil() bool +``` + +- Example: + +``` +/// IsNil +func ExampleVar_IsNil() { + g.Dump(gvar.New(0).IsNil()) + g.Dump(gvar.New(0.1).IsNil()) + // true + g.Dump(gvar.New(nil).IsNil()) + g.Dump(gvar.New("").IsNil()) + + // Output: + // false + // false + // true + // false +} +``` + +### `IsEmpty` + +- Description: `IsEmpty` checks if `v` is empty, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsEmpty() bool +``` + +- Example: + +```go +// IsEmpty +func ExampleVar_IsEmpty() { + g.Dump(gvar.New(0).IsEmpty()) + g.Dump(gvar.New(nil).IsEmpty()) + g.Dump(gvar.New("").IsEmpty()) + g.Dump(gvar.New(g.Map{"k": "v"}).IsEmpty()) + + // Output: + // true + // true + // true + // false +} +``` + +### `IsInt` + +- Description: `IsInt` checks if `v` is of `int` type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsInt() bool +``` + +- Example: + +```go +// IsInt +func ExampleVar_IsInt() { + g.Dump(gvar.New(0).IsInt()) + g.Dump(gvar.New(0.1).IsInt()) + g.Dump(gvar.New(nil).IsInt()) + g.Dump(gvar.New("").IsInt()) + + // Output: + // true + // false + // false + // false +} +``` + +### `IsUint` + +- Description: `IsUint` checks if `v` is of `uint` type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsUint() bool +``` + +- Example: + +```go +// IsUint +func ExampleVar_IsUint() { + g.Dump(gvar.New(0).IsUint()) + g.Dump(gvar.New(uint8(8)).IsUint()) + g.Dump(gvar.New(nil).IsUint()) + + // Output: + // false + // true + // false +} +``` + +### `IsFloat` + +- Description: `IsFloat` checks if `v` is of `float` type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsFloat() bool +``` + +- Example: + +```go +// IsFloat +func ExampleVar_IsFloat() { + g.Dump(g.NewVar(uint8(8)).IsFloat()) + g.Dump(g.NewVar(float64(8)).IsFloat()) + g.Dump(g.NewVar(0.1).IsFloat()) + + // Output: + // false + // true + // true +} +``` + +### `IsSlice` + +- Description: `IsSlice` checks if `v` is of slice type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsSlice() bool +``` + +- Example: + +```go +// IsSlice +func ExampleVar_IsSlice() { + g.Dump(g.NewVar(0).IsSlice()) + g.Dump(g.NewVar(g.Slice{0}).IsSlice()) + + // Output: + // false + // true +} +``` + +### `IsMap` + +- Description: `IsMap` checks if `v` is of map type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsMap() bool +``` + +- Example: + +```go +// IsMap +func ExampleVar_IsMap() { + g.Dump(g.NewVar(0).IsMap()) + g.Dump(g.NewVar(g.Map{"k": "v"}).IsMap()) + g.Dump(g.NewVar(g.Slice{}).IsMap()) + + // Output: + // false + // true + // false +} +``` + +### `IsStruct` + +- Description: `IsStruct` checks if `v` is of struct type, returning `true` if it is, and `false` otherwise. +- Format: + +```go +func (v *Var) IsStruct() bool +``` + +- Example: + +```go +// IsStruct +func ExampleVar_IsStruct() { + g.Dump(g.NewVar(0).IsStruct()) + g.Dump(g.NewVar(g.Map{"k": "v"}).IsStruct()) + + a := struct{}{} + g.Dump(g.NewVar(a).IsStruct()) + g.Dump(g.NewVar(&a).IsStruct()) + + // Output: + // false + // false + // true + // true +} +``` + +### `ListItemValues` + +- Description: `ListItemValues` retrieves and returns all item elements of the struct/map with the key `key`. Note that the `list` parameter should be a slice type containing elements of `map` or `struct`, otherwise it will return an empty slice. +- Format: + +```go +func (v *Var) ListItemValues(key interface{}) (values []interface{}) +``` + +- Example: + +```go +// ListItemValues +func ExampleVar_ListItemValues() { + var goods1 = g.List{ + g.Map{"id": 1, "price": 100.00}, + g.Map{"id": 2, "price": 0}, + g.Map{"id": 3, "price": nil}, + } + var v = gvar.New(goods1) + fmt.Println(v.ListItemValues("id")) + fmt.Println(v.ListItemValues("price")) + + // Output: + // [1 2 3] + // [100 0 ] +} +``` + +### `ListItemValuesUnique` + +- Description: `ListItemValuesUnique` retrieves and returns all unique elements of the struct/map with the specified `key`. Note that the `list` parameter should be a slice type containing elements of `map` or `struct`, otherwise it will return an empty slice. +- Format: + +```go +func (v *Var) ListItemValuesUnique(key string) []interface{} +``` + +- Example: + +```go +// ListItemValuesUnique +func ExampleVar_ListItemValuesUnique() { + var ( + goods1 = g.List{ + g.Map{"id": 1, "price": 100.00}, + g.Map{"id": 2, "price": 100.00}, + g.Map{"id": 3, "price": nil}, + } + v = gvar.New(goods1) + ) + + fmt.Println(v.ListItemValuesUnique("id")) + fmt.Println(v.ListItemValuesUnique("price")) + + // Output: + // [1 2 3] + // [100 ] +} +``` + +### `Struct` + +- Description: `Struct` maps the value of `v` to the `pointer`. The `pointer` parameter should point to an instance of a struct. The `mapping` parameter is used to specify key-to-field mapping rules. +- Format: + +```go +func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error +``` + +- Example: + +```go +func ExampleVar_Struct() { + params1 := g.Map{ + "uid": 1, + "Name": "john", + } + v := gvar.New(params1) + type tartget struct { + Uid int + Name string + } + t := new(tartget) + if err := v.Struct(&t); err != nil { + panic(err) + } + g.Dump(t) + + // Output: + // { + // Uid: 1, + // Name: "john", + // } +} +``` + +### `Structs` + +- Description: `Structs` converts `v` to a slice type of the given struct. The `pointer` parameter should point to an instance of a struct. The `mapping` parameter is used to specify key-to-field mapping rules. +- Format: + +```go +func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error +``` + +- Example: + +```go +func ExampleVar_Structs() { + paramsArray := []g.Map{} + params1 := g.Map{ + "uid": 1, + "Name": "golang", + } + params2 := g.Map{ + "uid": 2, + "Name": "java", + } + + paramsArray = append(paramsArray, params1, params2) + v := gvar.New(paramsArray) + type tartget struct { + Uid int + Name string + } + var t []tartget + if err := v.Structs(&t); err != nil { + panic(err) + } + g.DumpWithType(t) + + // Output: + // []gvar_test.tartget(2) [ + // gvar_test.tartget(2) { + // Uid: int(1), + // Name: string(6) "golang", + // }, + // gvar_test.tartget(2) { + // Uid: int(2), + // Name: string(4) "java", + // }, + // ] +} +``` + +### `Ints` + +- Description: `Ints` converts `v` to `[]int`. +- Format: + +```go +func (v *Var) Ints() []int +``` + +- Example: + +```go +// Ints +func ExampleVar_Ints() { + var ( + arr = []int{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Ints()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Int64s` + +- Description: `Int64s` converts `v` to `[]int64`. +- Format: + +```go +func (v *Var) Int64s() []int64 +``` + +- Example: + +```go +// Int64s +func ExampleVar_Int64s() { + var ( + arr = []int64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Int64s()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Uints` + +- Description: `Uints` converts `v` to `[]uint`. +- Format: + +```go +func (v *Var) Uints() []uint +``` + +- Example: + +```go +// Uints +func ExampleVar_Uints() { + var ( + arr = []uint{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + fmt.Println(obj.Uints()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Uint64s` + +- Description: `Uint64s` converts `v` to `[]uint64`. +- Format: + +```go +func (v *Var) Uint64s() []uint64 +``` + +- Example: + +```go +// Uint64s +func ExampleVar_Uint64s() { + var ( + arr = []uint64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Uint64s()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Floats` + +- Description: `Floats` is an alias for `Float64s`. +- Format: + +```go +func (v *Var) Floats() []float64 +``` + +- Example: + +```go +// Floats +func ExampleVar_Floats() { + var ( + arr = []float64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Floats()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Float64s` + +- Description: `Float64s` converts `v` to `[]float64`. +- Format: + +```go +func (v *Var) Float64s() []float64 +``` + +- Example: + +```go +// Float64s +func ExampleVar_Float64s() { + var ( + arr = []float64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Float64s()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Float32s` + +- Description: `Float32s` converts `v` to `[]float32`. +- Format: + +```go +func (v *Var) Float32s() []float32 +``` + +- Example: + +```go +// Float32s +func ExampleVar_Float32s() { + var ( + arr = []float32{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Float32s()) + + // Output: + // [1 2 3 4 5] +} +``` + +### `Strings` + +- Description: `Strings` converts `v` to `[]string`. +- Format: + +```go +func (v *Var) Strings() []string +``` + +- Example: + +```go +// Strings +func ExampleVar_Strings() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + fmt.Println(obj.Strings()) + + // Output: + // [GoFrame Golang] +} +``` + +### `Interfaces` + +- Description: `Interfaces` converts `v` to `[]interface{}`. +- Format: + +```go +func (v *Var) Interfaces() []interface{} +``` + +- Example: + +```go +// Interfaces +func ExampleVar_Interfaces() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Interfaces()) + + // Output: + // [GoFrame Golang] +} +``` + +### `Slice` + +- Description: `Slice` is an alias for `Interfaces`. +- Format: + +```go +func (v *Var) Slice() []interface{} +``` + +- Example: + +```go +// Slice +func ExampleVar_Slice() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Slice()) + + // Output: + // [GoFrame Golang] +} +``` + +### `Array` + +- Description: `Array` is an alias for `Interfaces`. +- Format: + +```go +func (v *Var) Array() []interface{} +``` + +- Example: + +```go +// Array +func ExampleVar_Array() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + fmt.Println(obj.Array()) + + // Output: + // [GoFrame Golang] +} +``` + +### `Vars` + +- Description: `Vars` converts `v` to `[]var`. +- Format: + +```go +func (v *Var) Vars() []*Var +``` + +- Example: + +```go +// Vars +func ExampleVar_Vars() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Vars()) + + // Output: + // [GoFrame Golang] +} +``` + +### `Map` + +- Description: `Map` converts `v` to `map[string]interface{}`. +- Format: + +```go +func (v *Var) Map(tags ...string) map[string]interface{} +``` + +- Example: + +```go +// Map +func ExampleVar_Map() { + var ( + m = g.Map{"id": 1, "price": 100.00} + v = gvar.New(m) + res = v.Map() + ) + + fmt.Println(res["id"], res["price"]) + + // Output: + // 1 100 +} +``` + +### `MapStrAny` + +- Description: `MapStrAny` is similar to the `Map` function but implements the `MapStrAny` interface. +- Format: + +```go +func (v *Var) MapStrAny() map[string]interface{} +``` + +- Example: + +```go +// MapStrAny +func ExampleVar_MapStrAny() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrAny() + ) + + fmt.Println(v2["price"], v2["id"]) + + // Output: + // 100 1 +} +``` + +### `MapStrStr` + +- Description: `MapStrStr` converts `v` to `map[string]string`. +- Format: + +```go +func (v *Var) MapStrStr(tags ...string) map[string]string +``` + +- Example: + +```go +// MapStrStr +func ExampleVar_MapStrStr() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrStr() + ) + + fmt.Println(v2["price"] + "$") + + // Output: + // 100$ +} +``` + +### `MapStrVar` + +- Description: `MapStrVar` converts `v` to `map[string]*Var`. +- Format: + +```go +func (v *Var) MapStrVar(tags ...string) map[string]*Var +``` + +- Example: + +```go +// MapStrVar +func ExampleVar_MapStrVar() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrVar() + ) + + fmt.Println(v2["price"].Float64() * 100) + + // Output: + // 10000 +} +``` + +### `MapDeep` + +- Description: `MapDeep` recursively converts `v` to `map[string]interface{}`. +- Format: + +```go +func (v *Var) MapDeep(tags ...string) map[string]interface{} +``` + +- Example: + +```go +// MapDeep +func ExampleVar_MapDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // map[id:1 price:100] +} +``` + +### `MapStrStrDeep` + +- Description: `MapStrStrDeep` recursively converts `v` to `map[string]string`. +- Format: + +```go +func (v *Var) MapStrStrDeep(tags ...string) map[string]string +``` + +- Example: + +```go +// MapStrStrDeep +func ExampleVar_MapStrStrDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapStrStrDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // {"id":1,"price":100} +} +``` + +### `MapStrVarDeep` + +- Description: `MapStrVarDeep` recursively converts `v` to `map[string]*Var`. +- Format: + +```go +func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var +``` + +- Example: + +```go +// MapStrVarDeep +func ExampleVar_MapStrVarDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapStrVarDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // {"id":1,"price":100} +} +``` + +### `Maps` + +- Description: `Maps` converts `v` to `map[string]interface{}`. +- Format: + +```go +func (v *Var) Maps(tags ...string) []map[string]interface{} +``` + +- Example: + +```go +// Maps +func ExampleVar_Maps() { + var m = gvar.New(g.ListIntInt{g.MapIntInt{0: 100, 1: 200}, g.MapIntInt{0: 300, 1: 400}}) + fmt.Printf("%#v", m.Maps()) + + // Output: + // []map[string]interface {}{map[string]interface {}{"0":100, "1":200}, map[string]interface {}{"0":300, "1":400}} +} +``` + +### `MapsDeep` + +- Description: `MapsDeep` recursively converts `v` to `[]map[string]interface{}`. +- Format: + +```go +func (v *Var) MapsDeep(tags ...string) []map[string]interface{} +``` + +- Example: + +```go +// MapsDeep +func ExampleVar_MapsDeep() { + var ( + p1 = g.MapStrAny{"product": g.Map{"id": 1, "price": 100}} + p2 = g.MapStrAny{"product": g.Map{"id": 2, "price": 200}} + v = gvar.New(g.ListStrAny{p1, p2}) + v2 = v.MapsDeep() + ) + + fmt.Printf("%#v", v2) + + // Output: + // []map[string]interface {}{map[string]interface {}{"product":map[string]interface {}{"id":1, "price":100}}, map[string]interface {}{"product":map[string]interface {}{"id":2, "price":200}}} +} +``` + +### `MapToMap` + +- Description: `MapToMap` converts `v` to the `map` type specified by `pointer`, with `mapping` as the specified mapping rules. +- Format: + +```go +func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- Example: + +```go +// MapToMap +func ExampleVar_MapToMap() { + var ( + m1 = gvar.New(g.MapIntInt{0: 100, 1: 200}) + m2 = g.MapStrStr{} + ) + + err := m1.MapToMap(&m2) + if err != nil { + panic(err) + } + + fmt.Printf("%#v", m2) + + // Output: + // map[string]string{"0":"100", "1":"200"} +} +``` + +### `MapToMaps` + +- Description: `MapToMaps` converts `v` to the `map` type specified by `pointer`, with `mapping` as the specified mapping rules. +- Format: + +```go +func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- Example: + +```go +// MapToMaps +func ExampleVar_MapToMaps() { + var ( + p1 = g.MapStrAny{"product": g.Map{"id": 1, "price": 100}} + p2 = g.MapStrAny{"product": g.Map{"id": 2, "price": 200}} + v = gvar.New(g.ListStrAny{p1, p2}) + v2 []g.MapStrStr + ) + + err := v.MapToMaps(&v2) + if err != nil { + panic(err) + } + fmt.Printf("%#v", v2) + + // Output: + // []map[string]string{map[string]string{"product":"{\"id\":1,\"price\":100}"}, map[string]string{"product":"{\"id\":2,\"price\":200}"}} +} +``` + +### `MapToMapsDeep` + +- Description: `MapToMapsDeep` recursively converts `v` to the `map` type specified by `pointer`, with `mapping` as the designated mapping rules. +- Format: + +```go +func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- Example: + +```go +// MapToMapDeep +func ExampleVar_MapToMapDeep() { + var ( + p1 = gvar.New(g.MapStrAny{"product": g.Map{"id": 1, "price": 100}}) + p2 = g.MapStrAny{} + ) + + err := p1.MapToMap(&p2) + if err != nil { + panic(err) + } + fmt.Printf("%#v", p2) + + // Output: + // map[string]interface {}{"product":map[string]interface {}{"id":1, "price":100}} +} +``` + +### `Scan` + +- Description: `Scan` automatically checks the type of `pointer` and converts `params` to `pointer`. Supported types for `pointer` are: `*map, *[]map, *[]*map, *struct, **struct, *[]struct, *[]*struct` +- Format: + +```go +func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error +``` + +- Example: + +```go +// Scan +func ExampleVar_Scan() { + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + var ( + s Student + m = g.Map{ + "Id": 1, + "Name": "john", + "Scores": []int{100, 99, 98}, + } + ) + if err := gconv.Scan(m, &s); err != nil { + panic(err) + } + + g.DumpWithType(s) + + // Output: + // gvar_test.Student(3) { + // Id: *gvar.Var(1) "1", + // Name: *gvar.Var(4) "john", + // Scores: *gvar.Var(11) "[100,99,98]", + // } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" new file mode 100644 index 00000000000..3cc86b58050 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" @@ -0,0 +1,41 @@ +--- +slug: '/docs/components/container-gvar-notice' +title: 'Generic - Precautions' +sidebar_position: 2 +hide_title: true +description: "Precautions for using generic types in the GoFrame framework. Although generics improve development convenience, they may affect long-term maintenance in complex business projects. It is recommended to use generics in foundational components and middleware projects while clearly defining the data types of business models to leverage the advantages of compiled languages." +keywords: [GoFrame, GoFrame framework, generic types, business models, long-term maintenance, foundational components, middleware projects, compiled languages, data types, type checking] +--- + +## Precautions + +Although the generic types provided by the framework significantly enhance development convenience, they should be used with caution for business models (not abused), as generic types can obscure the actual data types. This can be more detrimental than beneficial for long-term maintenance in business projects, especially complex ones. The data types of a business model should be as clear, meaningful, and immutable as possible to facilitate type checking and optimization during the compilation stage, and to benefit long-term maintenance. + +For example, here is a real business model case provided by an enthusiastic community member: + +```go +type MiDispatchData struct { + Status *g.Var `json:"status"` + BrandId *g.Var `json:"brand_id"` + AreaId *g.Var `json:"area_id"` + Year *g.Var `json:"year"` + Month *g.Var `json:"month"` + Day *g.Var `json:"day"` + Hour *g.Var `json:"hour"` + RequestTime *g.Var `json:"request_time"` + Source *g.Var `json:"source"` + BikeId *g.Var `json:"bike_id"` + BikeType *g.Var `json:"bike_type"` + Lon *g.Var `json:"lon"` + Lat *g.Var `json:"lat"` + SiteId *g.Var `json:"site_id"` + BikeMac *g.Var `json:"bike_mac"` +} +``` + +While this approach allows the program to run normally and covers business scenarios, it loses the compiler advantage of compiled languages (similar to PHP variables), making it difficult to determine the data type of fields during later project maintenance. + +## Recommendations + +- Generics are more frequently used in foundational components and middleware projects. +- If a field has multiple meanings or types in a business scenario, generics can be used instead of types like `interface{}`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" new file mode 100644 index 00000000000..856f1184aad --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/components/container-glist' +title: 'List' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Linked List, Doubly Linked List, Concurrent Safe, glist, Component, Go Language, Programming, Data Structure] +description: "The glist component in the GoFrame framework supports concurrent safe doubly linked lists. glist provides data structures and concurrent control for linked lists, suitable for scenarios requiring doubly linked lists, thereby improving the development efficiency and runtime performance of Go language programs." +--- + +## Introduction + +A doubly linked list with a concurrent safety switch. + +**Usage Scenarios**: + +Doubly linked list. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/glist" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/glist](https://pkg.go.dev/github.com/gogf/gf/v2/container/glist) + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..1e67b925c41 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,359 @@ +--- +slug: '/docs/components/container-glist-example' +title: 'List - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Linked List, glist, Concurrent Safety, Data Structure, JSON Serialization, Go Language, Container Operations, Programming Example] +description: "Use the glist container in the GoFrame framework to perform linked list operations, including basic usage, list traversal, element push and pop, insertion and movement, concatenation and removal operations, and JSON serialization and deserialization. The example code demonstrates different operations in non-concurrent and concurrent safe scenarios to help understand the application of linked lists in Go language." +--- + +### Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" +) + +func main() { + // Not concurrent-safe in default. + l := glist.New() + + // Push + l.PushBack(1) // insert value from the back + l.PushBack(2) // insert value from the back + e := l.PushFront(0) // insert value from the front + + // Insert + l.InsertBefore(e, -1) // insert value before 0 + l.InsertAfter(e, "a") // insert value after 0 + fmt.Println(l) + + // Pop After popping, remove from list + fmt.Println(l.PopFront()) // pop from the front, return the popped value + fmt.Println(l.PopBack()) // pop from the back + fmt.Println(l) + + // All + fmt.Println(l.FrontAll()) // return a copy in order + fmt.Println(l.BackAll()) // return a copy in reverse order + + // Output: + // [-1,0,"a",1,2] + // -1 + // 2 + // [0,"a",1] + // [0 "a" 1] + // [1 "a" 0] +} +``` + +### Linked List Traversal + +In this example, we will traverse a concurrent-safe linked list using read lock and write lock, implemented by `RLockFunc` and `LockFunc` respectively. After execution, the output is: + +```go +package main + +import ( + "container/list" + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/glist" +) + +func main() { + // concurrent-safe list. + l := glist.NewFrom(garray.NewArrayRange(1, 9, 1).Slice(), true) + fmt.Println(l) + // iterate reading from head. + l.RLockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() { + fmt.Print(e.Value) + } + } + }) + fmt.Println() + // iterate reading from tail. + l.RLockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Back(); i < length; i, e = i+1, e.Prev() { + fmt.Print(e.Value) + } + } + }) + + fmt.Println() + + // iterate reading from head using IteratorAsc. + l.IteratorAsc(func(e *glist.Element) bool { + fmt.Print(e.Value) + return true + }) + fmt.Println() + // iterate reading from tail using IteratorDesc. + l.IteratorDesc(func(e *glist.Element) bool { + fmt.Print(e.Value) + return true + }) + + fmt.Println() + + // iterate writing from head. + l.LockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() { + if e.Value == 6 { + e.Value = "M" + break + } + } + } + }) + fmt.Println(l) + + // Output: + // [1,2,3,4,5,6,7,8,9] + // 123456789 + // 987654321 + // 123456789 + // 987654321 + // [1,2,3,4,5,M,7,8,9] +``` + +### `Push*` Element Push + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5}) + + l.PushBack(6) + fmt.Println(l) + + l.PushFront(0) + fmt.Println(l) + + // Positive numbers push from the right + l.PushBacks(g.Slice{7, 8}) + fmt.Println(l) + + // Negative numbers push from the left + l.PushFronts(g.Slice{-1, -2}) + fmt.Println(l) + + l.PushFrontList(glist.NewFrom(g.Slice{"a", "b", "c"})) + l.PushBackList(glist.NewFrom(g.Slice{"d", "e", "f"})) + fmt.Println(l) + + // Output: + // [1,2,3,4,5,6] + // [0,1,2,3,4,5,6] + // [0,1,2,3,4,5,6,7,8] + // [-2,-1,0,1,2,3,4,5,6,7,8] + // ["a","b","c",-2,-1,0,1,2,3,4,5,6,7,8,"d","e","f"] + +} +``` + +### `Pop*` Element Pop + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + fmt.Println(l.PopBack()) + fmt.Println(l.PopBacks(2)) + fmt.Println(l.PopFront()) + fmt.Println(l.PopFronts(2)) + + fmt.Println(glist.NewFrom(g.Slice{"a", "b", "c", "d"}).PopFrontAll()) + fmt.Println(glist.NewFrom(g.Slice{"a", "b", "c", "d"}).PopBackAll()) + + // Output: + // 9 + // [8 7] + // 1 + // [2 3] + // [4,5,6] + // [a b c d] + // [d c b a] +} +``` + +### `Move*/Insert*` Element Movement and Insertion + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + l.MoveToBack(l.Front()) // move the first element (1) to the most right [2,3,4,5,6,7,8,9,1] + l.MoveToFront(l.Back().Prev()) // move the element before the last (9) to the most left [9,2,3,4,5,6,7,8,1] + fmt.Println(l) + + // Move 2 before the first element of the stack + l.MoveBefore(l.Front().Next(), l.Front()) + // Move 8 after the last element of the stack + l.MoveAfter(l.Back().Prev(), l.Back()) + fmt.Println(l) + + // Insert new element before the last element of the stack + l.InsertBefore(l.Back(), "a") + // Insert new element after the first element of the stack + l.InsertAfter(l.Front(), "b") + + // Output: + // [9,2,3,4,5,6,7,8,1] + // [2,9,3,4,5,6,7,1,8] + // [2,"b",9,3,4,5,6,7,1,"a",8] +} +``` + +### `Join` Element Concatenation + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var l glist.List + l.PushBacks(g.Slice{"a", "b", "c", "d"}) + + fmt.Println(l.Join(",")) + + // Output: + // a,b,c,d +} +``` + +### `Remove*` Element Removal + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + fmt.Println(l) + + fmt.Println(l.Remove(l.Front())) + fmt.Println(l) + + l.Removes([]*glist.Element{l.Front(), l.Front().Next()}) + fmt.Println(l) + + l.RemoveAll() + fmt.Println(l) + + // Output: + // [0,1,2,3,4,5,6,7,8,9] + // 0 + // [1,2,3,4,5,6,7,8,9] + // [3,4,5,6,7,8,9] + // [] +} +``` + +### `JSON` Serialization/Deserialization + +The `glist` container implements the standard library `json` data format's serialization/deserialization interface. + +- `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + type Student struct { + Id int + Name string + Scores *glist.List + } + s := Student{ + Id: 1, + Name: "john", + Scores: glist.NewFrom(g.Slice{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // Output: + // {"Id":1,"Name":"john","Scores":[100,99,98]} +} +``` + + +- `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/glist" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *glist.List + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // Output: + // {1 john [100,99,98]} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..6fc8f6d3af9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,22 @@ +--- +slug: '/docs/components/container-glist-benchmark' +title: 'List - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Linked List Performance Test, Go Language, Performance Benchmark, PushBack, PushFront, Len, PopFront, PopBack] +description: "Under the GoFrame framework, the performance test results of the linked list (container/glist). A series of benchmark tests, including PushBack, PushFront, Len, PopFront, and PopBack, were conducted to evaluate the efficiency and performance of linked list operations to help developers optimize code performance." +--- + +[https://github.com/gogf/gf/blob/master/container/glist/glist\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/glist/glist_z_bench_test.go) + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/glist +Benchmark_PushBack-4 5000000 268 ns/op 56 B/op 2 allocs/op +Benchmark_PushFront-4 10000000 435 ns/op 56 B/op 2 allocs/op +Benchmark_Len-4 30000000 44.5 ns/op 0 B/op 0 allocs/op +Benchmark_PopFront-4 20000000 71.1 ns/op 0 B/op 0 allocs/op +Benchmark_PopBack-4 30000000 70.1 ns/op 0 B/op 0 allocs/op +PASS +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" new file mode 100644 index 00000000000..097a90c94cb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" @@ -0,0 +1,32 @@ +--- +slug: '/docs/components/container-gqueue' +title: 'Queue' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame framework, gqueue, queue, concurrency safe, dynamic queue, fixed queue, goroutine, data communication, Go language] +description: "The dynamic size concurrency-safe queue gqueue in the GoFrame framework supports both fixed and dynamic size queue features, achieving efficiency comparable to the standard library channel. gqueue is particularly suitable for data communication between multiple goroutines and provides developers with simple yet powerful concurrency handling capabilities." +--- + +## Introduction + +A dynamic size concurrency-safe queue. At the same time, `gqueue` also supports fixed queue size, achieving efficiency comparable to the standard library's `channel` when using a fixed queue size. + +**Usage Scenarios**: + +This queue is concurrency safe and is often used in scenarios where multiple `goroutine` data communication and dynamic queue size are supported. + +**How to Use**: + +```go +import "github.com/gogf/gf/v2/container/gqueue" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gqueue](https://pkg.go.dev/github.com/gogf/gf/v2/container/gqueue) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..e8b537caf75 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,185 @@ +--- +slug: '/docs/components/container-gqueue-example' +title: 'Queue - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame framework,queue,gqueue,gtimer,Pop,Push,queue length,queue close,glist] +description: "Using the gqueue component in the GoFrame framework for basic queue operations, including enqueuing and dequeuing elements, obtaining queue length, and closing the queue. It demonstrates in detail managing queue elements through Push and Pop methods and shows the relationship between the queue and glist linked list to ensure efficient construction of concurrency-safe program logic under the GoFrame framework." +--- + +## Basic Usage + +### Using `Queue.Pop` + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + // Data producer, writes data to the queue every second + gtimer.SetInterval(time.Second, func() { + v := gtime.Now().String() + q.Push(v) + fmt.Println("Push:", v) + }) + + // Close the queue after 3 seconds + gtimer.SetTimeout(3*time.Second, func() { + q.Close() + }) + + // Consumer, continuously reads queue data and outputs to the terminal + for { + if v := q.Pop(); v != nil { + fmt.Println(" Pop:", v) + } else { + break + } + } + + // The program exits immediately when the queue is closed at the third second, so only 2 seconds of data will be printed in the result. After execution, the output will be: + // Output: + // Push: 2021-09-07 14:03:00 + // Pop: 2021-09-07 14:03:00 + // Push: 2021-09-07 14:03:01 + // Pop: 2021-09-07 14:03:01 +} +``` + +### Using `Queue.C` + +```go +package main + +import ( + "context" + "fmt" + "time" + + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/container/gqueue" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + queue := gqueue.New() + gtimer.AddTimes(gctx.GetInitCtx(), time.Second, 3, func(ctx context.Context) { + queue.Push(gtime.Now().String()) + }) + for { + select { + case queueItem := <-queue.C: + fmt.Println(queueItem) + + case <-time.After(3 * time.Second): + fmt.Println("timeout, exit loop") + return + } + } +} +``` + +## Enqueue/Dequeue Elements + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + for i := 0; i < 10; i++ { + q.Push(i) + } + + fmt.Println(q.Pop()) + fmt.Println(q.Pop()) + fmt.Println(q.Pop()) + + // Output: + // 0 + // 1 + // 2 +} +``` + +## Queue Length + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + q.Push(1) + q.Push(2) + + fmt.Println(q.Len()) + // size is an alias for the len method + fmt.Println(q.Size()) + + // May Output: + // 2 + // 2 +} +``` + +## Queue Close + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + for i := 0; i < 10; i++ { + q.Push(i) + } + + fmt.Println(q.Pop()) + q.Close() + fmt.Println(q.Pop()) + fmt.Println(q.Len()) + + // Output: + // 0 + // + // 0 +} +``` + +## `gqueue` and `glist` + +The underlying implementation of `gqueue` is based on the `glist` linked list, which provides dynamic size characteristics. Writing data when the queue is full or reading data when the queue is empty will result in blocking. + +`glist` is a concurrent-safe linked list and can behave like a normal list when the concurrent-safe feature is turned off, without encountering blocking during data storage and retrieval. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..0f4b84337d5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,25 @@ +--- +slug: '/docs/components/container-gqueue-benchmark' +title: 'Queue - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, gqueue, performance testing, queue type, channel, benchmark, dynamic queue, queue performance, benchmark] +description: "Performance testing of gqueue and the standard library channel in the GoFrame framework. Benchmark tests demonstrate gqueue's advantages in dynamic storage and elastic capacity, showing better efficiency and flexibility in creation compared to the fixed memory allocation and capacity limitation of the channel." +--- + +[https://github.com/gogf/gf/blob/master/container/gqueue/gqueue\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/gqueue/gqueue_z_bench_test.go) + +Benchmark testing of `gqueue` and the standard library `channel`, where each benchmark test has a `b.N` value of `20000000` to ensure consistent dynamic queue access and prevent `deadlock`: + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/gqueue +Benchmark_Gqueue_StaticPushAndPop-4 20000000 84.2 ns/op +Benchmark_Gqueue_DynamicPush-4 20000000 164 ns/op +Benchmark_Gqueue_DynamicPop-4 20000000 121 ns/op +Benchmark_Channel_PushAndPop-4 20000000 70.0 ns/op +PASS +``` + +It can be seen that the read and write performance of the standard library `channel` is very high, but due to the need to initialize memory during creation, the efficiency is very, very low when creating a `channel` (initialization involves memory allocation), and it is limited by the queue size, meaning that the written data cannot exceed the specified queue size. `gqueue` is more flexible to use compared to `channel`, offering higher creation efficiency (dynamically allocated memory) and not being restricted by queue size (though size can be limited). \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" new file mode 100644 index 00000000000..900afae9039 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/components/container-gset' +title: 'Set' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gset, Collection Type, Concurrent Safety, Element Collection, Go Language, Collection Operation, GoFrame Framework, Collection Interface] +description: "The gset collection type in the GoFrame framework is characterized by a set of non-repeating elements, supporting elements of any type. gset provides a concurrent safety option, making it an efficient tool for collection operations, suitable for application in the Go language. Detailed usage instructions and interface documentation links are provided for developers to consult." +--- + +## Introduction + +A collection is a set of non-repeating elements, where the elements can be of any type. + +At the same time, `gset` supports optional concurrent safety parameters for concurrent-safe scenarios. + +**Use Cases**: + +Collection operations. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/container/gset" +``` + +**Interface Documentation**: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..0148767975c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,350 @@ +--- +slug: '/docs/components/container-gset-example' +title: 'Set - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame framework,set,gset,intersection,difference,union,complement,concurrent safety,serialization] +description: "Using set types and their basic operation methods in the GoFrame framework, including creating, adding, deleting, and traversing sets, as well as exploring advanced operations such as intersection, difference, union, and complement. In addition, the article also provides detailed explanations on inclusion judgment, set item popping, subset judgment, conditional writing, and demonstrates JSON serialization and deserialization with code examples." +--- + +## Basic Usage + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gset" + "fmt" +) + +func main() { + // Create a concurrent safe set object + s := gset.New(true) + + // Add an item + s.Add(1) + + // Add items in batch + s.Add([]interface{}{1, 2, 3}...) + + // Size of the set + fmt.Println(s.Size()) + + // Check if an item exists in the set + fmt.Println(s.Contains(2)) + + // Return items as a slice + fmt.Println(s.Slice()) + + // Remove an item + s.Remove(3) + + // Iterate through items + s.Iterator(func(v interface{}) bool { + fmt.Println("Iterator:", v) + return true + }) + + // Convert the set to a string + fmt.Println(s.String()) + + // Concurrent safe write lock operation + s.LockFunc(func(m map[interface{}]struct{}) { + m[4] = struct{}{} + }) + + // Concurrent safe read lock operation + s.RLockFunc(func(m map[interface{}]struct{}) { + fmt.Println(m) + }) + + // Clear the set + s.Clear() + fmt.Println(s.Size()) +} +``` + +After execution, the output is: + +```3 +true +[1 2 3] +Iterator: 1 +Iterator: 2 +[1 2] +map[1:{} 2:{} 4:{}] +0 +``` + +## Intersection, Difference, Union, Complement + +We can achieve intersection, difference, union, and complement using the following methods, and return a new result set, + +```go +func (set *Set) Intersect(others ...*Set) (newSet *Set) +func (set *Set) Diff(others ...*Set) (newSet *Set) +func (set *Set) Union(others ...*Set) (newSet *Set) +func (set *Set) Complement(full *Set) (newSet *Set) +``` + +1. `Intersect`: Intersection, a set of elements that belong to both set and others. +2. `Diff`: Difference, a set of elements that belong to set and not others. +3. `Union`: Union, a set of elements that belong to either set or others. +4. `Complement`: Complement, (precondition: set should be a subset of full) a set of elements that belong to the universal set full but not to set. If the given full set is not the universal set of set, it returns the difference between full and set. + +Through set methods, we can see that intersection, difference, and union methods support multiple set parameters for computation. The following is a simplified example, using only one parameter set. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + s1 := gset.NewFrom(g.Slice{1, 2, 3}) + s2 := gset.NewFrom(g.Slice{4, 5, 6}) + s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7}) + + // Intersection + fmt.Println(s3.Intersect(s1).Slice()) + // Difference + fmt.Println(s3.Diff(s1).Slice()) + // Union + fmt.Println(s1.Union(s2).Slice()) + // Complement + fmt.Println(s1.Complement(s3).Slice()) +} +``` + +After execution, the output is: + +``` +[1 2 3] +[4 5 6 7] +[1 2 3 4 5 6] +[7 4 5 6] +``` + +## `Contains/ContainsI` Inclusion Judgment + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.Contains("a")) + fmt.Println(set.Contains("A")) + fmt.Println(set.ContainsI("A")) + + // Output: + // true + // false + // true +} +``` + +## `Pop/Pops` Set Item Popping + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + set.Add(1, 2, 3, 4) + fmt.Println(set.Pop()) + fmt.Println(set.Pops(2)) + fmt.Println(set.Size()) + + // May Output: + // 1 + // [2 3] + // 1 +} +``` + +## `Join` Set Item Concatenation + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + set.Add("a", "b", "c", "d") + fmt.Println(set.Join(",")) + + // May Output: + // a,b,c,d +} +``` + +## `IsSubsetOf` Subset Judgment + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var s1, s2 gset.Set + s1.Add(g.Slice{1, 2, 3}...) + s2.Add(g.Slice{2, 3}...) + fmt.Println(s1.IsSubsetOf(&s2)) + fmt.Println(s2.IsSubsetOf(&s1)) + + // Output: + // false + // true +} +``` + +## `AddIfNotExist*` Conditional Writing + +Conditional writing means writing if the specified item does not exist, and returning `true`. Otherwise, skipping writing and returning `false`. Related methods include: + +- `AddIfNotExist` +- `AddIfNotExistFunc` +- `AddIfNotExistFuncLock` + +For detailed descriptions of the methods, please refer to the interface documentation or source code comments. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + fmt.Println(set.AddIfNotExist(1)) + fmt.Println(set.AddIfNotExist(1)) + fmt.Println(set.Slice()) + + // Output: + // true + // false + // [1] +} +``` + +## `Walk` Traverse and Modify + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var ( + set gset.StrSet + names = g.SliceStr{"user", "user_detail"} + prefix = "gf_" + ) + set.Add(names...) + // Add prefix for given table names. + set.Walk(func(item string) string { + return prefix + item + }) + fmt.Println(set.Slice()) + + // May Output: + // [gf_user gf_user_detail] +} +``` + +## `JSON` Serialization/Deserialization + +All container types under the `gset` module implement interfaces for standard library `json` data format serialization/deserialization. + +1. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + type Student struct { + Id int + Name string + Scores *gset.IntSet + } + s := Student{ + Id: 1, + Name: "john", + Scores: gset.NewIntSetFrom([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +After execution, the terminal output is: + +``` +{"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *gset.IntSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +After execution, the output is: + +``` +{1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..4ee16edb3b6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/components/container-gset-benchmark' +title: 'Set - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Performance Test, Collection Type, Container, Benchmark, Test, GoFrame Framework, GSet, GoFrame Framework, Optimization] +description: "Performance test results of collection types in the GoFrame framework, including benchmark tests for set operations with various data types such as integers, any data type, and strings. These benchmarks illustrate the performance metrics of different set operations, helping developers to optimize code performance and improve efficiency when building high-performance applications using the GoFrame framework." +--- + +[https://github.com/gogf/gf/blob/master/container/gset/gset\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/gset/gset_z_bench_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_IntSet_Add-4 10000000 277 ns/op 8 B/op 0 allocs/op +Benchmark_IntSet_Contains-4 20000000 60.6 ns/op 0 B/op 0 allocs/op +Benchmark_IntSet_Remove-4 10000000 211 ns/op 0 B/op 0 allocs/op +Benchmark_AnySet_Add-4 5000000 312 ns/op 21 B/op 1 allocs/op +Benchmark_AnySet_Contains-4 30000000 68.2 ns/op 0 B/op 0 allocs/op +Benchmark_AnySet_Remove-4 5000000 267 ns/op 0 B/op 0 allocs/op +Benchmark_StrSet_Add-4 5000000 383 ns/op 20 B/op 1 allocs/op +Benchmark_StrSet_Contains-4 10000000 160 ns/op 7 B/op 0 allocs/op +Benchmark_StrSet_Remove-4 5000000 306 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Add-4 10000000 258 ns/op 35 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Contains-4 20000000 146 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Remove-4 10000000 173 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnySet_Add-4 5000000 355 ns/op 41 B/op 1 allocs/op +Benchmark_Unsafe_AnySet_Contains-4 10000000 150 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnySet_Remove-4 200000000 11.9 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_StrSet_Add-4 5000000 486 ns/op 59 B/op 1 allocs/op +Benchmark_Unsafe_StrSet_Contains-4 5000000 298 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrSet_Remove-4 10000000 158 ns/op 7 B/op 0 allocs/op +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..a313c5937d3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,803 @@ +--- +slug: '/docs/components/container-gset-funcs' +title: 'Set - Methods' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, Set Type, StrSet, Method Introduction, Set Operations, Concurrent Safety, String Set, Code Examples, Element Operations, Set Functions] +description: "Implement basic set operations using the GoFrame library, including creating sets, adding elements, set operations, element checking and removal, set iteration, among others. It also provides concrete code examples to help understand and apply these methods." +--- +:::tip +The following is a list of common methods. The documentation may lag behind new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) +::: +:::tip +The usage of methods is introduced using the `StrSet` type; methods for other set types are similar and will not be repeated. +::: +## `NewStrSet` + +- Description: `NewStrSet` creates and returns an empty set that contains unique string data. The `safe` parameter specifies whether to use in concurrent safety, with a default value of `false`. +- Signature: + +```go +func NewStrSet(safe ...bool) *StrSet +``` + +- Example: + +```go +func ExampleNewStrSet() { + strSet := gset.NewStrSet(true) + strSet.Add([]string{"str1", "str2", "str3"}...) + fmt.Println(strSet.Slice()) + + // May Output: + // [str3 str1 str2] +} +``` + + +## `NewStrSetFrom` + +- Description: `NewStrSetFrom` creates a set from a given array. The `safe` parameter specifies whether to use in concurrent safety, with a default value of `false`. +- Signature: + +```go +func NewStrSetFrom(items []string, safe ...bool) *StrSet +``` + +- Example: + +```go +func ExampleNewStrSetFrom() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(strSet.Slice()) + + // May Output: + // [str1 str2 str3] +} +``` + + +## `Add` + +- Description: `Add` adds one or more elements to the set. +- Signature: + +```go +func (set *StrSet) Add(item ...string) +``` + +- Example: + +```go +func ExampleStrSet_Add() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExist("str")) + + // May Output: + // [str str1 str2 str3] + // false +} +``` + + +## `AddIfNotExist` + +- Description: `AddIfNotExist` checks if the specified element `item` exists in the set. If it does not exist, it adds `item` to the set and returns `true`; otherwise, it does nothing and returns `false`. +- Signature: + +```go +func (set *StrSet) AddIfNotExist(item string) bool +``` + +- Example: + +```go +func ExampleStrSet_AddIfNotExist() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExist("str")) + + // May Output: + // [str str1 str2 str3] + // false +} +``` + + +## `AddIfNotExistFunc` + +- Description: `AddIfNotExistFunc` checks if the specified element `item` exists in the set. If it does not exist and the function `f` returns `true`, it sets `item` into the set and returns `true`; otherwise, it does nothing and returns `false`. +- Signature: + +```go +func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool +``` + +- Example: + +```go +func ExampleStrSet_AddIfNotExistFunc() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExistFunc("str5", func() bool { + return true + })) + + // May Output: + // [str1 str2 str3 str] + // true +} +``` + + +## `AddIfNotExistFuncLock` + +- Description: `AddIfNotExistFuncLock` is similar to `AddIfNotExistFunc`, but when multiple goroutines simultaneously call `AddIfNotExistFuncLock`, it uses a concurrent safety lock mechanism to ensure that only one goroutine executes at a time. This method is effective only if the `safe` parameter is set to `true` when creating the set; otherwise, it behaves like the `AddIfNotExistFunc` method. +- Signature: + +```go +func (set *StrSet) AddIfNotExistFuncLock(item string, f func() bool) bool +``` + +- Example: + +```go +func ExampleStrSet_AddIfNotExistFuncLock() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExistFuncLock("str4", func() bool { + return true + })) + + // May Output: + // [str1 str2 str3 str] + // true +} +``` + + +## `Clear` + +- Description: `Clear` removes all elements from the set. +- Signature: + +```go +func (set *StrSet) Clear() +``` + +- Example: + +```go +func ExampleStrSet_Clear() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(strSet.Size()) + strSet.Clear() + fmt.Println(strSet.Size()) + + // Output: + // 3 + // 0 +} +``` + + +## `Intersect` + +- Description: `Intersect` performs an intersection operation between the set `set` and `others`, returning a new set `newSet` where the elements exist in both `set` and `others`. +- Signature: + +```go +func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) +``` + +- Example: + +```go +func ExampleStrSet_Intersect() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c"}...) + var s2 gset.StrSet + s2.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s2.Intersect(s1).Slice()) + + // May Output: + // [c a b] +} +``` + + +## `Diff` + +- Description: `Diff` performs a difference operation between the set `set` and `others`, returning a new set `newSet`. The elements in `newSet` exist in `set` but not in `others`. Note that `others` can specify multiple set parameters. +- Signature: + +```go +func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) +``` + +- Example: + +```go +func ExampleStrSet_Diff() { + s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s2 := gset.NewStrSetFrom([]string{"a", "b", "c", "d"}, true) + fmt.Println(s2.Diff(s1).Slice()) + + // Output: + // [d] +} +``` + + +## `Union` + +- Description: `Union` performs a union operation between the set `set` and `others`, returning a new set `newSet`. +- Signature: + +```go +func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) +``` + +- Example: + +```go +func ExampleStrSet_Union() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s2 := gset.NewStrSet(true) + s2.Add([]string{"a", "b", "d"}...) + fmt.Println(s1.Union(s2).Slice()) + + // May Output: + // [a b c d] +} +``` + + +## `Complement` + +- Description: `Complement` performs a complement operation between `set` and `full`, returning a new set `newSet`. +- Signature: + +```go +func (set *StrSet) Complement(full *StrSet) (newSet *StrSet) +``` + +- Example: + +```go +func ExampleStrSet_Complement() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3", "str4", "str5"}, true) + s := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(s.Complement(strSet).Slice()) + + // May Output: + // [str4 str5] +} +``` + + +## `Contains` + +- Description: `Contains` checks if the set contains `item`. +- Signature: + +```go +func (set *StrSet) Contains(item string) bool +``` + +- Example: + +```go +func ExampleStrSet_Contains() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.Contains("a")) + fmt.Println(set.Contains("A")) + + // Output: + // true + // false +} +``` + + +## `ContainsI` + +- Description: `ContainsI` is similar to `Contains`, but it performs a case-insensitive comparison. +- Signature: + +```go +func (set *StrSet) ContainsI(item string) bool +``` + +- Example: + +```go +func ExampleStrSet_ContainsI() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.ContainsI("a")) + fmt.Println(set.ContainsI("A")) + + // Output: + // true + // true +} +``` + + +## `Equal` + +- Description: `Equal` checks whether two sets are completely equal (including size and elements). +- Signature: + +```go +func (set *StrSet) Equal(other *StrSet) bool +``` + +- Example: + +```go +func ExampleStrSet_Equal() { + s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s2 := gset.NewStrSetFrom([]string{"a", "b", "c", "d"}, true) + fmt.Println(s2.Equal(s1)) + + s3 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s4 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + fmt.Println(s3.Equal(s4)) + + // Output: + // false + // true +} +``` + + +## `IsSubSetOf` + +- Description: `IsSubsetOf` checks whether the current set `set` is a subset of the specified set `other`. +- Signature: + +```go +func (set *StrSet) IsSubsetOf(other *StrSet) bool +``` + +- Example: + +```go +func ExampleStrSet_IsSubsetOf() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + var s2 gset.StrSet + s2.Add([]string{"a", "b", "d"}...) + fmt.Println(s2.IsSubsetOf(s1)) + + // Output: + // true +} +``` + + +## `Iterator` + +- Description: `Iterator` iterates over the current set `set` using the given callback function `f`. If the function `f` returns `true`, it continues iteration; otherwise, it stops. +- Signature: + +```go +func (set *StrSet) Iterator(f func(v string) bool) +``` + +- Example: + +```go +func ExampleStrSet_Iterator() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.Iterator(func(v string) bool { + fmt.Println("Iterator", v) + return true + }) + + // May Output: + // Iterator a + // Iterator b + // Iterator c + // Iterator d +} +``` + + +## `Join` + +- Description: `Join` concatenates the elements of the set into a new string using `glue`. +- Signature: + +```go +func (set *StrSet) Join(glue string) string +``` + +- Example: + +```go +func ExampleStrSet_Join() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Join(",")) + + // May Output: + // b,c,d,a +} +``` + + +## `LockFunc` + +- Description: `LockFunc` is useful only in concurrent safety scenarios. It locks the set `set` with a write lock and executes the callback function `f`. +- Signature: + +```go +func (set *StrSet) LockFunc(f func(m map[string]struct{})) +``` + +- Example: + +```go +func ExampleStrSet_LockFunc() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"1", "2"}...) + s1.LockFunc(func(m map[string]struct{}) { + m["3"] = struct{}{} + }) + fmt.Println(s1.Slice()) + + // May Output + // [2 3 1] + +} +``` + + +## `RLockFunc` + +- Description: `RLockFunc` is useful only in concurrent safety scenarios. It locks the set `set` with a read lock and executes the callback function `f`. +- Signature: + +```go +func (set *StrSet) RLockFunc(f func(m map[string]struct{})) +``` + +- Example: + +```go +func ExampleStrSet_RLockFunc() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.RLockFunc(func(m map[string]struct{}) { + fmt.Println(m) + }) + + // Output: + // map[a:{} b:{} c:{} d:{}] +} +``` + + +## `Merge` + +- Description: `Merge` merges all elements from the `others` sets into `set`. +- Signature: + +```go +func (set *StrSet) Merge(others ...*StrSet) *StrSet +``` + +- Example: + +```go +func ExampleStrSet_Merge() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + + s2 := gset.NewStrSet(true) + fmt.Println(s1.Merge(s2).Slice()) + + // May Output: + // [d a b c] +} +``` + + +## `Pop` + +- Description: `Pop` randomly retrieves an element from the set. +- Signature: + +```go +func (set *StrSet) Pop() string +``` + +- Example: + +```go +func ExampleStrSet_Pop() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + + fmt.Println(s1.Pop()) + + // May Output: + // a +} +``` + + +## `Pops` + +- Description: `Pops` randomly pops `size` number of elements from the set. If `size == -1`, it returns all elements. +- Signature: + +```go +func (set *StrSet) Pops(size int) []string +``` + +- Example: + +```go +func ExampleStrSet_Pops() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + for _, v := range s1.Pops(2) { + fmt.Println(v) + } + + // May Output: + // a + // b +} +``` + + +## `Remove` + +- Description: `Remove` removes the specified element `item` from the set. +- Signature: + +```go +func (set *StrSet) Remove(item string) +``` + +- Example: + +```go +func ExampleStrSet_Remove() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.Remove("a") + fmt.Println(s1.Slice()) + + // May Output: + // [b c d] +} +``` + + +## `Size` + +- Description: `Size` returns the size of the set. +- Signature: + +```go +func (set *StrSet) Size() int +``` + +- Example: + +```go +func ExampleStrSet_Size() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Size()) + + // Output: + // 4 +} +``` + + +## `Silce` + +- Description: `Slice` returns the elements of the set as a slice. +- Signature: + +```go +func (set *StrSet) Slice() []string +``` + +- Example: + +```go +func ExampleStrSet_Slice() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Slice()) + + // May Output: + // [a,b,c,d] +} +``` + + +## `String` + +- Description: `String` returns the set as a string. +- Signature: + +```go +func (set *StrSet) String() string +``` + +- Example: + +```go +func ExampleStrSet_String() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.String()) + + // May Output: + // "a","b","c","d" +} + + +``` + + +## `Sum` + +- Description: `Sum` sums up the elements of the set. Note: It is valid only when the elements are numbers; otherwise, you will get an unexpected result. +- Signature: + +```go +func (set *StrSet) Sum() (sum int) +``` + +- Example: + +```go +func ExampleStrSet_Sum() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"1", "2", "3", "4"}...) + fmt.Println(s1.Sum()) + + // Output: + // 10 +} +``` + + +## `Walk` + +- Description: `Walk` traverses the current set with the user-provided callback function `f`, and resets the current set with the return results of `f`. Note, in a concurrent safety scenario, this method uses a write lock internally to ensure safety. +- Signature: + +```go +func (set *StrSet) Walk(f func(item string) string) *StrSet +``` + +- Example: + +```go +func ExampleStrSet_Walk() { + var ( + set gset.StrSet + names = g.SliceStr{"user", "user_detail"} + prefix = "gf_" + ) + set.Add(names...) + // Add prefix for given table names. + set.Walk(func(item string) string { + return prefix + item + }) + fmt.Println(set.Slice()) + + // May Output: + // [gf_user gf_user_detail] +} +``` + + +## `MarshalJSON` + +- Description: `MarshalJSON` implements the `MarshalJSON` interface of `json.Marshal`. +- Signature: + +```go +func (set *StrSet) MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleStrSet_MarshalJSON() { + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{ + Id: 1, + Name: "john", + Scores: gset.NewStrSetFrom([]string{"100", "99", "98"}, true), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // May Output: + // {"Id":1,"Name":"john","Scores":["100","99","98"]} +} +``` + + +## `UnmarshalJSON` + +- Description: `UnmarshalJSON` implements the `UnmarshalJSON` interface of `json.Unmarshal`. +- Signature: + +```go +func (set *StrSet) UnmarshalJSON(b []byte) error +``` + +- Example: + +```go +func ExampleStrSet_UnmarshalJSON() { + b := []byte(`{"Id":1,"Name":"john","Scores":["100","99","98"]}`) + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // May Output: + // {1 john "99","98","100"} +} +``` + + +## `UnmarshalValue` + +- Description: `UnmarshalValue` implements the internal unified setting interface of the `goframe` framework. It initializes the current object with an `interface{}` type parameter, the usage logic of which is determined by the implementation method of this interface. +- Signature: + +```go +func (set *StrSet) UnmarshalValue(value interface{}) (err error) +``` + +- Example: + +```go +func ExampleStrSet_UnmarshalValue() { + b := []byte(`{"Id":1,"Name":"john","Scores":["100","99","98"]}`) + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // May Output: + // {1 john "99","98","100"} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" new file mode 100644 index 00000000000..b50498bc271 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" @@ -0,0 +1,2743 @@ +--- +slug: '/docs/components/text-gstr' +title: 'String Processing' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,String processing,Text processing component,String judgment,Case conversion,String comparison,Split and join,Naming conversion,GoFrame framework,String conversion] +description: "gstr provides powerful and convenient text processing components, including string judgment, case conversion, string comparison, splitting and joining, naming conversion, and more, offering more comprehensive and rich features than the Golang standard library." +--- + +`gstr` provides powerful and convenient text processing components, with a large number of commonly used string processing methods built in, making it more comprehensive and rich compared to the `Golang` standard library, suitable for dealing with most business scenarios. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/text/gstr" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +:::tip +The list of common methods below might be outdated compared to code updates with new features. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +::: +## String Judgment + +### `IsNumeric` + +- Description: `IsNumeric` verifies whether the string `s` is a number. + +- Format: + +```go +IsNumeric(s string) bool +``` + +- Example: + +```go +func ExampleIsNumeric() { + fmt.Println(gstr.IsNumeric("88")) + fmt.Println(gstr.IsNumeric("3.1415926")) + fmt.Println(gstr.IsNumeric("abc")) + // Output: + // true + // true + // false +} +``` + + +## String Length + +### `LenRune` + +- Description: `LenRune` returns the length of a `unicode` string. + +- Format: + +```go +LenRune(str string) int +``` + +- Example: + +```go +func ExampleLenRune() { + var ( + str = `GoFrame框架` + result = gstr.LenRune(str) + ) + fmt.Println(result) + + // Output: + // 9 +} +``` + + +## String Creation + +### `Repeat` + +- Description: `Repeat` returns a new string consisting of the `input` string repeated `multiplier` times. + +- Format: + +```go +Repeat(input string, multiplier int) string +``` + +- Example: + +```go +func ExampleRepeat() { + var ( + input = `goframe ` + multiplier = 3 + result = gstr.Repeat(input, multiplier) + ) + fmt.Println(result) + + // Output: + // goframe goframe goframe +} +``` + + +## Case Conversion + +### `ToLower` + +- Description: `ToLower` converts all `Unicode` characters in `s` to lowercase and returns its copy. + +- Format: + +```go +ToLower(s string) string +``` + +- Example: + +```go +func ExampleToLower() { + var ( + s = `GOFRAME` + result = gstr.ToLower(s) + ) + fmt.Println(result) + + // Output: + // goframe +} +``` + + +### `ToUpper` + +- Description: `ToUpper` converts all `Unicode` characters in `s` to uppercase and returns its copy. + +- Format: + +```go +ToUpper(s string) string +``` + +- Example: + +```go +func ExampleToUpper() { + var ( + s = `goframe` + result = gstr.ToUpper(s) + ) + fmt.Println(result) + + // Output: + // GOFRAME +} +``` + + +### `UcFirst` + +- Description: `UcFirst` capitalizes the first character of `s` and returns its copy. + +- Format: + +```go +UcFirst(s string) string +``` + +- Example: + +```go +func ExampleUcFirst() { + var ( + s = `hello` + result = gstr.UcFirst(s) + ) + fmt.Println(result) + + // Output: + // Hello +} +``` + + +### `LcFirst` + +- Description: `LcFirst` converts the first character of `s` to lowercase and returns its copy. + +- Format: + +```go +LcFirst(s string) string +``` + +- Example: + +```go +func ExampleLcFirst() { + var ( + str = `Goframe` + result = gstr.LcFirst(str) + ) + fmt.Println(result) + + // Output: + // goframe +} +``` + + +### `UcWords` + +- Description: `UcWords` capitalizes the first character of each word in the string `str`. + +- Format: + +```go +UcWords(str string) string +``` + +- Example: + +```go +func ExampleUcWords() { + var ( + str = `hello world` + result = gstr.UcWords(str) + ) + fmt.Println(result) + + // Output: + // Hello World +} +``` + + +### `IsLetterLower` + +- Description: `IsLetterLower` verifies whether the given character `b` is lowercase. + +- Format: + +```go +IsLetterLower(b byte) bool +``` + +- Example: + +```go +func ExampleIsLetterLower() { + fmt.Println(gstr.IsLetterLower('a')) + fmt.Println(gstr.IsLetterLower('A')) + + // Output: + // true + // false +} +``` + + +### `IsLetterUpper` + +- Description: `IsLetterUpper` verifies whether the character `b` is uppercase. + +- Format: + +```go +IsLetterUpper(b byte) bool +``` + +- Example: + +```go +func ExampleIsLetterUpper() { + fmt.Println(gstr.IsLetterUpper('A')) + fmt.Println(gstr.IsLetterUpper('a')) + + // Output: + // true + // false +} +``` + + +## String Comparison + +### `Compare` + +- Description: `Compare` returns an integer comparing two strings lexicographically. If `a == b`, it returns `0`; if `a < b`, it returns `-1`; if `a > b`, it returns `+1`. + +- Format: + +```go +Compare(a, b string) int +``` + +- Example: + +```go +func ExampleCompare() { + fmt.Println(gstr.Compare("c", "c")) + fmt.Println(gstr.Compare("a", "b")) + fmt.Println(gstr.Compare("c", "b")) + + // Output: + // 0 + // -1 + // 1 +} +``` + + +### `Equal` + +- Description: `Equal` returns whether `a` and `b` are equal, case-insensitively. + +- Format: + +```go +Equal(a, b string) bool +``` + +- Example: + +```go +func ExampleEqual() { + fmt.Println(gstr.Equal(`A`, `a`)) + fmt.Println(gstr.Equal(`A`, `A`)) + fmt.Println(gstr.Equal(`A`, `B`)) + + // Output: + // true + // true + // false +} +``` + + +## Split and Join + +### `Split` + +- Description: `Split` splits `str` into `[]string` using `delimiter`. + +- Format: + +```go +Split(str, delimiter string) []string +``` + +- Example: + +```go +func ExampleSplit() { + var ( + str = `a|b|c|d` + delimiter = `|` + result = gstr.Split(str, delimiter) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"a", "b", "c", "d"} +} +``` + + +### `SplitAndTrim` + +- Description: `SplitAndTrim` splits `str` into `[]string` using `delimiter` and calls `Trim` on each element of `[]string`, ignoring elements that are empty after `Trim`. + +- Format: + +```go +SplitAndTrim(str, delimiter string, characterMask ...string) []string +``` + +- Example: + +```go +func ExampleSplitAndTrim() { + var ( + str = `a|b|||||c|d` + delimiter = `|` + result = gstr.SplitAndTrim(str, delimiter) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"a", "b", "c", "d"} +} +``` + + +### `Join` + +- Description: `Join` concatenates each element of `array` into a new string. The parameter `sep` is used as the separator for the new string. + +- Format: + +```go +Join(array []string, sep string) string +``` + +- Example: + +```go +func ExampleJoin() { + var ( + array = []string{"goframe", "is", "very", "easy", "to", "use"} + sep = ` ` + result = gstr.Join(array, sep) + ) + fmt.Println(result) + + // Output: + // goframe is very easy to use +} +``` + + +### `JoinAny` + +- Description: `JoinAny` concatenates each element of `array` into a new string. The parameter `sep` is used as the separator for the new string. The parameter `array` can be of any type. + +- Format: + +```go +JoinAny(array interface{}, sep string) string +``` + +- Example: + +```go +func ExampleJoinAny() { + var ( + sep = `,` + arr2 = []int{99, 73, 85, 66} + result = gstr.JoinAny(arr2, sep) + ) + fmt.Println(result) + + // Output: + // 99,73,85,66 +} +``` + + +### `Explode` + +- Description: `Explode` splits `str` into `[]string` using the delimiter `delimiter`. + +- Format: + +```go +Explode(delimiter, str string) []string +``` + +- Example: + +```go +func ExampleExplode() { + var ( + str = `Hello World` + delimiter = " " + result = gstr.Explode(delimiter, str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"Hello", "World"} +} +``` + + +### `Implode` + +- Description: `Implode` joins each element of the `pieces` string array using `glue`. + +- Format: + +```go +Implode(glue string, pieces []string) string +``` + +- Example: + +```go +func ExampleImplode() { + var ( + pieces = []string{"goframe", "is", "very", "easy", "to", "use"} + glue = " " + result = gstr.Implode(glue, pieces) + ) + fmt.Println(result) + + // Output: + // goframe is very easy to use +} +``` + + +### `ChunkSplit` + +- Description: `ChunkSplit` splits the string into smaller chunks of `chunkLen` and joins each chunk with `end`. + +- Format: + +```go +ChunkSplit(body string, chunkLen int, end string) string +``` + +- Example: + +```go +func ExampleChunkSplit() { + var ( + body = `1234567890` + chunkLen = 2 + end = "#" + result = gstr.ChunkSplit(body, chunkLen, end) + ) + fmt.Println(result) + + // Output: + // 12#34#56#78#90# +} +``` + + +### `Fields` + +- Description: `Fields` returns `[]string` representing each word in the string. + +- Format: + +```go +Fields(str string) []string +``` + +- Example: + +```go +func ExampleFields() { + var ( + str = `Hello World` + result = gstr.Fields(str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"Hello", "World"} +} +``` + + +## Escape Processing + +### `AddSlashes` + +- Description: `AddSlashes` adds an escape character `'\'` before the symbols in the string. + +- Format: + +```go +AddSlashes(str string) string +``` + +- Example: + +```go +func ExampleAddSlashes() { + var ( + str = `'aa'"bb"cc\r\n\d\t` + result = gstr.AddSlashes(str) + ) + + fmt.Println(result) + + // Output: + // \'aa\'\"bb\"cc\\r\\n\\d\\t +} +``` + + +### `StripSlashes` + +- Description: `StripSlashes` removes the escape characters `'\'` from string `str`. + +- Format: + +```go +StripSlashes(str string) string +``` + +- Example: + +```go +func ExampleStripSlashes() { + var ( + str = `C:\\windows\\GoFrame\\test` + result = gstr.StripSlashes(str) + ) + fmt.Println(result) + + // Output: + // C:\windows\GoFrame\test +} +``` + + +### `QuoteMeta` + +- Description: `QuoteMeta` adds an escape character `'\'` before each character in `str` among '` \ + * ? [ ^ ] ( $ )`. + +- Format: + +```go +QuoteMeta(str string, chars ...string) string +``` + +- Example: + +```go +func ExampleQuoteMeta() { + { + var ( + str = `.\+?[^]()` + result = gstr.QuoteMeta(str) + ) + fmt.Println(result) + } + { + var ( + str = `https://goframe.org/pages/viewpage.action?pageId=1114327` + result = gstr.QuoteMeta(str) + ) + fmt.Println(result) + } + + // Output: + // \.\\\+\?\[\^\]\(\) + // https://goframe\.org/pages/viewpage\.action\?pageId=1114327 + +} +``` + + +## Count Statistics + +### `Count` + +- Description: `Count` calculates the number of times `substr` occurs in `s`. If `substr` is not found in `s`, it returns `0`. + +- Format: + +```go +Count(s, substr string) int +``` + +- Example: + +```go +func ExampleCount() { + var ( + str = `goframe is very, very easy to use` + substr1 = "goframe" + substr2 = "very" + result1 = gstr.Count(str, substr1) + result2 = gstr.Count(str, substr2) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // 1 + // 2 +} +``` + + +### `CountI` + +- Description: `Count` calculates the number of times `substr` occurs in `s`, case-insensitively. If not found, returns `0`. + +- Format: + +```go +CountI(s, substr string) int +``` + +- Example: + +```go +func ExampleCountI() { + var ( + str = `goframe is very, very easy to use` + substr1 = "GOFRAME" + substr2 = "VERY" + result1 = gstr.CountI(str, substr1) + result2 = gstr.CountI(str, substr2) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // 1 + // 2 +} +``` + + +### `CountWords` + +- Description: `CountWords` returns a `map[string]int` with statistics of words used in `str`. + +- Format: + +```go +CountWords(str string) map[string]int +``` + +- Example: + +```go +func ExampleCountWords() { + var ( + str = `goframe is very, very easy to use!` + result = gstr.CountWords(str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // map[string]int{"easy":1, "goframe":1, "is":1, "to":1, "use!":1, "very":1, "very,":1} +} +``` + + +### `CountChars` + +- Description: `CountChars` returns statistics of characters used in `str` as `map[string]int`. The `noSpace` parameter controls whether to count spaces. + +- Format: + +```go +CountChars(str string, noSpace ...bool) map[string]int +``` + +- Example: + +```go +func ExampleCountChars() { + var ( + str = `goframe` + result = gstr.CountChars(str) + ) + fmt.Println(result) + + // May Output: + // map[a:1 e:1 f:1 g:1 m:1 o:1 r:1] +} +``` + + +## Array Processing + +### `SearchArray` + +- Description: `SearchArray` searches string `'s'` in `[]string 'a'` case-sensitively and returns its index in `'a'`. If `'s'` is not found, it returns `-1`. + +- Format: + +```go +SearchArray(a []string, s string) int +``` + +- Example: + +```go +func ExampleSearchArray() { + var ( + array = []string{"goframe", "is", "very", "nice"} + str = `goframe` + result = gstr.SearchArray(array, str) + ) + fmt.Println(result) + + // Output: + // 0 +} +``` + + +### `InArray` + +- Description: `InArray` verifies whether the `[]string 'a'` contains the string `' s '`. + +- Format: + +```go +InArray(a []string, s string) bool +``` + +- Example: + +```go +func ExampleInArray() { + var ( + a = []string{"goframe", "is", "very", "easy", "to", "use"} + s = "goframe" + result = gstr.InArray(a, s) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +### `PrefixArray` + +- Description: `PrefixArray` adds the prefix `'prefix'` to each string in `[]string array`. + +- Format: + +```go +PrefixArray(array []string, prefix string) +``` + +- Example: + +```go +func ExamplePrefixArray() { + var ( + strArray = []string{"tom", "lily", "john"} + ) + + gstr.PrefixArray(strArray, "classA_") + + fmt.Println(strArray) + + // Output: + // [classA_tom classA_lily classA_john] +} +``` + + +## Naming Conversion + +### `CaseCamel` + +- Description: `CaseCamel` converts a string to a camel case format (capitalize the first letter). + +- Format: + +```go +CaseCamel(s string) string +``` + +- Example: + +```go +func ExampleCaseCamel() { + var ( + str = `hello world` + result = gstr.CaseCamel(str) + ) + fmt.Println(result) + + // Output: + // HelloWorld +} +``` + + +### `CaseCamelLower` + +- Description: `CaseCamelLower` converts a string to camel case format (lowercase the first letter). + +- Format: + +```go +CaseCamelLower(s string) string +``` + +- Example: + +```go +func ExampleCaseCamelLower() { + var ( + str = `hello world` + result = gstr.CaseCamelLower(str) + ) + fmt.Println(result) + + // Output: + // helloWorld +} +``` + + +### `CaseSnake` + +- Description: `CaseSnake` replaces symbols (underscore, space, dot, hyphen) in a string with underscores (`_`) and converts all to lowercase. + +- Format: + +```go +CaseSnake(s string) string +``` + +- Example: + +```go +func ExampleCaseSnake() { + var ( + str = `hello world` + result = gstr.CaseSnake(str) + ) + fmt.Println(result) + + // Output: + // hello_world +} +``` + + +### `CaseSnakeScreaming` + +- Description: `CaseSnakeScreaming` replaces symbols (underscore, space, dot, hyphen) in a string with underscores (`'_'`) and converts all letters to uppercase. + +- Format: + +```go +CaseSnakeScreaming(s string) string +``` + +- Example: + +```go +func ExampleCaseSnakeScreaming() { + var ( + str = `hello world` + result = gstr.CaseSnakeScreaming(str) + ) + fmt.Println(result) + + // Output: + // HELLO_WORLD +} +``` + + +### `CaseSnakeFirstUpper` + +- Description: `CaseSnakeFirstUpper` converts uppercase letters to lowercase and adds an underscore `'_'` before them, except the first uppercase letter, which is only converted to lowercase without an underscore. + +- Format: + +```go +CaseSnakeFirstUpper(word string, underscore ...string) string +``` + +- Example: + +```go +func ExampleCaseSnakeFirstUpper() { + var ( + str = `RGBCodeMd5` + result = gstr.CaseSnakeFirstUpper(str) + ) + fmt.Println(result) + + // Output: + // rgb_code_md5 +} +``` + + +### `CaseKebab` + +- Description: `CaseKebab` replaces symbols (underscore, space, dot,) in a string with hyphens (`'-'`) and converts all to lowercase. + +- Format: + +```go +CaseKebab(s string) string +``` + +- Example: + +```go +func ExampleCaseKebab() { + var ( + str = `hello world` + result = gstr.CaseKebab(str) + ) + fmt.Println(result) + + // Output: + // hello-world +} +``` + + +### `CaseKebabScreaming` + +- Description: `CaseKebabScreaming` replaces symbols (underscore, space, dot, hyphen) in a string with hyphens (`'-'`) and converts all to uppercase. + +- Format: + +```go +CaseKebabScreaming(s string) string +``` + +- Example: + +```go +func ExampleCaseKebabScreaming() { + var ( + str = `hello world` + result = gstr.CaseKebabScreaming(str) + ) + fmt.Println(result) + + // Output: + // HELLO-WORLD +} +``` + + +### `CaseDelimited` + +- Description: `CaseDelimited` replaces symbols in a string based on specified delimiters. + +- Format: + +```go +CaseDelimited(s string, del byte) string +``` + +- Example: + +```go +func ExampleCaseDelimited() { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimited(str, del) + ) + fmt.Println(result) + + // Output: + // hello-world +} +``` + + +### `CaseDelimitedScreaming` + +- Description: `CaseDelimitedScreaming` replaces symbols (space, underscore, dot, hyphen) in a string with the second parameter delimiter, converting to uppercase if the third parameter is `true`, or to lowercase if `false`. + +- Format: + +```go +CaseDelimitedScreaming(s string, del uint8, screaming bool) string +``` + +- Example: + +```go +func ExampleCaseDelimitedScreaming() { + { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimitedScreaming(str, del, true) + ) + fmt.Println(result) + } + { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimitedScreaming(str, del, false) + ) + fmt.Println(result) + } + + // Output: + // HELLO-WORLD + // hello-world +} +``` + + +## Contains Check + +### `Contains` + +- Description: `Contains` returns whether the string `str` contains the substring `substr`, case-sensitively. + +- Format: + +```go +Contains(str, substr string) bool +``` + +- Example: + +```go +func ExampleContains() { + { + var ( + str = `Hello World` + substr = `Hello` + result = gstr.Contains(str, substr) + ) + fmt.Println(result) + } + { + var ( + str = `Hello World` + substr = `hello` + result = gstr.Contains(str, substr) + ) + fmt.Println(result) + } + + // Output: + // true + // false +} +``` + + +### `ContainsI` + +- Description: `ContainsI` verifies whether `substr` is in `str`, case-insensitively. + +- Format: + +```go +ContainsI(str, substr string) bool +``` + +- Example: + +```go +func ExampleContainsI() { + var ( + str = `Hello World` + substr = "hello" + result1 = gstr.Contains(str, substr) + result2 = gstr.ContainsI(str, substr) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // false + // true +} +``` + + +### `ContainsAny` + +- Description: `ContainsAny` verifies whether `s` contains `chars`. + +- Format: + +```go +ContainsAny(s, chars string) bool +``` + +- Example: + +```go +func ExampleContainsAny() { + { + var ( + s = `goframe` + chars = "g" + result = gstr.ContainsAny(s, chars) + ) + fmt.Println(result) + } + { + var ( + s = `goframe` + chars = "G" + result = gstr.ContainsAny(s, chars) + ) + fmt.Println(result) + } + + // Output: + // true + // false +} +``` + + +## String Conversion + +### `Chr` + +- Description: `Chr` returns the `ascii` character string for a number `0-255`. + +- Format: + +```go +Chr(ascii int) string +``` + +- Example: + +```go +func ExampleChr() { + var ( + ascii = 65 // A + result = gstr.Chr(ascii) + ) + fmt.Println(result) + + // Output: + // A +} +``` + + +### `Ord` + +- Description: `Ord` converts the first byte of a string to a value between `0-255`. + +- Format: + +```go +Ord(char string) int +``` + +- Example: + +```go +func ExampleOrd() { + var ( + str = `goframe` + result = gstr.Ord(str) + ) + + fmt.Println(result) + + // Output: + // 103 +} +``` + + +### `OctStr` + +- Description: `OctStr` converts an octal string in `str` to its original string form. + +- Format: + +```go +OctStr(str string) string +``` + +- Example: + +```go +func ExampleOctStr() { + var ( + str = `\346\200\241` + result = gstr.OctStr(str) + ) + fmt.Println(result) + + // Output: + // 怡 +} +``` + + +### `Reverse` + +- Description: `Reverse` returns the reversed string of `str`. + +- Format: + +```go +Reverse(str string) string +``` + +- Example: + +```go +func ExampleReverse() { + var ( + str = `123456` + result = gstr.Reverse(str) + ) + fmt.Println(result) + + // Output: + // 654321 +} +``` + + +### `NumberFormat` + +- Description: `NumberFormat` formats a number with thousand separators. + + - Parameter `decimal` sets the number of decimal points. + - Parameter `decPoint` sets the decimal point separator. + - Parameter `thousand` sets the thousand separator. +- Format: + +```go +NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string +``` + +- Example: + +```go +func ExampleNumberFormat() { + var ( + number float64 = 123456 + decimals = 2 + decPoint = "." + thousandsSep = "," + result = gstr.NumberFormat(number, decimals, decPoint, thousandsSep) + ) + fmt.Println(result) + + // Output: + // 123,456.00 +} +``` + + +### `Shuffle` + +- Description: `Shuffle` returns a randomly shuffled version of `str`. + +- Format: + +```go +Shuffle(str string) string +``` + +- Example: + +```go +func ExampleShuffle() { + var ( + str = `123456` + result = gstr.Shuffle(str) + ) + fmt.Println(result) + + // May Output: + // 563214 +} +``` + + +### `HideStr` + +- Description: `HideStr` converts a percentage `percent` of characters in `str`, starting from the middle, to the `hide` string. + +- Format: + +```go +HideStr(str string, percent int, hide string) string +``` + +- Example: + +```go +func ExampleHideStr() { + var ( + str = `13800138000` + percent = 40 + hide = `*` + result = gstr.HideStr(str, percent, hide) + ) + fmt.Println(result) + + // Output: + // 138****8000 +} +``` + + +### `Nl2Br` + +- Description: `Nl2Br` inserts HTML line break `(' br ' |
    )` before all newline characters in a string: \n\r, \r\n, \r, \n. + +- Format: + +```go +Nl2Br(str string, isXhtml ...bool) string +``` + +- Example: + +```go +func ExampleNl2Br() { + var ( + str = `goframe +is +very +easy +to +use` + result = gstr.Nl2Br(str) + ) + + fmt.Println(result) + + // Output: + // goframe
    is
    very
    easy
    to
    use +} +``` + + +### `WordWrap` + +- Description: `WordWrap` uses line breaks to wrap `str` to a given number of characters (without splitting words). + +- Format: + +```go +WordWrap(str string, width int, br string) string +``` + +- Example: + +```go +func ExampleWordWrap() { + { + var ( + str = `A very long woooooooooooooooooord. and something` + width = 8 + br = "\n" + result = gstr.WordWrap(str, width, br) + ) + fmt.Println(result) + } + { + var ( + str = `The quick brown fox jumped over the lazy dog.` + width = 20 + br = "
    \n" + result = gstr.WordWrap(str, width, br) + ) + fmt.Printf("%v", result) + } + + // Output: + // A very + // long + // woooooooooooooooooord. + // and + // something + // The quick brown fox
    + // jumped over the lazy
    + // dog. +} +``` + + +## Domain Processing + +### `IsSubDomain` + +- Description: `IsSubDomain` verifies whether `subDomain` is a subdomain of `mainDomain`. Supports `'*'` in `mainDomain`. + +- Format: + +```go +IsSubDomain(subDomain string, mainDomain string) bool +``` + +- Example: + +```go +func ExampleIsSubDomain() { + var ( + subDomain = `s.goframe.org` + mainDomain = `goframe.org` + result = gstr.IsSubDomain(subDomain, mainDomain) + ) + fmt.Println(result) + + // Output: + // true +} + + +``` + + +## Parameter Parsing + +### `Parse` + +- Description: `Parse` parses a string and returns it as `map[string]interface{}`. + +- Format: + +```go +Parse(s string) (result map[string]interface{}, err error) +``` + +- Example: + +```go +func ExampleParse() { + { + var ( + str = `v1=m&v2=n` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + { + var ( + str = `v[a][a]=m&v[a][b]=n` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + { + // The form of nested Slice is not yet supported. + var str = `v[][]=m&v[][]=n` + result, err := gstr.Parse(str) + if err != nil { + panic(err) + } + fmt.Println(result) + } + { + // This will produce an error. + var str = `v=m&v[a]=n` + result, err := gstr.Parse(str) + if err != nil { + println(err) + } + fmt.Println(result) + } + { + var ( + str = `a .[[b=c` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + + // May Output: + // map[v1:m v2:n] + // map[v:map[a:map[a:m b:n]]] + // map[v:map[]] + // Error: expected type 'map[string]interface{}' for key 'v', but got 'string' + // map[] + // map[a___[b:c] +} +``` + + +## Position Finding + +### `Pos` + +- Description: `Pos` returns the first occurrence of `needle` in `haystack`, case-sensitively. If not found, returns -1. + +- Format: + +```go +Pos(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePos() { + var ( + haystack = `Hello World` + needle = `World` + result = gstr.Pos(haystack, needle) + ) + fmt.Println(result) + + // Output: + // 6 +} +``` + + +### `PosRune` + +- Description: `PosRune` functions similarly to `Pos`, but supports `haystack` and `needle` as `unicode` strings. + +- Format: + +```go +PosRune(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `Go` + posI = gstr.PosRune(haystack, needle) + posR = gstr.PosRRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +### `PosI` + +- Description: `PosI` returns the first occurrence of `needle` in `haystack`, case-insensitively. If not found, returns -1. + +- Format: + +```go +PosI(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosI() { + var ( + haystack = `goframe is very, very easy to use` + needle = `very` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosR(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRuneI` + +- Description: `PosRuneI` functions similarly to `PosI`, but supports `haystack` and `needle` as `unicode` strings. + +- Format: + +```go +PosIRune(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosIRune() { + { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `高性能` + startOffset = 10 + result = gstr.PosIRune(haystack, needle, startOffset) + ) + fmt.Println(result) + } + { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `高性能` + startOffset = 30 + result = gstr.PosIRune(haystack, needle, startOffset) + ) + fmt.Println(result) + } + + // Output: + // 14 + // -1 +} +``` + + +### `PosR` + +- Description: `PosR` returns the last occurrence of `needle` in `haystack`, case-sensitively. If not found, returns -1. + +- Format: + +```go +PosR(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosR() { + var ( + haystack = `goframe is very, very easy to use` + needle = `very` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosR(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRuneR` + +- Description: `PosRuneR` functions similarly to `PosR`, but supports `haystack` and `needle` as `unicode` strings. + +- Format: + +```go +PosRRune(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosRRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `Go` + posI = gstr.PosIRune(haystack, needle) + posR = gstr.PosRRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +### `PosRI` + +- Description: `PosRI` returns the last occurrence of `needle` in `haystack`, case-insensitively. If not found, returns -1. + +- Format: + +```go +PosRI(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosRI() { + var ( + haystack = `goframe is very, very easy to use` + needle = `VERY` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosRI(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRIRune` + +- Description: `PosRIRune` functions similarly to `PosRI`, but supports `haystack` and `needle` as `unicode` strings. + +- Format: + +```go +PosRIRune(haystack, needle string, startOffset ...int) int +``` + +- Example: + +```go +func ExamplePosRIRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `GO` + posI = gstr.PosIRune(haystack, needle) + posR = gstr.PosRIRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +## Find and Replace + +### `Replace` + +- Description: `Replace` returns a new string where `search` in `origin` is replaced by `replace`. `search` is case-sensitive. + +- Format: + +```go +Replace(origin, search, replace string, count ...int) string +``` + +- Example: + +```go +func ExampleReplace() { + var ( + origin = `golang is very nice!` + search = `golang` + replace = `goframe` + result = gstr.Replace(origin, search, replace) + ) + fmt.Println(result) + + // Output: + // goframe is very nice! +} +``` + + +### `ReplaceI` + +- Description: `ReplaceI` returns a new string where `search` in `origin` is replaced by `replace`. `search` is case-insensitive. + +- Format: + +```go +ReplaceI(origin, search, replace string, count ...int) string +``` + +- Example: + +```go +func ExampleReplaceI() { + var ( + origin = `golang is very nice!` + search = `GOLANG` + replace = `goframe` + result = gstr.ReplaceI(origin, search, replace) + ) + fmt.Println(result) + + // Output: + // goframe is very nice! +} +``` + + +### `ReplaceByArray` + +- Description: `ReplaceByArray` returns a new string where `origin` is sequentially replaced with pairs `(search, replace)` from an array, case-sensitively. + +- Format: + +```go +ReplaceByArray(origin string, array []string) string +``` + +- Example: + +```go +func ExampleReplaceByArray() { + { + var ( + origin = `golang is very nice` + array = []string{"lang", "frame"} + result = gstr.ReplaceByArray(origin, array) + ) + fmt.Println(result) + } + { + var ( + origin = `golang is very good` + array = []string{"golang", "goframe", "good", "nice"} + result = gstr.ReplaceByArray(origin, array) + ) + fmt.Println(result) + } + + // Output: + // goframe is very nice + // goframe is very nice +} +``` + + +### `ReplaceIByArray` + +- Description: `ReplaceIByArray` returns a new string where `origin` is sequentially replaced with pairs `(search, replace)` from an array, case-insensitively. + +- Format: + +```go +ReplaceIByArray(origin string, array []string) string +``` + +- Example: + +```go +func ExampleReplaceIByArray() { + var ( + origin = `golang is very Good` + array = []string{"Golang", "goframe", "GOOD", "nice"} + result = gstr.ReplaceIByArray(origin, array) + ) + + fmt.Println(result) + + // Output: + // goframe is very nice +} +``` + + +### `ReplaceByMap` + +- Description: `ReplaceByMap` returns a new string where keys in `map` are replaced with values in `origin`, case-sensitively. + +- Format: + +```go +ReplaceByMap(origin string, replaces map[string]string) string +``` + +- Example: + +```go +func ExampleReplaceByMap() { + { + var ( + origin = `golang is very nice` + replaces = map[string]string{ + "lang": "frame", + } + result = gstr.ReplaceByMap(origin, replaces) + ) + fmt.Println(result) + } + { + var ( + origin = `golang is very good` + replaces = map[string]string{ + "golang": "goframe", + "good": "nice", + } + result = gstr.ReplaceByMap(origin, replaces) + ) + fmt.Println(result) + } + + // Output: + // goframe is very nice + // goframe is very nice +} +``` + + +### `ReplaceIByMap` + +- Description: `ReplaceIByMap` returns a new string where keys in `map` are replaced with values in `origin`, case-insensitively. + +- Format: + +```go +ReplaceIByMap(origin string, replaces map[string]string) string +``` + +- Example: + +```go +func ExampleReplaceIByMap() { + var ( + origin = `golang is very nice` + replaces = map[string]string{ + "Lang": "frame", + } + result = gstr.ReplaceIByMap(origin, replaces) + ) + fmt.Println(result) + + // Output: + // goframe is very nice +} +``` + + +## Substring Extraction + +### `Str` + +- Description: `Str` returns the substring from the first occurrence of `needle` to the end of `haystack` (including `needle` itself). + +- Format: + +```go +Str(haystack string, needle string) string +``` + +- Example: + +```go +func ExampleStr() { + var ( + haystack = `xxx.jpg` + needle = `.` + result = gstr.Str(haystack, needle) + ) + fmt.Println(result) + + // Output: + // .jpg +} +``` + + +### `StrEx` + +- Description: `StrEx` returns the substring from the first occurrence of `needle` to the end of `haystack` (excluding `needle` itself). + +- Format: + +```go +StrEx(haystack string, needle string) string +``` + +- Example: + +```go +func ExampleStrEx() { + var ( + haystack = `https://goframe.org/index.html?a=1&b=2` + needle = `?` + result = gstr.StrEx(haystack, needle) + ) + fmt.Println(result) + + // Output: + // a=1&b=2 +} +``` + + +### `StrTill` + +- Description: `StrTill` returns the substring from the start of `haystack` to the first occurrence of `needle` (including `needle` itself). + +- Format: + +```go +StrTill(haystack string, needle string) string +``` + +- Example: + +```go +func ExampleStrTill() { + var ( + haystack = `https://goframe.org/index.html?test=123456` + needle = `?` + result = gstr.StrTill(haystack, needle) + ) + fmt.Println(result) + + // Output: + // https://goframe.org/index.html? +} +``` + + +### `StrTillEx` + +- Description: `StrTillEx` returns the substring from the start of `haystack` to the first occurrence of `needle` (excluding `needle` itself). + +- Format: + +```go +StrTillEx(haystack string, needle string) string +``` + +- Example: + +```go +func ExampleStrTillEx() { + var ( + haystack = `https://goframe.org/index.html?test=123456` + needle = `?` + result = gstr.StrTillEx(haystack, needle) + ) + fmt.Println(result) + + // Output: + // https://goframe.org/index.html +} +``` + + +### `SubStr` + +- Description: `SubStr` returns a new substring from `str` starting at `start` with length `length`. Parameter `length` is optional and defaults to the length of `str`. + +- Format: + +```go +SubStr(str string, start int, length ...int) (substr string) +``` + +- Example: + +```go +func ExampleSubStr() { + var ( + str = `1234567890` + start = 0 + length = 4 + subStr = gstr.SubStr(str, start, length) + ) + fmt.Println(subStr) + + // Output: + // 1234 +} +``` + + +### `SubStrRune` + +- Description: `SubStrRune` returns a new substring from `unicode` string `str` starting at `start` with length `length`. Parameter `length` is optional and defaults to the length of `str`. + +- Format: + +```go +SubStrRune(str string, start int, length ...int) (substr string) +``` + +- Example: + +```go +func ExampleSubStrRune() { + var ( + str = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架。` + start = 14 + length = 3 + subStr = gstr.SubStrRune(str, start, length) + ) + fmt.Println(subStr) + + // Output: + // 高性能 +} +``` + + +### `StrLimit` + +- Description: `StrLimit` takes a substring from the beginning of `str` with length `length`, adds `suffix...`, and returns the new string. + +- Format: + +```go +StrLimit(str string, length int, suffix ...string) string +``` + +- Example: + +```go +func ExampleStrLimit() { + var ( + str = `123456789` + length = 3 + suffix = `...` + result = gstr.StrLimit(str, length, suffix) + ) + fmt.Println(result) + + // Output: + // 123... +} +``` + + +### `StrLimitRune` + +- Description: `StrLimitRune` takes a substring from the beginning of `unicode` string `str` with length `length`, adds `suffix...`, and returns the new string. + +- Format: + +```go +StrLimitRune(str string, length int, suffix ...string) string +``` + +- Example: + +```go +func ExampleStrLimitRune() { + var ( + str = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架。` + length = 17 + suffix = "..." + result = gstr.StrLimitRune(str, length, suffix) + ) + fmt.Println(result) + + // Output: + // GoFrame是一款模块化、高性能... +} +``` + + +### `SubStrFrom` + +- Description: `SubStrFrom` returns the substring from the first occurrence of `need` to the end of `str` (including `need`). + +- Format: + +```go +SubStrFrom(str string, need string) (substr string) +``` + +- Example: + +```go +func ExampleSubStrFrom() { + var ( + str = "我爱GoFrameGood" + need = `爱` + ) + + fmt.Println(gstr.SubStrFrom(str, need)) + + // Output: + // 爱GoFrameGood +} +``` + + +### `SubStrFromEx` + +- Description: `SubStrFromEx` returns the substring from the first occurrence of `need` to the end of `str` (excluding `need`). + +- Format: + +```go +SubStrFromEx(str string, need string) (substr string) +``` + +- Example: + +```go +func ExampleSubStrFromEx() { + var ( + str = "我爱GoFrameGood" + need = `爱` + ) + + fmt.Println(gstr.SubStrFromEx(str, need)) + + // Output: + // GoFrameGood +} +``` + + +### `SubStrFromR` + +- Description: `SubStrFromR` returns the substring from the last occurrence of `need` to the end of `str` (including `need`). + +- Format: + +```go +SubStrFromR(str string, need string) (substr string) +``` + +- Example: + +```go +func ExampleSubStrFromR() { + var ( + str = "我爱GoFrameGood" + need = `Go` + ) + + fmt.Println(gstr.SubStrFromR(str, need)) + + // Output: + // Good +} +``` + + +### `SubStrFromREx` + +- Description: `SubStrFromREx` returns the substring from the last occurrence of `need` to the end of `str` (excluding `need`). + +- Format: + +```go +SubStrFromREx(str string, need string) (substr string) +``` + +- Example: + +```go +func ExampleSubStrFromREx() { + var ( + str = "我爱GoFrameGood" + need = `Go` + ) + + fmt.Println(gstr.SubStrFromREx(str, need)) + + // Output: + // od +} +``` + + +## Character/Substring Filtering + +### `Trim` + +- Description: `Trim` removes spaces (or other characters) from the beginning and end of a string. The optional parameter `characterMask` specifies additional characters to trim. + +- Format: + +```go +Trim(str string, characterMask ...string) string +``` + +- Example: + +```go +func ExampleTrim() { + var ( + str = `*Hello World*` + characterMask = "*d" + result = gstr.Trim(str, characterMask) + ) + fmt.Println(result) + + // Output: + // Hello Worl +} +``` + + +### `TrimStr` + +- Description: `TrimStr` removes all `cut` strings from the beginning and end of a string (does not remove leading or trailing whitespace). + +- Format: + +```go +TrimStr(str string, cut string, count ...int) string +``` + +- Example: + +```go +func ExampleTrimStr() { + var ( + str = `Hello World` + cut = "World" + count = -1 + result = gstr.TrimStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // Hello +} +``` + + +### `TrimLeft` + +- Description: `TrimLeft` removes spaces (or other characters) from the beginning of a string. + +- Format: + +```go +TrimLeft(str string, characterMask ...string) string +``` + +- Example: + +```go +func ExampleTrimLeft() { + var ( + str = `*Hello World*` + characterMask = "*" + result = gstr.TrimLeft(str, characterMask) + ) + fmt.Println(result) + + // Output: + // Hello World* +} +``` + + +### `TrimLeftStr` + +- Description: `TrimLeftStr` removes `count` occurrences of the `cut` string from the beginning of a string (does not remove leading whitespace). + +- Format: + +```go +TrimLeftStr(str string, cut string, count ...int) string +``` + +- Example: + +```go +func ExampleTrimLeftStr() { + var ( + str = `**Hello World**` + cut = "*" + count = 1 + result = gstr.TrimLeftStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // *Hello World** +} +``` + + +### `TrimRight` + +- Description: `TrimRight` removes spaces (or other characters) from the end of a string. + +- Format: + +```go +TrimRight(str string, characterMask ...string) string +``` + +- Example: + +```go +func ExampleTrimRight() { + var ( + str = `**Hello World**` + characterMask = "*def" // []byte{"*", "d", "e", "f"} + result = gstr.TrimRight(str, characterMask) + ) + fmt.Println(result) + + // Output: + // **Hello Worl +} +``` + + +### `TrimRightStr` + +- Description: `TrimRightStr` removes `count` occurrences of the `cut` string from the end of a string (does not remove trailing whitespace). + +- Format: + +```go +TrimRightStr(str string, cut string, count ...int) string +``` + +- Example: + +```go +func ExampleTrimRightStr() { + var ( + str = `Hello World!` + cut = "!" + count = -1 + result = gstr.TrimRightStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // Hello World +} +``` + + +### `TrimAll` + +- Description: `TrimAll` removes all spaces (or other characters) and `characterMask` characters from string `str`. + +- Format: + +```go +TrimAll(str string, characterMask ...string) string +``` + +- Example: + +```go +func ExampleTrimAll() { + var ( + str = `*Hello World*` + characterMask = "*" + result = gstr.TrimAll(str, characterMask) + ) + fmt.Println(result) + + // Output: + // HelloWorld +} +``` + + +### `HasPrefix` + +- Description: `HasPrefix` returns whether `s` starts with `prefix`. + +- Format: + +```go +HasPrefix(s, prefix string) bool +``` + +- Example: + +```go +func ExampleHasPrefix() { + var ( + s = `Hello World` + prefix = "Hello" + result = gstr.HasPrefix(s, prefix) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +### `HasSuffix` + +- Description: `HasSuffix` returns whether `s` ends with `suffix`. + +- Format: + +```go +HasSuffix(s, suffix string) bool +``` + +- Example: + +```go +func ExampleHasSuffix() { + var ( + s = `my best love is goframe` + prefix = "goframe" + result = gstr.HasSuffix(s, prefix) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +## Version Comparison + +### `CompareVersion` + +- Description: `CompareVersion` compares `a` and `b` as standard `GNU` versions. + +- Format: + +```go +CompareVersion(a, b string) int +``` + +- Example: + +```go +func ExampleCompareVersion() { + fmt.Println(gstr.CompareVersion("v2.11.9", "v2.10.8")) + fmt.Println(gstr.CompareVersion("1.10.8", "1.19.7")) + fmt.Println(gstr.CompareVersion("2.8.beta", "2.8")) + + // Output: + // 1 + // -1 + // 0 +} +``` + + +### `CompareVersionGo` + +- Description: `CompareVersionGo` compares `a` and `b` as standard `Golang` versions. + +- Format: + +```go +CompareVersionGo(a, b string) int +``` + +- Example: + +```go +func ExampleCompareVersionGo() { + fmt.Println(gstr.CompareVersionGo("v2.11.9", "v2.10.8")) + fmt.Println(gstr.CompareVersionGo("v4.20.1", "v4.20.1+incompatible")) + fmt.Println(gstr.CompareVersionGo( + "v0.0.2-20180626092158-b2ccc119800e", + "v1.0.1-20190626092158-b2ccc519800e", + )) + + // Output: + // 1 + // 1 + // -1 +} +``` + + +## Similarity Calculation + +### `Levenshtein` + +- Description: `Levenshtein` calculates the `Levenshtein` distance between two strings. + +- Format: + +```go +Levenshtein(str1, str2 string, costIns, costRep, costDel int) int +``` + +- Example: + +```go +func ExampleLevenshtein() { + var ( + str1 = "Hello World" + str2 = "hallo World" + costIns = 1 + costRep = 1 + costDel = 1 + result = gstr.Levenshtein(str1, str2, costIns, costRep, costDel) + ) + fmt.Println(result) + + // Output: + // 2 +} +``` + + +### `SimilarText` + +- Description: `SimilarText` calculates the similarity between two strings. + +- Format: + +```go +SimilarText(first, second string, percent *float64) int +``` + +- Example: + +```go +func ExampleSimilarText() { + var ( + first = `AaBbCcDd` + second = `ad` + percent = 0.80 + result = gstr.SimilarText(first, second, &percent) + ) + fmt.Println(result) + + // Output: + // 2 +} +``` + + +### `Soundex` + +- Description: `Soundex` is used to calculate the `Soundex` key for a string. + +- Format: + +```go +Soundex(str string) string +``` + +- Example: + +```go +func ExampleSoundex() { + var ( + str1 = `Hello` + str2 = `Hallo` + result1 = gstr.Soundex(str1) + result2 = gstr.Soundex(str2) + ) + fmt.Println(result1, result2) + + // Output: + // H400 H400 +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..d2592020658 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/text' +title: 'Text' +sidebar_position: 2 +hide_title: true +keywords: [Text Processing, GoFrame, GoFrame Framework, Components, Programming, Development, Tools, Frontend Development, Backend Development, Web Development] +description: "Using the GoFrame framework for text processing, helping developers efficiently build and manage text content in the GoFrame environment. Leverage the powerful features of GoFrame to simplify the programming process, enhance development efficiency, and allow developers to focus on implementing core business logic." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" new file mode 100644 index 00000000000..6154704c711 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/text-gregex' +title: 'Regular Expression' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gregex,regular expression,regex library,regex parsing,parsing cache,execution efficiency,project documentation,golang] +description: "Basic functions and usage of the gregex library in the GoFrame framework. gregex is a wrapper around the standard library regexp, providing a simplified way to use regular expressions and optimizing execution efficiency through parsing cache design, making regex operations more efficient and convenient." +--- + +## Introduction + +`gregex` provides support for regular expressions, encapsulating the standard library `regexp` at the core, greatly simplifying regex usage, and employs a parsing cache design to improve execution efficiency. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/text/gregex" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex](https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..492d02f3d6b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,30 @@ +--- +slug: '/docs/components/text-gregex-example' +title: 'Regular Expressions - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, regular expressions, basic usage, programming example, Golang, text processing, software development, open source framework, Go language] +description: "Demonstrates how to use regular expressions in the GoFrame framework for basic text matching operations. Through a simple example code, you can learn how to extract and process information from strings, providing an important reference for developers in text processing." +--- + +A simple example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/text/gregex" +) + +func main() { + match, _ := gregex.MatchString(`(\w+).+\-\-\s*(.+)`, `GoFrame is best! -- John`) + fmt.Printf(`%s says "%s" is the one he loves!`, match[2], match[1]) +} +``` + +After execution, the output result is: + +``` +John says "GoFrame" is the one he loves! +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..acf0cd8471d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,341 @@ +--- +slug: '/docs/components/text-gregex-funcs' +title: 'Regular Expressions - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame framework, regular expressions, IsMatch, Match, MatchAll, Quote, Replace, ReplaceFunc, ReplaceFuncMatch, Split] +description: "Using GoFrame framework for regular expression matching, including common methods such as IsMatch, Match, MatchAll, and more, with function definitions and examples. The GoFrame framework is used in various regex processing scenarios for its efficient execution performance. This document provides teaching through specific examples to help you better understand and apply these features." +--- +:::tip +The following is a list of common methods. Documentation updates may lag behind new code features. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex](https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex) +::: +> When the function name contains `All`, it will continue to search for non-overlapping follow-ups and return a `slice`. +> +> When the function name contains `String`, the parameters and return values are `string`, otherwise they are `[]byte`. + +## `IsMatch/IsMatchString` + +- Description: The `IsMatch()` method can test a string to determine whether it matches the pattern of a regular expression. If one match is found, the method returns `true`; otherwise, it returns `false`. +- Format: + +```go +IsMatch(pattern string, src []byte) bool +IsMatchString(pattern string, src string) bool +``` + +- Tip: `regexp` has already handled and cached the `Regex` object at the bottom level to significantly improve execution efficiency, so there is no need to explicitly recreate it each time. +- Example: + +```go +func ExampleIsMatch() { + patternStr := `\d+` + g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello GoFrame!"))) + g.Dump(gregex.IsMatch(patternStr, nil)) + g.Dump(gregex.IsMatch(patternStr, []byte("hello GoFrame!"))) + + // Output: + // true + // false + // false +} +``` + + +## `Match/MatchString` + +- Description: Used to match substrings. `Match` only returns the first matching result. Unlike native regex methods, `gregex` wraps `FindSubmatch` to directly return a `slice` of the first sub-pattern result. +- Format: + +```go +Match(pattern string, src []byte) ([][]byte, error) +MatchString(pattern string, src string) ([]string, error) +``` + +- Example: Matching parameters in `url`. + +```go +func ExampleMatch() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + // This method looks for the first match index + result, err := gregex.Match(patternStr, []byte(matchStr)) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ] + // +} +``` + + +## `MatchAll/MatchAllString` + +- Description: Used for matching substrings, `MatchAll` returns all results. Unlike native regex methods, `gregex`'s `MatchAll` wraps `FindAllSubmatch`, returning a `slice` of all result sets, including sub-pattern results within the result sets. +- Format: + +```go +MatchAllString(pattern string, src string) ([][]string, error) +MatchAll(pattern string, src []byte) ([][][]byte, error) +``` + +- Example: The following example matches the parameters in `url`. + +```go +func ExampleMatchAll() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + result, err := gregex.MatchAll(patternStr, []byte(matchStr)) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ], + // [ + // "searchId=8QC5D1D2E", + // "searchId", + // "8QC5D1D2E", + // ], + // ] + // +} + +func ExampleMatchAllString() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + result, err := gregex.MatchAllString(patternStr, matchStr) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ], + // [ + // "searchId=8QC5D1D2E", + // "searchId", + // "8QC5D1D2E", + // ], + // ] + // +} +``` + + +## `Quote` + +- Description: Escapes specific symbols in the given regular expression. +- Format: + +```go +Quote(s string) string +``` + +- Example: + +```go +func ExampleQuote() { + result := gregex.Quote(`[1-9]\d+`) + g.Dump(result) + + // Output: + // "\[1-9\]\\d\+" +} +``` + + +## `Replace/ReplaceString` + +- Description: Used to replace all matching strings and return a copy of the source string. +- Format: + +```go +Replace(pattern string, replace, src []byte) ([]byte, error) +ReplaceString(pattern, replace, src string) (string, error) +``` + +- Example: + +```go +func ExampleReplace() { + var ( + patternStr = `\d+` + str = "hello GoFrame 2020!" + repStr = "2021" + result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str)) + ) + g.Dump(err) + g.Dump(result) + + // Output: + // + // "hello GoFrame 2021!" +} +``` + + +## `ReplaceFunc/ReplaceStringFunc` + +- Description: Used to replace all matching strings and return a copy of the source string. Unlike the `Replace` method, this method allows secondary evaluation or processing within a closure instead of simple replacement. +- Format: + +```go +ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte) ([]byte, error) +ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) string) (string, error) +``` + +- Example: + +```go +func ExampleReplaceFunc() { + var ( + patternStr = `(\d+)~(\d+)` + str = "hello GoFrame 2018~2020!" + ) + // In contrast to [ExampleReplace] + result, err := gregex.ReplaceFunc(patternStr, []byte(str), func(match []byte) []byte { + g.Dump(match) + return bytes.Replace(match, []byte("2020"), []byte("2023"), -1) + }) + g.Dump(result) + g.Dump(err) + + // ReplaceStringFunc + resultStr, _ := gregex.ReplaceStringFunc(patternStr, str, func(match string) string { + g.Dump(match) + return strings.Replace(match, "2020", "2023", -1) + }) + g.Dump(resultStr) + g.Dump(err) + + // Output: + // "2018~2020" + // "hello gf 2018~2023!" // ReplaceFunc result + // + // "2018~2020" + // "hello gf 2018-2023!" // ReplaceStringFunc result + // +} +``` + + +## `ReplaceFuncMatch/ReplaceStringFuncMatch` + +> `ReplaceFuncMatch` returns a copy of `src`, where all matches of `regexp` are replaced by the function's return value applied to the matched byte slices. The returned replacement replaces directly. + +- Description: Used to replace all matching strings and return a copy of the source string. The power of this method lies in secondary evaluation or processing within a closure, where the `MatchString` function contains all sub-pattern query results, rather than simple replacement. +- Format: + +```go +ReplaceFuncMatch(pattern string, src []byte, replaceFunc func(match [][]byte) []byte) ([]byte, error) +ReplaceStringFuncMatch(pattern string, src string, replaceFunc func(match []string) string) (string, error) +``` + +- Example: + +```go +func ExampleReplaceStringFuncMatch() { + var ( + patternStr = `([A-Z])\w+` + str = "hello Golang 2018~2021!" + ) + // In contrast to [ExampleReplaceFunc] + // the result contains the `pattern' of all subpatterns that use the matching function + result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte { + g.Dump(match) + return []byte("GoFrame") + }) + g.Dump(result) + g.Dump(err) + + // ReplaceStringFuncMatch + resultStr, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string { + g.Dump(match) + match[0] = "Gf" + return match[0] + }) + g.Dump(resultStr) + g.Dump(err) + + // Output: + // [ + // "Golang", + // "G", + // ] + // "hello GoFrame 2018~2021!" // ReplaceFuncMatch result + // + // [ + // "Golang", + // "G", + // ] + // "hello Gf 2018~2021!" // ReplaceStringFuncMatch result + // +} +``` + + +## `Split` + +- Description: Splits the text content using the specified regular expression. Without metacharacters, equivalent to `strings.SplitN`. +- Format: + +```go +Split(pattern string, src string) []string +``` + +- Example: + +```go +func ExampleSplit() { + patternStr := `\d+` + str := "hello2020GoFrame" + result := gregex.Split(patternStr, str) + g.Dump(result) + + // Output: + // [ + // "hello", + // "GoFrame", + // ] +} +``` + + +## `Validate` + +- Description: Wraps the native `compile` method to check if the given regular expression is valid. +- Format: + +```go +Validate(pattern string) error +``` + +- Example: + +```go +func ExampleValidate() { + // Valid match statement + g.Dump(gregex.Validate(`\d+`)) + // Mismatched statement + g.Dump(gregex.Validate(`[a-9]\d+`)) + + // Output: + // + // { + // Code: "invalid character class range", + // Expr: "a-9", + // } +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" new file mode 100644 index 00000000000..62e7c606723 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" @@ -0,0 +1,24 @@ +--- +slug: '/docs/components/os-gctx' +title: 'Context' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, gctx, context management, trace tracing, process initialization, context object, component API, New method, GetInitCtx] +description: "The basic concepts and common methods of the gctx component in the GoFrame framework. gctx is used to simplify trace tracing and context object management, facilitating process initialization and context operations. The main content includes how to create and obtain context objects that support trace tracing, as well as its application in processes and init packages. More detailed understanding of the actual application of gctx can be gained with example code and API documentation." +--- + +## Introduction + +The `gctx` component implements encapsulation of context operations to simplify trace tracing and the management of process initialization context objects. + +## Common Methods + +Here are just a few common methods; for other methods, please refer to the component API documentation or source code. + +### `New` + +Used to create a context object with trace tracing capabilities. + +### `GetInitCtx` + +Used in the startup process or init package methods to obtain the current process's context object with trace tracing capabilities, and also for transferring trace tracing information between processes. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" new file mode 100644 index 00000000000..89fa97809c8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" @@ -0,0 +1,154 @@ +--- +slug: '/docs/components/os-gmutex' +title: 'Mutex' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame Framework, gmutex mutex, mutex tutorial, concurrent read/write control, lock mechanism optimization, TryLock method, Func method example, benchmark, concurrency safety, Go programming] +description: "The gmutex mutex in the GoFrame framework supports concurrent read/write control, similar to the standard library's sync.RWMutex. It features Try* methods and *Func methods for non-blocking lock mechanisms and specific scope lock control. Example code demonstrates its convenient usage and benchmark comparisons with standard library locks, showing its performance advantages. Suitable for concurrent programming scenarios needing efficient lock mechanisms." +--- + +`gmutex.Mutex` mutex objects support read/write control, with functionality similar to the standard library's `sync.RWMutex`, allowing concurrent reads but not concurrent writes. + +> For design details of the mutex, it's recommended to read the lightweight high-definition version of the implementation source code: [https://github.com/gogf/gf/blob/master/os/gmutex/gmutex.go](https://github.com/gogf/gf/blob/master/os/gmutex/gmutex.go) + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gmutex" +``` + +**Interface Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gmutex](https://pkg.go.dev/github.com/gogf/gf/v2/os/gmutex) + +```go +type Mutex + func (m *Mutex) LockFunc(f func()) + func (m *Mutex) TryLockFunc(f func()) (result bool) +type RWMutex + func New() *RWMutex + func (m *RWMutex) LockFunc(f func()) + func (m *RWMutex) RLockFunc(f func()) + func (m *RWMutex) TryLockFunc(f func()) (result bool) + func (m *RWMutex) TryRLockFunc(f func()) (result bool) +``` + +1. The standout feature of this mutex module is the support for `Try*` methods and `*Func` methods. +2. `Try*` methods are used to attempt to acquire a specific type of lock. If the lock is acquired successfully, it immediately returns `true`; otherwise, it returns `false` without blocking, which is very practical for business logic requiring non-blocking lock mechanisms. +3. `*Func` methods use closure anonymous functions for concurrent safety lock control in specific scopes, which is particularly convenient for concurrent safety control of specific code blocks. As it uses `defer` internally to release locks, even if exceptions or errors occur within the function, it will not affect the safety control of the locking mechanism. + +### Benchmark + +Benchmark comparison results of `gmutex.Mutex` with the standard library's `sync.Mutex` and `sync.RWMutex`: [gmutex\_bench\_test.go](https://github.com/gogf/gf/blob/master/os/gmutex/gmutex_bench_test.go) + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/os/gmutex +Benchmark_Mutex_LockUnlock-4 50000000 31.5 ns/op +Benchmark_RWMutex_LockUnlock-4 30000000 54.1 ns/op +Benchmark_RWMutex_RLockRUnlock-4 50000000 27.9 ns/op +Benchmark_GMutex_LockUnlock-4 50000000 27.2 ns/op +Benchmark_GMutex_TryLock-4 100000000 16.7 ns/op +Benchmark_GMutex_RLockRUnlock-4 50000000 38.0 ns/op +Benchmark_GMutex_TryRLock-4 100000000 16.8 ns/op +``` + +### Example 1, Basic Usage + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gmutex" +) + +func main() { + ctx := gctx.New() + mu := gmutex.New() + for i := 0; i < 10; i++ { + go func(n int) { + mu.Lock() + defer mu.Unlock() + glog.Print(ctx, "Lock:", n) + time.Sleep(time.Second) + }(i) + } + for i := 0; i < 10; i++ { + go func(n int) { + mu.RLock() + defer mu.RUnlock() + glog.Print(ctx, "RLock:", n) + time.Sleep(time.Second) + }(i) + } + time.Sleep(15 * time.Second) +} +``` + +After execution, the terminal output: + +```html +2019-07-13 16:19:55.417 Lock: 0 +2019-07-13 16:19:56.421 Lock: 1 +2019-07-13 16:19:57.424 RLock: 0 +2019-07-13 16:19:57.424 RLock: 4 +2019-07-13 16:19:57.425 RLock: 8 +2019-07-13 16:19:57.425 RLock: 2 +2019-07-13 16:19:57.425 RLock: 7 +2019-07-13 16:19:57.425 RLock: 5 +2019-07-13 16:19:57.425 RLock: 9 +2019-07-13 16:19:57.425 RLock: 1 +2019-07-13 16:19:57.425 RLock: 6 +2019-07-13 16:19:57.425 RLock: 3 +2019-07-13 16:19:58.429 Lock: 3 +2019-07-13 16:19:59.433 Lock: 4 +2019-07-13 16:20:00.438 Lock: 5 +2019-07-13 16:20:01.443 Lock: 6 +2019-07-13 16:20:02.448 Lock: 7 +2019-07-13 16:20:03.452 Lock: 8 +2019-07-13 16:20:04.456 Lock: 9 +2019-07-13 16:20:05.461 Lock: 2 +``` + +The purpose of using `glog` for printing is to conveniently observe the print output time. You can see that in the third second, the read lock seized the opportunity. Since `gmutex.Mutex` objects support concurrent reads but not concurrent writes, the read lock quickly executed after seizing; meanwhile, the write lock continues executing with one log per second. + +### Example 2, `*Func` Usage + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/os/glog" + + "github.com/gogf/gf/v2/os/gmutex" +) + +func main() { + mu := gmutex.New() + go mu.LockFunc(func() { + glog.Println("lock func1") + time.Sleep(1 * time.Second) + }) + time.Sleep(time.Millisecond) + go mu.LockFunc(func() { + glog.Println("lock func2") + }) + time.Sleep(2 * time.Second) +} +``` + +After execution, the terminal output: + +```html +2019-07-13 16:28:10.381 lock func1 +2019-07-13 16:28:11.385 lock func2 +``` + +As you can see, using `*Func` methods for lock control in specific scopes is very convenient. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" new file mode 100644 index 00000000000..0fb3ac37a35 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" @@ -0,0 +1,149 @@ +--- +slug: '/docs/components/os-gmlock' +title: 'Memory Lock' +sidebar_position: 2 +hide_title: true +keywords: [memory lock, dynamic mutex lock, GoFrame, gmlock, concurrency safety, TryLock, Remove method, dynamic creation of mutex locks, GoFrame framework, lock management] +description: "The memory lock module provides dynamic mutex lock functionality based on the GoFrame framework, supporting concurrent safety and the TryLock feature by dynamically generating locks for given key names. Methods provided by GoFrame can be conveniently applied in scenarios requiring dynamic creation of a large number of mutex locks, such as effectively managing locks in multi-goroutine concurrent processing to ensure safe access to resources." +--- + +The memory lock module, also known as the `dynamic mutex lock` module, supports `dynamically generating mutex locks` based on given key names, ensuring concurrency safety and supporting the `Try*Lock` feature. + +> In scenarios where a large number of dynamic mutex locks are maintained, please manually call the `Remove` method to delete mutex lock objects that are no longer in use. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gmlock" +``` + +**Usage scenarios**: Scenarios that require `dynamic creation of mutex locks`, or need to maintain `a large number of dynamic locks`; + +**Interface documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gmlock](https://pkg.go.dev/github.com/gogf/gf/v2/os/gmlock) + +```go +func Lock(key string) +func LockFunc(key string, f func()) +func RLock(key string) +func RLockFunc(key string, f func()) +func RUnlock(key string) +func Remove(key string) +func TryLock(key string) bool +func TryLockFunc(key string, f func()) bool +func TryRLock(key string) bool +func TryRLockFunc(key string, f func()) bool +func Unlock(key string) +type Locker + func New() *Locker + func (l *Locker) Clear() + func (l *Locker) Lock(key string) + func (l *Locker) LockFunc(key string, f func()) + func (l *Locker) RLock(key string) + func (l *Locker) RLockFunc(key string, f func()) + func (l *Locker) RUnlock(key string) + func (l *Locker) Remove(key string) + func (l *Locker) TryLock(key string) bool + func (l *Locker) TryLockFunc(key string, f func()) bool + func (l *Locker) TryRLock(key string) bool + func (l *Locker) TryRLockFunc(key string, f func()) bool + func (l *Locker) Unlock(key string) +``` + +### Example 1: Basic Usage + +```go +package main + +import ( + "time" + "sync" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gmlock" +) + +func main() { + key := "lock" + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + gmlock.Lock(key) + glog.Println(i) + time.Sleep(time.Second) + gmlock.Unlock(key) + wg.Done() + }(i) + } + wg.Wait() +} +``` + +In this example, it simulates opening `10` `goroutines` simultaneously, but at any one time only one `goroutine` can acquire the lock. The `goroutine` with the lock will execute for `1` second before exiting, allowing other `goroutines` to acquire the lock. + +After execution, the output is: + +```html +2018-10-15 23:57:28.295 9 +2018-10-15 23:57:29.296 0 +2018-10-15 23:57:30.296 1 +2018-10-15 23:57:31.296 2 +2018-10-15 23:57:32.296 3 +2018-10-15 23:57:33.297 4 +2018-10-15 23:57:34.297 5 +2018-10-15 23:57:35.297 6 +2018-10-15 23:57:36.298 7 +2018-10-15 23:57:37.298 8 +``` + +### Example 2: TryLock Non-blocking Lock + +The `TryLock` method returns a value, indicating whether the lock was successfully acquired. If successful, it returns `true`; if the lock is already acquired by another `goroutine`, it returns `false`. + +```go +package main + +import ( + "sync" + "github.com/gogf/gf/v2/os/glog" + "time" + "github.com/gogf/gf/v2/os/gmlock" +) + +func main() { + key := "lock" + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + if gmlock.TryLock(key) { + glog.Println(i) + time.Sleep(time.Second) + gmlock.Unlock(key) + } else { + glog.Println(false) + } + wg.Done() + }(i) + } + wg.Wait() +} +``` + +Similarly, in this example, only `1` `goroutine` can acquire the lock at the same time, and other `goroutines` will exit immediately if `TryLock` fails. + +After execution, the output is: + +```html +2018-10-16 00:01:59.172 9 +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.176 false +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" new file mode 100644 index 00000000000..d5f1ad08438 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" @@ -0,0 +1,378 @@ +--- +slug: '/docs/components/os-grpool' +title: 'Goroutine' +sidebar_position: 16 +hide_title: true +keywords: [GoFrame, GoFrame Framework, goroutine, coroutine pool, high concurrency, grpool, task queue, resource management, asynchronous execution, memory optimization] +description: "A lightweight coroutine management tool in Go, grpool, discussing its performance advantages and resource reuse under high concurrency. Through pooling technology, it manages a large number of goroutines to reduce memory usage and optimize global scheduling, suitable for scenarios requiring asynchronous tasks and high memory usage." +--- + +## Introduction + +While `goroutine` in Go is relatively lightweight compared to system threads (with an initial stack size of only `2KB` and supports dynamic expansion), threads started using languages such as `Java` or `C++` are generally kernel-mode threads occupying about `4MB` of memory. Assuming our server CPU has `4GB` of memory, it's clear that the total number of concurrent kernel-mode threads is limited to about `1024`. In contrast, the number of goroutines in Go can reach `4*1024*1024/2=2 million`. This illustrates why Go naturally supports high concurrency. + +## Pain Points + +### Resource Consumption of Coroutine Execution + +However, under high concurrency, the frequent creation and destruction of `goroutine` can be a performance bottleneck and create pressure on `GC`. Reusing `goroutine` to reduce the overhead of creation/destruction is the purpose of `grpool`, which pools `goroutine`. For example, for `1 million` tasks, creating and destroying `1 million` `goroutine` is required, but with `grpool`, only a few ten thousand `goroutine` might be needed to execute all tasks effectively. + +Tests show that goroutine pooling offers little improvement in execution efficiency for business logic (reducing execution time/CPU usage). It might even be slower than native goroutine execution because the scheduling of pooled goroutines is not as efficient as the underlying Go scheduler. However, due to the reuse design, memory usage decreases significantly with pooling. + +### Large Number of Coroutines Affect Global Scheduling + +Some business modules need to dynamically create coroutines to execute tasks, such as asynchronous collection tasks and metric calculation tasks. These tasks are not the core logic of the service but can lead to an explosion in coroutines in extreme cases, affecting the global scheduling of the Go engine and causing significant service execution delays. + +For example, an asynchronous collection task might execute every `5` seconds, creating `100` coroutines each time to collect data from different targets. If network delays occur, tasks from previous executions might not finish before new coroutines are created, potentially causing an explosion in coroutines and affecting global service execution. In such scenarios, pooling techniques can be used to execute tasks quantitatively. After a certain number of tasks accumulate, subsequent tasks should block. For example, if the maximum number of tasks in a pool is set to `10,000`, execution blocks when this limit is exceeded, but it does not delay global service execution. + +## Concept Introduction + +### `Pool` + +Goroutine pool used to manage several reusable `goroutine` resources. + +### `Job` + +Tasks waiting for execution added to the pool's task queue. A `Job` is a method of type `Func` and can only be obtained and executed by one `Worker`. `Func` is defined as: + +```go +type Func func(ctx context.Context) +``` + +### `Worker` + +A `goroutine` in the pool involved in task execution. A `Worker` can execute several `Job`s until there are no more `Job`s waiting in the queue. + +## Usage Introduction + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/grpool" +``` + +**Scenarios:** + +Managing a large number of asynchronous tasks, reusing asynchronous coroutines, and reducing memory usage. + +**Interface Documentation:** + +```go +func Add(ctx context.Context, f Func) error +func AddWithRecover(ctx context.Context, userFunc Func, recoverFunc RecoverFunc) error +func Jobs() int +func Size() int +func New(limit ...int) *Pool + func (p *Pool) Add(ctx context.Context, f Func) error + func (p *Pool) AddWithRecover(ctx context.Context, userFunc Func, recoverFunc RecoverFunc) error + func (p *Pool) Cap() int + func (p *Pool) Close() + func (p *Pool) IsClosed() bool + func (p *Pool) Jobs() int + func (p *Pool) Size() int +``` + +Create a `goroutine pool` object using `grpool.New`. The parameter `limit` is optional and used to limit the number of worker `goroutine` in the pool, with no limit by default. Tasks can be continuously added to the pool without restriction, but the number of worker `goroutine` can be limited. Use `Size()` to check the current number of worker `goroutine` and `Jobs()` to check the current number of pending tasks in the pool. + +For convenience, the `grpool` package provides a default `goroutine` pool, which does not limit the number of `goroutine`, and tasks can be added directly to the default pool via `grpool.Add`, with task parameters required to be functions/methods of type `func()`. + +The most common question about this module is how to pass parameters to tasks within `grpool` from the outside. Please see example 2 for details. + +## Usage Examples + +### Using the Default `goroutine` Pool, Restrict `100` `goroutine` to Execute `1000` Tasks + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +var ( + ctx = gctx.New() +) + +func job(ctx context.Context) { + time.Sleep(1 * time.Second) +} + +func main() { + pool := grpool.New(100) + for i := 0; i < 1000; i++ { + pool.Add(ctx, job) + } + fmt.Println("worker:", pool.Size()) + fmt.Println(" jobs:", pool.Jobs()) + gtimer.SetInterval(ctx, time.Second, func(ctx context.Context) { + fmt.Println("worker:", pool.Size()) + fmt.Println(" jobs:", pool.Jobs()) + fmt.Println() + }) + + select {} +} +``` + +The task function in this program performs `sleep 1 second`, which clearly demonstrates the function of limiting goroutine count. A `gtime.SetInterval` timer is used to print out the number of work `goroutine` and pending tasks every second. + +### Asynchronous Parameter Passing: A Common Mistake for Beginners + +> This example is not valid in go versions ≥ 1.22, as the loop variable trap no longer exists post go 1.22. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "sync" +) + +var ( + ctx = gctx.New() +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + grpool.Add(ctx, func(ctx context.Context) { + fmt.Println(i) + wg.Done() + }) + } + wg.Wait() +} +``` + +The goal of this code is to sequentially print numbers from 0-9, but the output is: + +```10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +``` + +Why does this happen? This behavior occurs both when using the `go` keyword and `grpool` for execution. The reason is that when the function is registered for asynchronous execution, it hasn't started executing (at registration time, only the memory address of the variable `i` is saved in the goroutine's stack). When it does start executing, it reads the variable `i`'s value, which by then has incremented to `10`. Knowing the reason, the solution is simple: pass the value of `i` at the time of registering for asynchronous execution; alternatively, assign the current value of `i` to a temporary variable and use this in the function instead of directly using `i`. + +Revised example code: + +**1)** Using the `go` keyword + +```go +package main + +import ( + "fmt" + "sync" +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(v int){ + fmt.Println(v) + wg.Done() + }(i) + } + wg.Wait() +} + +``` + +The output is: + +```0 +9 +3 +4 +5 +6 +7 +8 +1 +2 +``` + +Note, asynchronous execution doesn't guarantee the order of execution matches the function registration order; similarly in the following examples. + +**2)** Using a temporary variable + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "sync" +) + +var ( + ctx = gctx.New() +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + v := i + grpool.Add(ctx, func(ctx context.Context) { + fmt.Println(v) + wg.Done() + }) + } + wg.Wait() +} +``` + +The output is: + +```9 +0 +1 +2 +3 +4 +5 +6 +7 +8 +``` + +Here, when registering a task with `grpool`, the registration method is `func(ctx context.Context)`, so it cannot directly register the value of `i` during task registration (avoid passing business parameters via `ctx` if possible). Therefore, use a temporary variable to pass the current value of `i`. + +### Automatically Catch `goroutine` Errors: `AddWithRecover` + +`AddWithRecover` pushes a new task into the pool with the specified recovery function. If there is a `panic` during the execution of `userFunc`, the optional `Recovery Func` is called. If no `Recovery Func` is provided or it's set to nil, then `panic` from `userFunc` is ignored. The task is executed asynchronously. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "time" +) + +var ( + ctx = gctx.New() +) +func main() { + array := garray.NewArray(true) + grpool.AddWithRecover(ctx, func(ctx context.Context) { + array.Append(1) + array.Append(2) + panic(1) + }, func(err error) { + array.Append(1) + }) + grpool.AddWithRecover(ctx, func(ctx context.Context) { + panic(1) + array.Append(1) + }) + time.Sleep(500 * time.Millisecond) + fmt.Print(array.Len()) +} +``` + +### Performance Test: `grpool` vs Native `goroutine` + +**1)** `grpool` + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "github.com/gogf/gf/v2/os/gtime" + "sync" + "time" +) + +var ( + ctx = gctx.New() +) + +func main() { + start := gtime.TimestampMilli() + wg := sync.WaitGroup{} + for i := 0; i < 10000000; i++ { + wg.Add(1) + grpool.Add(ctx, func(ctx context.Context) { + time.Sleep(time.Millisecond) + wg.Done() + }) + } + wg.Wait() + fmt.Println(grpool.Size()) + fmt.Println("time spent:", gtime.TimestampMilli() - start) +} +``` + +**2)** `goroutine` + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" + "sync" + "time" +) + +func main() { + start := gtime.TimestampMilli() + wg := sync.WaitGroup{} + for i := 0; i < 10000000; i++ { + wg.Add(1) + go func() { + time.Sleep(time.Millisecond) + wg.Done() + }() + } + wg.Wait() + fmt.Println("time spent:", gtime.TimestampMilli() - start) +} +``` + +**3)** Comparison of Results + +The test results are the averages of three runs for both programs. + +```shell +grpool: + goroutine count: 847313 + memory spent: ~2.1 G + time spent: 37792 ms + +goroutine: + goroutine count: 1000W + memory spent: ~4.8 GB + time spent: 27085 ms +``` + +It's clear that after pooling, the number of goroutines and memory usage for executing the same number of tasks have reduced significantly. CPU time consumption is also reasonably acceptable. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" new file mode 100644 index 00000000000..935bd2a21af --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcmd' +title: 'Command' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Command Management, gcmd, Core Component, Command Line Tool, Development Framework, Command Execution, Code Management, Software Development] +description: "Use the command management component gcmd in the GoFrame framework for command line operations, including how to create and manage commands, execute commands, and configure command parameters. It is one of the core components of GoFrame, suitable for various development scenarios, improving development efficiency." +--- + +For details, please refer to the section: [Command](../../核心组件/命令管理/命令管理.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" new file mode 100644 index 00000000000..54374d4a7cd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/components/os-gcron' +title: 'Cron Job' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gcron,Scheduled Task,crontab,CRON Syntax,Task Management,Programming Interface,Framework Tutorial,Go Language] +description: "Usage of the gcron module in the GoFrame framework, providing crontab-like scheduled task management features, supporting second-level management. It introduces how to create, add, manage, and delete scheduled tasks, and emphasizes the impact of global timezone on task execution, suitable for developers needing to write scheduled tasks." +--- + +## Introduction + +The `gcron` module provides the implementation of scheduled tasks, supporting a configuration management style similar to `crontab`, and manages scheduled tasks with a minimum granularity of `seconds`. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gcron" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcron](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcron) + +**Brief Description**: + +1. The `New` method is used to create a custom scheduled task management object. +2. The `Add` method is used to add scheduled tasks, where: + - The `pattern` parameter uses `CRON syntax format` (for details, see the subsequent related description in this chapter). + - The `job` parameter is the method (address) that needs to be executed. + - The `name` is an optional parameter used to assign a **unique** name to the scheduled task. Note that if a task with the same name already exists, adding the scheduled task will fail. +3. The `AddSingleton` method is used to add singleton scheduled tasks, meaning only one copy of the task can run simultaneously (deduplication is performed in memory). +4. The `AddOnce` method is used to add scheduled tasks that run only once. When run once, the task automatically destroys itself. +5. The `AddTimes` method is used to add scheduled tasks that run a specified number of times. When run `times` times, the task automatically destroys itself. +6. The `Entries` method is used to obtain information on all currently registered scheduled tasks. +7. The `Remove` method is used to delete scheduled tasks by name (stop and delete). +8. The `Search` method is used to search scheduled tasks by name (returns the `*Entry` object pointer of the task). +9. The `Start` method is used to start a scheduled task (`Add` automatically starts the task). The `name` parameter can be specified to start the task. +10. The `Stop` method is used to stop a scheduled task (`Remove` stops and deletes). The `name` parameter can be specified to stop the task. +11. The `Close` method is used to close the custom scheduled task management object. + +## Notes + +- Influence of Global Timezone: Scheduled tasks strictly depend on time calculation, so the global timezone of the process greatly affects scheduled task execution. When adding scheduled tasks, pay attention to the global timezone settings of the current process. If no global timezone is set, the system timezone is used by default. For more information on timezone settings, please refer to: [Time - Time Zone](../时间管理-gtime/时间管理-时区设置.md) + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" new file mode 100644 index 00000000000..2f981a802f6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" @@ -0,0 +1,22 @@ +--- +slug: '/docs/components/os-gcron-differ-with-gtimer' +title: 'Cron Job' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Scheduled Task,gcron,gtimer,Performance Module,crontab,Time Interval,TCP Communication,Game Development] +description: "The difference between the scheduled task module gcron and the timer module gtimer in the GoFrame framework. gtimer is a high-performance module suitable for various scheduling scenarios, including TCP communication and game development. gcron supports crontab syntax, built on gtimer, providing users with a convenient way to manage scheduled tasks." +--- + +## Difference between `gcron` and `gtimer` + +Differences between [Cron Job](定时任务-gcron.md) and [Timer](../定时器-gtimer/定时器-gtimer.md): + +- `gtimer` is a high-performance module, a core framework module, serving as the foundation for building any scheduled task, with method operation times measured in `nanoseconds`. +- `gtimer` is applicable in any scheduled task scenario, such as TCP communication, game development, etc. +- `gcron` supports the classic `crontab` syntax for scheduled tasks, with a minimum time setting interval of `seconds`. +- `gcron` is implemented based on `gtimer`. + +| Similar Module | Description | Performance | Linux-like Crontab Pattern | Underlying Implementation | +| --- | --- | --- | --- | --- | +| [Cron Job](定时任务-gcron.md) | Scheduled task.
    Higher-level encapsulation, time scale in natural seconds. | General | Supported | Based on `gtimer` | +| [Timer](../定时器-gtimer/定时器-gtimer.md) | Timer.
    Low-level component, time scale in terms of time slots (customizable). | Efficient | Not supported | Custom implementation based on `PriorityQueue` data structure | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..53180383581 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,420 @@ +--- +slug: '/docs/components/os-gcron-example' +title: 'Cron Job - Usage' +sidebar_position: 1 +hide_title: true +keywords: [cron jobs, GoFrame, GoFrame framework, gcron, singleton cron job, one-time cron job, specified times job, job search, job stop, job remove] +description: "Manage cron jobs using gcron in the GoFrame framework. Learn how to add, start, stop, remove, and search cron jobs. Also covers advanced features like singleton cron jobs, one-time cron jobs, and running specified times jobs. These features help developers efficiently manage and debug in-app cron jobs, enhancing application performance and reliability." +--- + +## Basic Usage + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + ) + _, err = gcron.Add(ctx, "* * * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "Every second") + }, "MySecondCronJob") + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "0 30 * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour on the half hour") + }) + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "@hourly", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour") + }) + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "@every 1h30m", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour thirty") + }) + if err != nil { + panic(err) + } + + g.Dump(gcron.Entries()) + + time.Sleep(3 * time.Second) + + g.Log().Print(ctx, `stop cronjob "MySecondCronJob"`) + gcron.Stop("MySecondCronJob") + + time.Sleep(3 * time.Second) + + g.Log().Print(ctx, `start cronjob "MySecondCronJob"`) + gcron.Start("MySecondCronJob") + + time.Sleep(3 * time.Second) +} +``` + +After execution, the output is: + +```text +[ + { + Name: "MySecondCronJob", + Job: 0x14077e0, + Time: "2021-11-14 12:13:53.445132 +0800 CST m=+0.006167069", + }, + { + Name: "cron-1", + Job: 0x14078a0, + Time: "2021-11-14 12:13:53.44515 +0800 CST m=+0.006185688", + }, + { + Name: "cron-2", + Job: 0x1407960, + Time: "2021-11-14 12:13:53.445161 +0800 CST m=+0.006196483", + }, + { + Name: "cron-3", + Job: 0x1407a20, + Time: "2021-11-14 12:13:53.445218 +0800 CST m=+0.006252937", + }, +] +2021-11-14 12:13:54.442 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:55.441 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:56.440 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:56.445 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} stop cronjob "MySecondCronJob" +2021-11-14 12:13:59.445 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} start cronjob "MySecondCronJob" +2021-11-14 12:14:00.443 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:14:01.442 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:14:02.443 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +``` + +## `AddSingleton` Add Singleton Cron Job + +A singleton cron job is one that only allows one instance of the job to run at any time. If a second instance of the job is triggered while the first is still running, it will exit without executing and wait for the next scheduled trigger. Use `AddSingleton` to add singleton cron jobs. + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + ) + _, err = gcron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "doing") + time.Sleep(2 * time.Second) + }) + if err != nil { + panic(err) + } + select {} +} +``` + +After execution, the output is: + +```text +2021-11-14 12:16:54.073 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:16:57.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:00.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:03.071 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:06.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:09.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +... +``` + +## `AddOnce` Add One-Time Cron Job + +A one-time cron job is added using the `AddOnce` method, which runs just once and then automatically destroys itself. The `Size` method allows you to check its status. The related methods are: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddOnce(ctx, "@every 2s", func(ctx context.Context) { + array.Append(1) + }) + fmt.Println(cron.Size(),array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(),array.Len()) + +} +``` + +After execution, the output is: + +```text +1 0 +0 1 +``` + +## `AddTimes` Add Specified Number of Cron Jobs + +The `AddTimes` method adds a cron job that runs a specified number of times. Once it completes the `times` count, the job auto-destructs. The `Size` method shows its status. Related methods: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 2,func(ctx context.Context) { + array.Append(1) + }) + fmt.Println(cron.Size(), array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(), array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(), array.Len()) +} +``` + +After execution, the output is: + +```text +1 0 +1 1 +0 2 +``` + +## `Entries` Get Registered Cron Job List + +The `Entries` method fetches information on all registered cron jobs and returns them as a slice (sorted by registration time `asc`). Related methods: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 1s", 2,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 1s",func(ctx context.Context) { + array.Append(1) + },"cron2") + entries := cron.Entries() + for k, v := range entries{ + fmt.Println(k,v.Name,v.Time) + + } + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len()) + +} +``` + +After execution, the output is: + +```text +0 cron2 2022-02-09 10:11:47.2421345 +0800 CST m=+0.159116501 +1 cron1 2022-02-09 10:11:47.2421345 +0800 CST m=+0.159116501 +3 +``` + +## `Search` Search for a Cron Job + +The `Search` function returns a scheduled task with a specified "name" (returning a `*Entry` pointer). If not found, it returns nil. Related methods: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 1s", 2,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 1s",func(ctx context.Context) { + array.Append(1) + },"cron2") + search := cron.Search("cron2") + + g.Log().Print(ctx, search) + + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len()) + + // Output: + // 3 +} + +``` + +After execution, the output is: + +```text +2022-02-09 10:52:30.011 {18a909957cfed11680c1b145da1ef096} {"Name":"cron2","Time":"2022-02-09T10:52:29.9972842+08:00"} +``` + +## `Stop` Pause a Cron Job + +The `Stop` method stops a cron job (`Stop` will pause but not delete). You can specify the task name with the `name` parameter. If `name` is not specified, it will stop the entire `cron`. Related methods: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 1,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + fmt.Println(array.Len(),cron.Size()) + cron.Stop("cron2") + fmt.Println(array.Len(),cron.Size()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + + // Output: + // 1 + // 1 +} +``` + +After execution, the output is: + +```text +0 2 +0 2 +1 1 +``` + +:::info +The `Stop` method marks a cron job as paused and returns immediately without waiting for the current execution to end. In some scenarios, you might need to wait for the current execution to finish before exiting. Starting from version `v2.8`, the `StopGracefully` method has been introduced to gracefully pause cron jobs. This method blocks until the current execution completes before returning. +::: + +## `Remove` Stop and Delete a Job + +The `Remove` method deletes a cron job based on name `name` (stopping and deleting it). Related methods: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 1,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + fmt.Println(array.Len(),cron.Size()) + cron.Remove("cron2") + fmt.Println(array.Len(),cron.Size()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + // Output: + // 0 2 + // 0 1 + // 1 0 +} +``` + +After execution, the output is: + +```text +0 2 +0 1 +1 0 +``` + +## `Start` Start a Cron Job + +The `Start` method initiates a cron job (automatically initiated after `Add`). You can specify the task name with the `name` parameter. If `name` is not specified, it will start the entire `cron`. Related methods: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + cron.Stop("cron2") + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + cron.Start("cron2") + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + + // Output: + // 0 1 + // 1 0 +} +``` + +After execution, the output is: + +```text +0 1 +1 0 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..254766e7497 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/components/os-gcron-logging' +title: 'Cron Job - Logging' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gcron, Log Management, Scheduled Task, Log Component, Log Output, Log Level, GoFrame Log, glog] +description: "Log management in the gcron component of the GoFrame framework. gcron supports setting log output files and levels, and by default, logs at the error level. Users can leverage all the features of the logging component through the GoFrame framework. The article provides Go code examples showing how to set and use gcron's logging feature." +--- + +`gcron` supports logging functionality, allowing the setting of log output files and levels. By default, it only outputs logs at the `LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT` error levels (including scheduled task exceptions), with runtime logs recorded at the `LEVEL_DEBUG` level, which are not recorded by default. The `gcron` component uses the unified logging component of the `goframe` framework, allowing reuse of all the logging component features. Relevant methods: + +```go +func SetLogger(logger glog.ILogger) +func GetLogger() glog.ILogger +``` +:::tip +For logging component features, please refer to the [Logging](../../../核心组件/日志组件/日志组件.md) section. +::: +Usage example: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + logger = glog.New() + ) + logger.SetLevel(glog.LEVEL_ALL) + gcron.SetLogger(logger) + _, err = gcron.Add(ctx, "* * * * * ?", func(ctx context.Context) { + g.Log().Info(ctx, "test") + }) + if err != nil { + panic(err) + } + time.Sleep(3 * time.Second) +} +``` + +After execution, the terminal output is: + +![](/markdown/673cee2f61375b3979a03c30934fd8d8.png) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 00000000000..2a038ffbe5d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,110 @@ +--- +slug: '/docs/components/os-gcron-pattern' +title: 'Cron Job - Expressions' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Scheduled Tasks, cron Expressions, Linux Crontab, Time Scheduling, Special Characters, Time Interval, Expression Examples, Predefined Formats, Second-Level Scheduling] +description: "cron expressions in the GoFrame framework and their usage tips. The cron expression consists of six fields, enabling time scheduling from seconds to weeks. It explains the significance of special characters and their application in expressions, making task scheduling more flexible and reliable through various predefined formats and interval configurations." +--- + +## Introduction + +A `cron expression` represents a set of times, using `6` space-separated fields. + +``` +Seconds Minutes Hours Day Month Week +``` + +> i.e., `秒 分 时 日 月 周` + +The meaning of each field is as follows: + +``` +Field name | Allowed values | Allowed special characters +---------- | -------------- | -------------------------- +Seconds | 0-59 | * / , - # +Minutes | 0-59 | * / , - +Hours | 0-23 | * / , - +Day | 1-31 | * / , - ? +Month | 1-12 or JAN-DEC | * / , - +Week | 0-6 or SUN-SAT | * / , - ? +``` + +:::warning +Month and week field values are case-insensitive. For example, `SUN`, `Sun`, and `sun` are equally accepted. +::: + +## Special Characters + +#### Asterisk (`*`) + +An asterisk means that the `cron` expression will match all values. For example, using an asterisk in the fifth field (`Month`) means every month. + +#### Slash (`/`) + +Slash is used to describe increments of ranges. For instance, using `3-59/15` in the second field means executing every `15` minutes starting from the `3rd` minute to the `59th` minute of every hour. + +#### Comma (`,`) + +Comma is used to separate items of a list. For example, using `MON,WED,FRI` in the fifth field will indicate execution on Monday, Wednesday, and Friday of each week. + +#### Hyphen (`-`) + +Hyphen is used to define ranges. For example, using `9-17` in the third field indicates from `9` AM to `5` PM (inclusive) every day. + +#### Ignore Sign (`#`) + +The ignore sign means the `cron` expression will bypass the use of this field. Currently, only the seconds field supports this symbol, for seamlessly converting `6` segment `cron pattern` to `5` segment `linux crontab pattern`. + +#### Question Mark (`?`) + +A `question mark` can be used instead of `*` to leave the `Day` or `Week` field blank. + +#### Predefined Formats + +You can use several predefined times instead of a `cron` expression. + +``` +Entry | Description | Equivalent To +----- | ----------- | ------------- +@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * +@monthly | Run once a month, midnight, first of month | 0 0 0 1 * * +@weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0 +@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * +@hourly | Run once an hour, beginning of hour | 0 0 * * * * +``` + +#### Intervals + +You can also define a task to run at a fixed interval, starting when added. This is supported by formatting the `cron` specification as follows: + +``` +@every +``` + +where `duration` is a string accepted by `time.ParseDuration` ([http://golang.org/pkg/time/#ParseDuration](http://golang.org/pkg/time/#ParseDuration)). + +For instance, `@every 1h30m10s` would mean executing every `1 hour, 30 minutes, and 10 seconds` after the task is added. + +:::warning +Intervals do not consider the execution overhead time of tasks. For example, if a task takes `3` minutes to complete and is scheduled to run every `5` minutes, there will only be `2` minutes of idle time between each task. +::: + +## Expression Examples + +| Expression Example | Description | +| --- | --- | +| `* * * * * *` | Execute every second | +| `# * * * * *` | Execute every minute, with at least `60` seconds between each execution | +| `2 * * * * *` | Execute at the `2nd` second of every minute | +| `*/5 * * * * *` | Execute every `5` seconds | +| `# */30 * * * *` | Execute every `30` minutes | +| `# 0 2 * * *` | Execute daily at `2` AM | +| `# */30 9-18 * * *` | Execute every `30` minutes from `9` AM to `6` PM daily | +| `# 0 9 * * MON,FRI` | Execute once at `9` AM on `Monday` and `Friday` | + +## Notes 🔥 + +All programming language-level `6` segment `cron pattern` designs, practically, have certain design flaws due to the inaccuracy of underlying timers. Since `cron pattern` is accurate to the second, when **delay reaches the second level**, tasks may be lost. Moreover, if the `golang` engine scheduling is relatively slow, such delays can easily reach the second level, leading to logical issues within the program. + +Considering that most scenarios do not require such precise control of scheduling at the second level, starting from framework version `v2.7`, we offer an ignore symbol `#` for the seconds field to convert `6` segment `cron pattern` into `5` segment `linux crontab pattern`, making it more robust. If the scenario requires second-level granularity scheduling, consider using the `gtimer` timer; however, bear in mind that no timer is entirely accurate and you cannot completely rely on the underlying system time. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" new file mode 100644 index 00000000000..5f17547ab50 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" @@ -0,0 +1,71 @@ +--- +slug: '/docs/components/os-gtimer' +title: 'Timer' +sidebar_position: 1 +hide_title: true +keywords: [Timer, Concurrent Safe, High Performance, GoFrame, Task Scheduling, Delayed Tasks, Timeout Control, Frequency Control, Task Management, Singleton Mode] +description: "gtimer is a concurrent safe and high-performance timer suitable for scenarios with a large number of scheduled tasks and delayed tasks. It supports timeout control and frequency control. gtimer provides various task management methods, including adding singleton and run-once tasks, and allows customization of timer parameters." +--- + +## Introduction + +`gtimer` is a concurrent safe and high-performance timer, similar to `Java`'s `Timer`. `gtimer` uses a **Priority Queue** to implement its core functionality. + +**Use Cases**: + +Any scenario involving scheduled tasks, scenarios with a large number of scheduled/delayed tasks, business scenarios that require timeout control/frequency control, or scenarios where the accuracy of the scheduled time is not strictly critical. + +**How to Use:** + +```go +import "github.com/gogf/gf/v2/os/gtimer" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtimer](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtimer) + +**Brief Explanation:** + +1. The `New` method is used to create a custom task timer object and can accept `TimerOptions` parameters upon creation, including: + - `Interval` to specify the minimum `tick` time interval for the timer. + - `Quick` to specify whether the timer should execute once upon start (default is `false`). +2. The `Add` method is used to add scheduled tasks, where: + - The `interval` parameter specifies the execution time interval of the method. + - The `job` parameter is the task method that needs to be executed. +3. The `AddEntry` method adds scheduled tasks, supporting more parameter controls. +4. The `AddSingleton` method is used to add **singleton** scheduled tasks, meaning **only one task can be running at the same time**. +5. The `AddOnce` method is used to add a task that runs only once and is automatically destroyed after running once. +6. The `AddTimes` method is used to add a task that runs a specified number of times and is automatically destroyed after running `times` times. +7. The `Search` method is used to search for scheduled tasks by name (returns the `*Entry` object pointer of the task). +8. The `Start` method is used to start the timer (the timer is automatically started when created using `New`). +9. The `Stop` method is used to stop the timer. +10. The `Close` method is used to close the timer. + +## Default Timer + +In most scenarios, the default timer can be used. When using the default timer of `gtimer`, the default detection interval is `100ms`, so the theoretical time interval error range is `0~100ms`. You can modify the parameters of the default timer using the following two methods: + +1. Use startup parameters + - `gf.gtimer.interval=50`: Change the default time scale to `50ms` +2. Use environment variables + - `GF_GTIMER_INTERVAL=50` +:::warning +It should be noted that the shorter the default detection interval of the timer, the greater the CPU usage. +::: +## Precautions🔥 + +1. Since modern computers use software-implemented timers, **all timers have inaccuracies**. They will not be completely precise and may delay or even execute ahead of time, but they will not fail to execute. This is particularly noticeable in systems with large time intervals or high concurrency/high load. See the reference link: [https://github.com/golang/go/issues/14410](https://github.com/golang/go/issues/14410) +2. Since inaccuracies are inevitable, any timer implementation (not just framework timers, but also standard library timers) **will not use system time**. Instead, it uses a fixed `tick` interval. Do not use system time to judge intervals in the logic of timer tasks, as this judgment is meaningless. +3. Assuming no inaccuracies, the time interval does not consider the execution time of the task. For example, if a job takes `3` minutes to complete and is scheduled to run every `5` minutes, there will be only `2` minutes of idle time between each task. +4. It should be noted that **the execution time of singleton mode** scheduled tasks will affect the **start time** of the task's next execution. For example, if a task runs every `1` second and takes `1` second to execute, then after starting at **second 1**, the next task will start executing at **second 3** since a running check in between found the current task still running and thus exited waiting for the next execution check. + +## Difference Between Timer and `gcron` + +Please refer to the section [Cron Job](../定时任务-gcron/定时任务-gcron与gtimer.md) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..c12cda1fa49 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,221 @@ +--- +slug: '/docs/components/os-gtimer-example' +title: 'Timer - Usage' +sidebar_position: 0 +hide_title: true +keywords: [Timer, Basic Usage, Singleton Task, Delayed Task, SetTimeout, SetInterval, Task Exit, GoFrame, GoFrame Framework, gtimer] +description: "Using the timer component in the GoFrame framework, including basic usage, singleton tasks, delayed tasks, and scheduled operations through SetTimeout and SetInterval methods. Detailed explanation of the implementation and execution results of these scheduled tasks, and demonstration of how to use the Exit method to exit timed tasks." +--- + +## Basic Example + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + now = time.Now() + ) + gtimer.AddTimes(ctx, time.Second, 10, func(ctx context.Context) { + fmt.Println(gtime.Now(), time.Duration(time.Now().UnixNano()-now.UnixNano())) + now = time.Now() + }) + + select {} +} +``` + +After execution, the output is: + +```html +2021-05-27 13:28:19 1.004516s +2021-05-27 13:28:20 997.262ms +2021-05-27 13:28:21 999.972ms +2021-05-27 13:28:22 1.00112s +2021-05-27 13:28:23 998.773ms +2021-05-27 13:28:24 999.957ms +2021-05-27 13:28:25 1.002468s +2021-05-27 13:28:26 997.468ms +2021-05-27 13:28:27 999.981ms +2021-05-27 13:28:28 1.002504s +``` + +## Singleton Task + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + interval = time.Second + ) + + gtimer.AddSingleton(ctx, interval, func(ctx context.Context) { + glog.Print(ctx, "doing") + time.Sleep(5 * time.Second) + }) + + select {} +} +``` + +After execution, the output is: + +```html +2021-11-14 11:50:42.192 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:50:48.190 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:50:54.192 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:51:00.189 {189cwi9mo40cfp73guzhugo100tnuedg} doing +... +``` + +## Delayed Task + +Delayed tasks refer to scheduled tasks that take effect after a specified time. We can create delayed tasks using `DelayAdd*` related methods. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + delay = time.Second + interval = time.Second + ) + fmt.Println("Start:", gtime.Now()) + gtimer.DelayAdd( + ctx, + delay, + interval, + func(ctx context.Context) { + fmt.Println("Running:", gtime.Now()) + }, + ) + select {} +} +``` + +After execution, the terminal output is: + +``` +Start: 2021-05-27 13:26:02 +Running: 2021-05-27 13:26:04 +Running: 2021-05-27 13:26:05 +Running: 2021-05-27 13:26:06 +Running: 2021-05-27 13:26:07 +Running: 2021-05-27 13:26:08 +Running: 2021-05-27 13:26:09 +Running: 2021-05-27 13:26:10 +Running: 2021-05-27 13:26:11 +... +``` + +## `SetTimeout` and `SetInterval` + +These two methods are common scheduling methods from `JavaScript`. `SetTimeout` is used to create a scheduled task that executes only once. However, you can achieve infinite interval execution through recursive calls to `SetTimeout`. `SetInterval` is used to create scheduled tasks that execute at intervals without exiting. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + timeout = time.Second + interval = time.Second + ) + gtimer.SetTimeout(ctx, timeout, func(ctx context.Context) { + fmt.Println("SetTimeout:", gtime.Now()) + }) + gtimer.SetInterval(ctx, interval, func(ctx context.Context) { + fmt.Println("SetInterval:", gtime.Now()) + }) + select {} +} +``` + +After execution, the terminal output is: + +``` +SetInterval: 2021-05-27 13:20:50 +SetTimeout: 2021-05-27 13:20:50 +SetInterval: 2021-05-27 13:20:51 +SetInterval: 2021-05-27 13:20:52 +SetInterval: 2021-05-27 13:20:53 +SetInterval: 2021-05-27 13:20:54 +SetInterval: 2021-05-27 13:20:55 +SetInterval: 2021-05-27 13:20:56 +SetInterval: 2021-05-27 13:20:57 +SetInterval: 2021-05-27 13:20:58 +... +``` + +## `Exit` Method to Exit + +We can use the `Exit` method in scheduled tasks to forcefully exit the continuation of the task, which will then be removed from the scheduler. + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + gtimer.SetInterval(ctx, time.Second, func(ctx context.Context) { + fmt.Println("exit:", gtime.Now()) + gtimer.Exit() + }) + select {} +} +``` + +After execution, the terminal output is: + +``` +exit: 2021-05-27 13:31:24 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..14d6ed42663 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/os-gtimer-benchmark' +title: 'Timer - Performance' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Timer, Performance Test, Benchmark, Go, gtimer, linux, amd64, github] +description: "Detailed information on timer performance testing using the GoFrame framework in a Linux environment. By comparing Benchmark_Add and Benchmark_StartStop, we can more clearly understand the efficiency and resource allocation of the timer under different operations. The test results demonstrate the efficient performance metrics of the Go language when executing timer operations, providing better references for developers using GoFrame." +--- + +## Performance Test + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/os/gtimer +Benchmark_Add-12 4048776 291.9 ns/op 249 B/op 6 allocs/op +Benchmark_StartStop-12 100000000 10.96 ns/op 0 B/op 0 allocs/op +PASS +ok command-line-arguments 6.602s +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" new file mode 100644 index 00000000000..c2c0a00a81c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" @@ -0,0 +1,139 @@ +--- +slug: '/docs/components/os-gstructs' +title: 'Object Information' +sidebar_position: 18 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gstructs, Structure Information, Field Retrieval, TagMapName, Fields Method, GoFrame Framework, Middleware Writing, Underlying Components] +description: "The gstructs component is a low-level utility in the GoFrame framework used for obtaining structure information. It is mainly used in framework, basic library, and middleware writing, supporting the Fields method to get structure field information and the TagMapName method to retrieve fields through tags. It is suitable for developers to use this component for field operations and retrieval when building Go applications." +--- + +## Introduction + +The `gstructs` component is used to conveniently obtain information about structures. + +This is a low-level component, rarely used in general business, but used in framework, basic library, and middleware writing. + +Usage: + +```go +import "github.com/gogf/gf/v2/os/gstructs" +``` + +API Documentation: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gstructs](https://pkg.go.dev/github.com/gogf/gf/v2/os/gstructs) + +## Common Methods + +### `Fields` + +- Description: `Fields` returns the fields of the `Pointer` attribute of the input parameter `in` in the form of a `Field` slice. + +- Format: + +```go +Fields(in FieldsInput) ([]Field, error) +``` + +- Example: + +```go +func main() { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user *User + fields, _ := gstructs.Fields(gstructs.FieldsInput{ + Pointer: user, + RecursiveOption: 0, + }) + + g.Dump(fields) +} + +// Output: +[ + { + Value: "", + Field: { + Name: "Id", + PkgPath: "", + Type: "int", + Tag: "", + Offset: 0, + Index: [ + 0, + ], + Anonymous: false, + }, + TagValue: "", + }, + { + Value: {}, + Field: { + Name: "Name", + PkgPath: "", + Type: "string", + Tag: "params:\"name\"", + Offset: 8, + Index: [ + 1, + ], + Anonymous: false, + }, + TagValue: "", + }, + { + Value: {}, + Field: { + Name: "Pass", + PkgPath: "", + Type: "string", + Tag: "my-tag1:\"pass1\" my-tag2:\"pass2\" params:\"pass\"", + Offset: 24, + Index: [ + 2, + ], + Anonymous: false, + }, + TagValue: "", + }, +] +``` + +### `TagMapName` + +- Description: `TagMapName` retrieves `tag` from the parameter `pointer` and returns it in the form of `map[string]string`. + +- Note: + - The type of parameter `pointer` should be `struct/*struct`. + - Only exported fields (fields with an uppercase initial) will be returned. +- Format: + +```go +TagMapName(pointer interface{}, priority []string) (map[string]string, error) +``` + +- Example: + +```go +func main() { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user User + m, _ := gstructs.TagMapName(user, []string{"params"}) + + g.Dump(m) +} + +// Output: +{ + "name": "Name", + "pass": "Pass", +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" new file mode 100644 index 00000000000..66f9d2f0a32 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" @@ -0,0 +1,32 @@ +--- +slug: '/docs/components/os-gfsnotify' +title: 'File Watching' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame, GoFrame framework, gfsnotify, file watching, Go framework, file operations, watching module, Go development, system watching, directory watching] +description: "Use the gfsnotify module in the GoFrame framework to implement the watching of files and directories. gfsnotify can detect changes such as addition, deletion, modification, and renaming of files, and provides convenient interface functions like Add and Remove for watching and unwatching operations. Applicable to the inotify mechanism of *nix systems, and usage may be limited by system kernel parameters. Through example code, it demonstrates how to set, remove watching, and perform file operation watching." +--- + +## Introduction + +`gfsnotify` can watch changes in specified files/directories, such as additions, deletions, modifications, and renaming of files. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/os/gfsnotify" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gfsnotify](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfsnotify) + +It is recommended to use the `Add` and `Remove` module methods provided by the `gfsnotify` module for adding and canceling watching. The reasons for this recommendation are explained in the following sections. + +Additionally, you can also create a watching management object using the `New` method and then perform watching management. When adding watching, you need to provide a callback function that triggers during watching, with the parameter type being a `*gfsnotify.Event` object pointer. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" new file mode 100644 index 00000000000..986ca1412fc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" @@ -0,0 +1,61 @@ +--- +slug: '/docs/components/os-gfsnotify-add' +title: 'File Watching - Add' +sidebar_position: 0 +hide_title: true +keywords: [Add Watch, File Watching, GoFrame Framework, gfsnotify, Recursive Watching, File Modification, Directory Watching, File Event, File Change, Watching Options] +description: "Define and implement file watching functionality, using the gfsnotify library in the GoFrame framework to watch file creation, writing, deletion, renaming, and permission modification events in a specified directory. Supports recursive watching, automatically detecting changes in files within directories and subdirectories, with flexible watching options and real-time output of file event information related to the directory." +--- + +## Add Watch + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" +) + +func main() { + var ( + path = "/home/john/temp" + ctx = context.Background() + logger = g.Log() + callback = func(event *gfsnotify.Event) { + if event.IsCreate() { + logger.Debug(ctx, "Create file: ", event.Path) + } + if event.IsWrite() { + logger.Debug(ctx, "Write file: ", event.Path) + } + if event.IsRemove() { + logger.Debug(ctx, "Delete file: ", event.Path) + } + if event.IsRename() { + logger.Debug(ctx, "Rename file: ", event.Path) + } + if event.IsChmod() { + logger.Debug(ctx, "Change permissions: ", event.Path) + } + logger.Debug(ctx, event) + } + ) + _, err := gfsnotify.Add(path, callback, gfsnotify.WatchOption{}) + if err != nil { + logger.Fatal(ctx, err) + } else { + select {} + } +} +``` + +The `/home/john` parameter is a directory. When we create/delete/modify files in the `/home/john` directory, `gfsnotify` detects the file modifications and outputs the corresponding event information. + +## Recursive Watching + +We can use `gfsnotify.WatchOption` to set some options for watching, such as whether to enable recursive watching. By default, the `Add` method performs recursive watching, meaning that changes to files within the directory (including those in subdirectories) will also trigger the file watching callback. + +If we create new directories under the watched directory and continue to create new directories or files within them, and so on, the newly created directories or files will also be automatically watched. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" new file mode 100644 index 00000000000..93332b7df49 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" @@ -0,0 +1,97 @@ +--- +slug: '/docs/components/os-gfsnotify-remove' +title: 'File Watching - Remove' +sidebar_position: 1 +hide_title: true +keywords: [File Watching, Remove Watching, GoFrame, Remove Method, RemoveCallback, File Callback, Directory Watching, gfsnotify, Callback Removal, GoFrame Framework] +description: "This document details how to use the Remove method and the RemoveCallback method in the GoFrame framework to remove watching callback functions for files and directories. It uses example code to illustrate how to add and remove watching callbacks, thereby improving system resource efficiency and ensuring the flexibility and controllability of file operation watching." +--- + +To remove watching, we can use the `Remove` method, which will remove watching for the entire file/directory. + +When there are multiple watching callbacks for the same file/directory, we can remove a specified callback using the `RemoveCallback` method. The `callbackId` parameter is the unique ID returned by the `Callback` object when adding watching. + +## Example 1 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + ) + c1, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback1") + }) + if err != nil { + panic(err) + } + c2, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback2") + }) + if err != nil { + panic(err) + } + // Remove the registration of callback function c1 after 5 seconds, leaving only c2 + gtimer.SetTimeout(ctx, 5*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(c1.Id) + logger.Debug(ctx, "remove callback c1", err) + }) + // Remove the registration of callback function c2 after 10 seconds, all callbacks are removed, and no more log messages are output + gtimer.SetTimeout(ctx, 10*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(c2.Id) + logger.Debug(ctx, "remove callback c2", err) + }) + + select {} +} +``` + +## Example 2 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + callback = func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback") + } + ) + cb, err := gfsnotify.Add("/home/john/temp", callback) + if err != nil { + panic(err) + } + + // During this period create files, directories, modify files, delete files + + // Remove the callback registration after 20 seconds, all callbacks are removed and no more log messages are output + gtimer.SetTimeout(ctx, 20*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(cb.Id) + logger.Debug(ctx, "remove callback", err) + }) + + select {} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..f1442395ba8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" @@ -0,0 +1,38 @@ +--- +slug: '/docs/components/os-gfsnotify-system-variables' +title: 'File Watching - System Vars' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, gfsnotify, file watching, inotify, system parameters, linux, watching instance, user instance limit, file queue size] +description: "In the Linux system, the gfsnotify module implements file and directory watching through the inotify feature, limited by system kernel parameters such as fs.inotify.max_user_instances and fs.inotify.max_user_watches. These parameters can be viewed and modified via command line to suit different watching needs." +--- + +## Impact of System Variables + +In the `linux` system, the `gfsnotify` module uses the system's `inotify` feature to implement file/directory watching. Therefore, this functionality is subject to the limitations of two kernel parameters: + +- `fs.inotify.max_user_instances`: Indicates the number of `inotify` watching instances that the current user can create, which is the number of `Watcher` objects created by the `gfsnotify.New` method. Each `Watcher` object corresponds to an `inotify` instance in the system. The default system quantity is: `128`. + +- `fs.inotify.max_user_watches`: Indicates the size of the watched file queue that an `inotify` instance can add. If you add watched files to the same `inotify` beyond this limit, it will fail, and there will be system error logs. The default system quantity is usually: `8192` (some systems may have a larger value). + + +## Viewing and Modifying + +Take `fs.inotify.max_user_instances` as an example, in the `linux` system, you can view the current value of `fs.inotify.max_user_instances` with the following command: +```bash +cat /proc/sys/fs/inotify/max_user_instances +``` + +If you need to modify this value, you can use the following command (for example, to change the value to `1024`): +```bash +sudo sysctl -w fs.inotify.max_user_instances=1024 +``` + +To permanently modify this value, you can add the following content to the `/etc/sysctl.conf` file: +```bash +fs.inotify.max_user_instances=1024 +``` +Then execute the following command to make the changes take effect: +```bash +sudo sysctl -p +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" new file mode 100644 index 00000000000..002a7931e1c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" @@ -0,0 +1,2498 @@ +--- +slug: '/docs/components/os-gfile' +title: 'File' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame, gfile, file management, caching mechanism, file operations, directory scanning, file copying, permission settings, path operations, content replacement] +description: "The gfile component provides rich file and directory operations for the GoFrame framework, including file content reading, caching mechanism, file copying and moving, directory scanning, and file permission settings. It supports flexible path operations and content replacement, optimizing file management and processing efficiency, making it an ideal library for developers performing file operations." +--- + +## Introduction + +The `gfile` file management component provides more comprehensive file/directory operation capabilities. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gfile" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile) +:::tip +The following list includes commonly used methods, and the documentation may lag behind new features in the code. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile) +::: +## Content Management + +### `GetContents` + +- Description: Reads the content of the specified file path and returns it as a string. +- Format: + +```go +func GetContents(path string) string +``` + +- Example: + +```go +func ExampleGetContents() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads and returns the file content as string. + // It returns empty string if it fails reading, for example, with permission or IO error. + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `GetContentsWithCache` + +- Description: Reads the file content with caching, allowing for cache timeout settings. The cache is automatically cleared when the file changes. +- Format: + +```go +func GetContentsWithCache(path string, duration ...time.Duration) string +``` + +- Example: + +```go +func ExampleGetContentsWithCache() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_cache") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads the file content with cache duration of one minute, + // which means it reads from cache after then without any IO operations within on minute. + fmt.Println(gfile.GetContentsWithCache(tempFile, time.Minute)) + + // write new contents will clear its cache + gfile.PutContents(tempFile, "new goframe example content") + + // There's some delay for cache clearing after file content change. + time.Sleep(time.Second * 1) + + // read contents + fmt.Println(gfile.GetContentsWithCache(tempFile)) + + // May Output: + // goframe example content + // new goframe example content +} +``` + + +### `GetBytesWithCache` + +- Description: Reads the file content with caching, allowing for cache timeout settings. The cache is automatically cleared when the file changes, and the content is returned as a byte slice. +- Format: + +```go +func GetBytesWithCache(path string, duration ...time.Duration) []byte +``` + +- Example: + +```go +func ExampleGetBytesWithCache() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_cache") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads the file content with cache duration of one minute, + // which means it reads from cache after then without any IO operations within on minute. + fmt.Println(gfile.GetBytesWithCache(tempFile, time.Minute)) + + // write new contents will clear its cache + gfile.PutContents(tempFile, "new goframe example content") + + // There's some delay for cache clearing after file content change. + time.Sleep(time.Second * 1) + + // read contents + fmt.Println(gfile.GetBytesWithCache(tempFile)) + + // Output: + // [103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] + // [110 101 119 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `GetBytes` + +- Description: Reads the content of the specified file path and returns it as a byte slice. +- Format: + +```go +func GetBytes(path string) []byte +``` + +- Example: + +```go +func ExampleGetBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads and returns the file content as []byte. + // It returns nil if it fails reading, for example, with permission or IO error. + fmt.Println(gfile.GetBytes(tempFile)) + + // Output: + // [103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `GetBytesTilChar` + +- Description: Reads the file content up to a specified character and returns the content as a byte slice. +- Format: + +```go +func GetBytesTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64) +``` + +- Example: + +```go +func ExampleGetBytesTilChar() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, _ := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, gfile.DefaultPermOpen) + + // GetBytesTilChar returns the contents of the file as []byte + // until the next specified byte `char` position. + char, i := gfile.GetBytesTilChar(f, 'f', 0) + fmt.Println(char) + fmt.Println(i) + + // Output: + // [103 111 102] + // 2 +} +``` + + +### `GetBytesByTwoOffsets` + +- Description: Reads the file content from the specified range and returns it as a byte slice. +- Format: + +```go +func GetBytesByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte +``` + +- Example: + +```go +func ExampleGetBytesByTwoOffsets() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, _ := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, gfile.DefaultPermOpen) + + // GetBytesTilChar returns the contents of the file as []byte + // until the next specified byte `char` position. + char := gfile.GetBytesByTwoOffsets(f, 0, 3) + fmt.Println(char) + + // Output: + // [103 111 102] +} +``` + + +### `PutContents` + +- Description: Writes string content to the specified file path. If the file does not exist, it will be created recursively. +- Format: + +```go +func putContents(path string, data []byte, flag int, perm os.FileMode) error +``` + +- Example: + +```go +func ExamplePutContents() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // It creates and puts content string into specifies file path. + // It automatically creates directory recursively if it does not exist. + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `PutBytes` + +- Description: Writes bytes to the specified file path. If the file does not exist, it will be created recursively. +- Format: + +```go +func PutBytes(path string, content []byte) error +``` + +- Example: + +```go +func ExamplePutBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutBytes(tempFile, []byte("goframe example content")) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `PutContentsAppend` + +- Description: Appends string content to the specified file. If the file does not exist, it will be created recursively. +- Format: + +```go +func PutContentsAppend(path string, content string) error +``` + +- Example: + +```go +func ExamplePutContentsAppend() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It creates and append content string into specifies file path. + // It automatically creates directory recursively if it does not exist. + gfile.PutContentsAppend(tempFile, " append content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example content append content +} +``` + + +### `PutBytesAppend` + +- Description: Appends byte content to the specified file. If the file does not exist, it will be created recursively. +- Format: + +```go +func PutBytesAppend(path string, content []byte) error +``` + +- Example: + +```go +func ExamplePutBytesAppend() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // write contents + gfile.PutBytesAppend(tempFile, []byte(" append")) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example content append +} +``` + + +### `GetNextCharOffset` + +- Description: Gets the index of the specified character from a certain offset in the file. +- Format: + +```go +func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 +``` + +- Example: + +```go +func ExampleGetNextCharOffset() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, err := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, DefaultPermOpen) + defer f.Close() + + // read contents + index := gfile.GetNextCharOffset(f, 'f', 0) + fmt.Println(index) + + // Output: + // 2 +} +``` + + +### `GetNextCharOffsetByPath` + +- Description: Gets the index of the specified character from a certain offset in the file. +- Format: + +```go +func GetNextCharOffsetByPath(path string, char byte, start int64) int64 +``` + +- Example: + +```go +func ExampleGetNextCharOffsetByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + index := gfile.GetNextCharOffsetByPath(tempFile, 'f', 0) + fmt.Println(index) + + // Output: + // 2 +} +``` + + +### `GetBytesTilCharByPath` + +- Description: Reads the file content up to a specified character and returns the content as a byte slice. +- Format: + +```go +func GetBytesTilCharByPath(path string, char byte, start int64) ([]byte, int64) +``` + +- Example: + +```go +func ExampleGetBytesTilCharByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetBytesTilCharByPath(tempFile, 'f', 0)) + + // Output: + // [103 111 102] 2 +} +``` + + +### `GetBytesByTwoOffsetsByPath` + +- Description: Reads the content of the specified file between two offsets and returns it as a byte slice. +- Format: + +```go +func GetBytesByTwoOffsetsByPath(path string, start int64, end int64) []byte +``` + +- Example: + +```go +func ExampleGetBytesByTwoOffsetsByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 7)) + + // Output: + // [103 111 102 114 97 109 101] +} +``` + + +### `ReadLines` + +- Description: Reads file content line by line as a string. +- Format: + +```go +func ReadLines(file string, callback func(text string) error) error +``` + +- Example: + +```go +func ExampleReadLines() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content") + + // read contents + gfile.ReadLines(tempFile, func(text string) error { + // Process each line + fmt.Println(text) + return nil + }) + + // Output: + // L1 goframe example content + // L2 goframe example content +} +``` + + +### `ReadLinesBytes` + +- Description: Reads file content line by line as bytes. +- Format: + +```go +func ReadLinesBytes(file string, callback func(bytes []byte) error) error +``` + +- Example: + +```go +func ExampleReadLinesBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content") + + // read contents + gfile.ReadLinesBytes(tempFile, func(bytes []byte) error { + // Process each line + fmt.Println(bytes) + return nil + }) + + // Output: + // [76 49 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] + // [76 50 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `Truncate` + +- Description: Truncates the file to the specified size. +- Note: If the given file path is a symlink, it will modify the source file. +- Format: + +```go +func Truncate(path string, size int) error +``` + +- Example: + +```go +func ExampleTruncate(){ + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Check whether the `path` size + stat, _ := gfile.Stat(path) + fmt.Println(stat.Size()) + + // Truncate file + gfile.Truncate(path, 0) + + // Check whether the `path` size + stat, _ = gfile.Stat(path) + fmt.Println(stat.Size()) + + // Output: + // 13 + // 0 +} +``` + + +## Content Replacement + +### `ReplaceFile` + +- Description: Replaces specified content in the file with new content. +- Format: + +```go +func ReplaceFile(search, replace, path string) error +``` + +- Example: + +```go +func ExampleReplaceFile() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content directly by file path. + gfile.ReplaceFile("content", "replace word", tempFile) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example replace word +} +``` + + +### `ReplaceFileFunc` + +- Description: Replaces the content of the specified file using a custom function. +- Format: + +```go +func ReplaceFileFunc(f func(path, content string) string, path string) error +``` + +- Example: + +```go +func ExampleReplaceFileFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example 123") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content directly by file path and callback function. + gfile.ReplaceFileFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempFile) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example 123 + // goframe example [num] +} +``` + + +### `ReplaceDir` + +- Description: Scans the specified directory and replaces the specified content in matching files with new content. +- Format: + +```go +func ReplaceDir(search, replace, path, pattern string, recursive ...bool) error +``` + +- Example: + +```go +func ExampleReplaceDir() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content of all files under specified directory recursively. + gfile.ReplaceDir("content", "replace word", tempDir, "gfile_example.txt", true) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example replace word +} +``` + + +### `ReplaceDirFunc` + +- Description: Scans the specified directory and uses a custom function to replace the specified content in matching files with new content. +- Format: + +```go +func ReplaceDirFunc(f func(path, content string) string, path, pattern string, recursive ...bool) error +``` + +- Example: + +```go +func ExampleReplaceDirFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example 123") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content of all files under specified directory with custom callback function recursively. + gfile.ReplaceDirFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempDir, "gfile_example.txt", true) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example 123 + // goframe example [num] +} +``` + + +## File Time + +### `MTime` + +- Description: Gets the modification time of the path. +- Format: + +```go +func MTime(path string) time.Time +``` + +- Example: + +```go +func ExampleMTime() { + t := gfile.MTime(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 2021-11-02 15:18:43.901141 +0800 CST +} +``` + + +### `MTimestamp` + +- Description: Gets the modification timestamp (seconds) of the path. +- Format: + +```go +func MTimestamp(path string) int64 +``` + +- Example: + +```go +func ExampleMTimestamp() { + t := gfile.MTimestamp(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 1635838398 +} +``` + + +### `MTimestampMilli` + +- Description: Gets the modification timestamp (milliseconds) of the path. +- Format: + +```go +func MTimestampMilli(path string) int64 +``` + +- Example: + +```go +func ExampleMTimestampMilli() { + t := gfile.MTimestampMilli(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 1635838529330 +} +``` + + +## File Size + +### `Size` + +- Description: Gets the size of the path without formatting. +- Format: + +```go +func Size(path string) int64 +``` + +- Example: + +```go +func ExampleSize() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "0123456789") + fmt.Println(gfile.Size(tempFile)) + + // Output: + // 10 +} +``` + + +### `SizeFormat` + +- Description: Gets the size of the path and formats it as a disk capacity. +- Format: + +```go +func SizeFormat(path string) string +``` + +- Example: + +```go +func ExampleSizeFormat() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "0123456789") + fmt.Println(gfile.SizeFormat(tempFile)) + + // Output: + // 10.00B +} +``` + + +### `ReadableSize` + +- Description: Gets the capacity size of the given path and formats it in a human-readable disk capacity format. +- Format: + +```go +func ReadableSize(path string) string +``` + +- Example: + +```go +func ExampleReadableSize() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "01234567899876543210") + fmt.Println(gfile.ReadableSize(tempFile)) + + // Output: + // 20.00B +} +``` + + +### `StrToSize` + +- Description: Converts a disk capacity size string to a size integer. +- Format: + +```go +func StrToSize(sizeStr string) int64 +``` + +- Example: + +```go +func ExampleStrToSize() { + size := gfile.StrToSize("100MB") + fmt.Println(size) + + // Output: + // 104857600 +} +``` + + +### `FormatSize` + +- Description: Converts a size integer to a disk capacity size string using `K, m, g, t, p, e, b`. +- Format: + +```go +func FormatSize(raw int64) string +``` + +- Example: + +```go +func ExampleFormatSize() { + sizeStr := gfile.FormatSize(104857600) + fmt.Println(sizeStr) + sizeStr0 := gfile.FormatSize(1024) + fmt.Println(sizeStr0) + sizeStr1 := gfile.FormatSize(999999999999999999) + fmt.Println(sizeStr1) + + // Output: + // 100.00M + // 1.00K + // 888.18P +} +``` + + +## File Sorting + +### `SortFiles` + +- Description: Sorts multiple paths alphabetically, with numbers taking precedence. +- Format: + +```go +func SortFiles(files []string) []string +``` + +- Example: + +```go +func ExampleSortFiles() { + files := []string{ + "/aaa/bbb/ccc.txt", + "/aaa/bbb/", + "/aaa/", + "/aaa", + "/aaa/ccc/ddd.txt", + "/bbb", + "/0123", + "/ddd", + "/ccc", + } + sortOut := gfile.SortFiles(files) + fmt.Println(sortOut) + + // Output: + // [/0123 /aaa /aaa/ /aaa/bbb/ /aaa/bbb/ccc.txt /aaa/ccc/ddd.txt /bbb /ccc /ddd] +} +``` + + +## File Search + +### `Search` + +- Description: Searches for files in the specified directory (default includes the current directory, execution directory, and main function directory; does not recurse subdirectories) and returns the real path. +- Format: + +```go +func Search(name string, prioritySearchPaths ...string) (realPath string, err error) +``` + +- Example: + +```go +func ExampleSearch() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_search") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // search file + realPath, _ := gfile.Search(fileName, tempDir) + fmt.Println(gfile.Basename(realPath)) + + // Output: + // gfile_example.txt +} +``` + + +## Directory Scanning + +### `ScanDir` + +- Description: Scans the specified directory and can scan files or directories, supporting recursive scans. +- Format: + +```go +func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) +``` + +- Example: + +```go +func ExampleScanDir() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively + list, _ := gfile.ScanDir(tempDir, "*", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // sub_dir + // gfile_example.txt +} +``` + + +### `ScanDirFile` + +- Description: Scans the files in the specified directory, supporting recursive scanning. +- Format: + +```go +func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, error) +``` + +- Example: + +```go +func ExampleScanDirFile() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_file") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFile(tempDir, "*.txt", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example.txt +} +``` + + +### `ScanDirFunc` + +- Description: Scans the specified directory (with custom filtering method), can scan files or directories, and supports recursive scans. +- Format: + +```go +func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) +``` + +- Example: + +```go +func ExampleScanDirFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_func") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively + list, _ := gfile.ScanDirFunc(tempDir, "*", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "gfile_example.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // sub_dir +} +``` + + +### `ScanDirFileFunc` + +- Description: Scans the files in the specified directory (with custom filtering method), supporting recursive scans. +- Format: + +```go +func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) +``` + +- Example: + +```go +func ExampleScanDirFileFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_file_func") + tempFile = gfile.Join(tempDir, fileName) + + fileName1 = "gfile_example_ignores.txt" + tempFile1 = gfile.Join(tempDir, fileName1) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempFile1, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFileFunc(tempDir, "*.txt", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "gfile_example_ignores.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example.txt +} +``` + + +## Common Directories + +### `Pwd` + +- Description: Gets the current working path. +- Format: + +```go +func Pwd() string +``` + +- Example: + +```go +func ExamplePwd() { + // Get absolute path of current working directory. + fmt.Println(gfile.Pwd()) + + // May Output: + // xxx/gf/os/gfile +} +``` + + +### `Home` + +- Description: Gets the home directory of the executing user. +- Format: + +```go +func Home(names ...string) (string, error) +``` + +- Example: + +```go +func ExampleHome() { + // user's home directory + homePath, _ := gfile.Home() + fmt.Println(homePath) + + // May Output: + // C:\Users\hailaz +} +``` + + +### `Temp` + +- Description: Gets the absolute path after appending the system temporary path. + +- Format: + +```go +func Temp(names ...string) string +``` + +- Example: + +```go +func ExampleTempDir() { + // init + var ( + fileName = "gfile_example_basic_dir" + ) + + // fetch an absolute representation of path. + path := gfile.Temp(fileName) + + fmt.Println(path) + + // Output: + // /tmp/gfile_example_basic_dir +} +``` + + +### `SelfPath` + +- Description: Gets the absolute path of the current running program. + +- Format: + +```go +func SelfPath() string +``` + +- Example: + +```go +func ExampleSelfPath() { + + // Get absolute file path of current running process + fmt.Println(gfile.SelfPath()) + + // May Output: + // xxx/___github_com_gogf_gf_v2_os_gfile__ExampleSelfPath +} +``` + + +## Type Judgment + +### `IsDir` + +- Description: Checks if the given path is a directory. +- Format: + +```go +func IsDir(path string) bool +``` + +- Example: + +```go +func ExampleIsDir() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + filePath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Checks whether given `path` a directory. + fmt.Println(gfile.IsDir(path)) + fmt.Println(gfile.IsDir(filePath)) + + // Output: + // true + // false +} +``` + + +### `IsFile` + +- Description: Checks if the given path is a file. +- Format: + +```go +func IsFile(path string) bool +``` + +- Example: + +```go +func ExampleIsFile() { + // init + var ( + filePath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dirPath = gfile.TempDir("gfile_example_basic_dir") + ) + // Checks whether given `path` a file, which means it's not a directory. + fmt.Println(gfile.IsFile(filePath)) + fmt.Println(gfile.IsFile(dirPath)) + + // Output: + // true + // false +} +``` + + +## Permission Operations + +### `IsReadable` + +- Description: Checks if the given path is readable. + +- Format: + +```go +func IsReadable(path string) bool +``` + +- Example: + +```go +func ExampleIsReadable() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Checks whether given `path` is readable. + fmt.Println(gfile.IsReadable(path)) + + // Output: + // true +} +``` + + +### `IsWritable` + +- Description: Checks if the specified path is writable. If the path is a directory, a temporary file is created to check if it is writable. If it is a file, it is checked if it can be opened. + +- Format: + +```go +func IsWritable(path string) bool +``` + +- Example: + +```go +func ExampleIsWritable() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Checks whether given `path` is writable. + fmt.Println(gfile.IsWritable(path)) + + // Output: + // true +} +``` + + +### `Chmod` + +- Description: Changes the file permissions of the specified path to the specified permissions. + +- Format: + +```go +func Chmod(path string, mode os.FileMode) error +``` + +- Example: + +```go +func ExampleChmod() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get a FileInfo describing the named file. + stat, err := gfile.Stat(path) + if err != nil { + fmt.Println(err.Error()) + } + // Show original mode + fmt.Println(stat.Mode()) + + // Change file model + gfile.Chmod(path, gfile.DefaultPermCopy) + + // Get a FileInfo describing the named file. + stat, _ = gfile.Stat(path) + // Show the modified mode + fmt.Println(stat.Mode()) + + // Output: + // -rw-r--r-- + // -rwxrwxrwx +} +``` + + +## File/Directory Operations + +### `Mkdir` + +- Description: Creates a directory, supporting recursive creation (absolute paths are recommended). The created directory permissions are: `drwxr-xr-x`. +- Format: + +```go +func Mkdir(path string) error +``` + +- Example: + +```go +func ExampleMkdir() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + ) + + // Creates directory + gfile.Mkdir(path) + + // Check if directory exists + fmt.Println(gfile.IsDir(path)) + + // Output: + // true +} +``` + + +### `Create` + +- Description: Creates a file/directory. If the directory in the path does not exist, it will automatically create the directory and file. The created file permissions are `-rw-r–r–`. +- Note: If the created file already exists, it will clear the file's content! +- Format: + +```go +func Create(path string) (*os.File, error) +``` + +- Example: + +```go +func ExampleCreate() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 50) + ) + // Check whether the file exists + isFile := gfile.IsFile(path) + + fmt.Println(isFile) + + // Creates file with given `path` recursively + fileHandle, _ := gfile.Create(path) + defer fileHandle.Close() + + // Write some content to file + n, _ := fileHandle.WriteString("hello goframe") + + // Check whether the file exists + isFile = gfile.IsFile(path) + + fmt.Println(isFile) + + // Reset file uintptr + unix.Seek(int(fileHandle.Fd()), 0, 0) + // Reads len(b) bytes from the File + fileHandle.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // false + // true + // hello goframe +} +``` + + +### `Open` + +- Description: Opens a file/directory in read-only mode. +- Format: + +```go +func Open(path string) (*os.File, error) +``` + +- Example: + +```go +func ExampleOpen() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + // Open file or directory with READONLY model + file, _ := gfile.Open(path) + defer file.Close() + + // Read data + n, _ := file.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // hello goframe +} +``` + + +### `OpenFile` + +- Description: Opens a file/directory with the specified `flag` and `perm`. +- Format: + +```go +func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) +``` + +- Example: + +```go +func ExampleOpenFile() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + // Opens file/directory with custom `flag` and `perm` + // Create if file does not exist,it is created in a readable and writable mode,prem 0777 + openFile, _ := gfile.OpenFile(path, os.O_CREATE|os.O_RDWR, gfile.DefaultPermCopy) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 28 + // hello goframe test open file +} +``` + + +### `OpenWithFalg` + +- Description: Opens a file/directory with the specified `flag`. +- Format: + +```go +func OpenWithFlag(path string, flag int) (*os.File, error) +``` + +- Example: + +```go +func ExampleOpenWithFlag() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + + // Opens file/directory with custom `flag` + // Create if file does not exist,it is created in a readable and writable mode with default `perm` is 0666 + openFile, _ := gfile.OpenWithFlag(path, os.O_CREATE|os.O_RDWR) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file with flag") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 38 + // hello goframe test open file with flag +} +``` + + +### `OpenWithFalgPerm` + +- Description: Opens a file/directory with the specified `flag` and `perm`. +- Format: + +```go +func OpenWithFlagPerm(path string, flag int, perm os.FileMode) (*os.File, error) +``` + +- Example: + +```go +func ExampleOpenWithFlagPerm() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + + // Opens file/directory with custom `flag` and `perm` + // Create if file does not exist,it is created in a readable and writable mode with `perm` is 0777 + openFile, _ := gfile.OpenWithFlagPerm(path, os.O_CREATE|os.O_RDWR, gfile.DefaultPermCopy) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file with flag and perm") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 38 + // hello goframe test open file with flag +} +``` + + +### `Stat` + +- Description: Gets the file information of the given path. +- Format: + +```go +func Stat(path string) (os.FileInfo, error) +``` + +- Example: + +```go +func ExampleStat() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Get a FileInfo describing the named file. + stat, _ := gfile.Stat(path) + + fmt.Println(stat.Name()) + fmt.Println(stat.IsDir()) + fmt.Println(stat.Mode()) + fmt.Println(stat.ModTime()) + fmt.Println(stat.Size()) + fmt.Println(stat.Sys()) + + // May Output: + // file1 + // false + // -rwxr-xr-x + // 2021-12-02 11:01:27.261441694 +0800 CST + // &{16777220 33261 1 8597857090 501 20 0 [0 0 0 0] {1638414088 192363490} {1638414087 261441694} {1638414087 261441694} {1638413480 485068275} 38 8 4096 0 0 0 [0 0]} +} +``` + + +### `Copy` + +- Description: Supports copying files or directories. +- Format: + +```go +func Copy(src string, dst string) error +``` + +- Example: + +```go +func ExampleCopy() { + // init + var ( + srcFileName = "gfile_example.txt" + srcTempDir = gfile.TempDir("gfile_example_copy_src") + srcTempFile = gfile.Join(srcTempDir, srcFileName) + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + + // copy dir + dstTempDir = gfile.TempDir("gfile_example_copy_dst") + ) + + // write contents + gfile.PutContents(srcTempFile, "goframe example copy") + + // copy file + gfile.Copy(srcTempFile, dstTempFile) + + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // copy dir + gfile.Copy(srcTempDir, dstTempDir) + + // list copy dir file + fList, _ := gfile.ScanDir(dstTempDir, "*", false) + for _, v := range fList { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // goframe example copy + // gfile_example.txt + // gfile_example_copy.txt +} +``` + + +### `CopyFile` + +- Description: Copies a file. +- Format: + +```go +func CopyFile(src, dst string) (err error) +``` + +- Example: + +```go +func ExampleCopyFile() { + // init + var ( + srcFileName = "gfile_example.txt" + srcTempDir = gfile.TempDir("gfile_example_copy_src") + srcTempFile = gfile.Join(srcTempDir, srcFileName) + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + ) + + // write contents + gfile.PutContents(srcTempFile, "goframe example copy") + + // copy file + gfile.CopyFile(srcTempFile, dstTempFile) + + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // Output: + // goframe example copy +} +``` + + +### `CopyDir` + +- Description: Supports copying files or directories. +- Format: + +```go +func CopyDir(src string, dst string) error +``` + +- Example: + +```go +func ExampleCopyDir() { + // init + var ( + srcTempDir = gfile.TempDir("gfile_example_copy_src") + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + + // copy dir + dstTempDir = gfile.TempDir("gfile_example_copy_dst") + ) + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // copy dir + gfile.CopyDir(srcTempDir, dstTempDir) + + // list copy dir file + fList, _ := gfile.ScanDir(dstTempDir, "*", false) + for _, v := range fList { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example_copy.txt +} +``` + + +### `Move` + +- Description: Renames `src` to `dst`. + +- Note: If `dst` already exists and is a file, it will be replaced, potentially causing data loss. +- Format: + +```go +func Move(src string, dst string) error +``` + +- Example: + +```go +func ExampleMove() { + // init + var ( + srcPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dstPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file2") + ) + // Check is file + fmt.Println(gfile.IsFile(dstPath)) + + // Moves `src` to `dst` path. + // If `dst` already exists and is not a directory, it'll be replaced. + gfile.Move(srcPath, dstPath) + + fmt.Println(gfile.IsFile(srcPath)) + fmt.Println(gfile.IsFile(dstPath)) + + // Output: + // false + // false + // true +} +``` + + +### `Rename` + +- Description: Alias for `Move`, renames `src` to `dst`. + +- Note: If `dst` already exists and is a file, it will be replaced, potentially causing data loss. +- Format: + +```go +func Rename(src string, dst string) error +``` + +- Example: + +```go +func ExampleRename() { + // init + var ( + srcPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file2") + dstPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Check is file + fmt.Println(gfile.IsFile(dstPath)) + + // renames (moves) `src` to `dst` path. + // If `dst` already exists and is not a directory, it'll be replaced. + gfile.Rename(srcPath, dstPath) + + fmt.Println(gfile.IsFile(srcPath)) + fmt.Println(gfile.IsFile(dstPath)) + + // Output: + // false + // false + // true +} +``` + + +### `Remove` + +- Description: Deletes the file or directory at the given path. + +- Format: + +```go +func Remove(path string) error +``` + +- Example: + +```go +func ExampleRemove() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Checks whether given `path` a file, which means it's not a directory. + fmt.Println(gfile.IsFile(path)) + + // deletes all file/directory with `path` parameter. + gfile.Remove(path) + + // Check again + fmt.Println(gfile.IsFile(path)) + + // Output: + // true + // false +} +``` + + +### `IsEmpty` + +- Description: Checks if the given path is empty, if a directory, checks if it contains files; if a file, checks if the file size is empty. + +- Format: + +```go +func IsEmpty(path string) bool +``` + +- Example: + +```go +func ExampleIsEmpty() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Check whether the `path` is empty + fmt.Println(gfile.IsEmpty(path)) + + // Truncate file + gfile.Truncate(path, 0) + + // Check whether the `path` is empty + fmt.Println(gfile.IsEmpty(path)) + + // Output: + // false + // true +} +``` + + +### `DirNames` + +- Description: Gets the list of files under the given path and returns them as a slice. + +- Format: + +```go +func DirNames(path string) ([]string, error) +``` + +- Example: + +```go +func ExampleDirNames() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + ) + // Get sub-file names of given directory `path`. + dirNames, _ := gfile.DirNames(path) + + fmt.Println(dirNames) + + // May Output: + // [file1] +} +``` + + +### `Glob` + +- Description: Fuzzy search for the file list under the given path, supports regex, the second parameter controls whether to return the results with absolute paths. + +- Format: + +```go +func Glob(pattern string, onlyNames ...bool) ([]string, error) +``` + +- Example: + +```go +func ExampleGlob() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "*_example_basic_test.go" + ) + // Get sub-file names of given directory `path`. + // Only show file name + matchNames, _ := gfile.Glob(path, true) + + fmt.Println(matchNames) + + // Show full path of the file + matchNames, _ = gfile.Glob(path, false) + + fmt.Println(matchNames) + + // May Output: + // [gfile_z_example_basic_test.go] + // [xxx/gf/os/gfile/gfile_z_example_basic_test.go] +} +``` + + +### `Exists` + +- Description: Checks if the given path exists. +- Format: + +```go +func Exists(path string) bool +``` + +- Example: + +```go +func ExampleExists() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Checks whether given `path` exist. + fmt.Println(gfile.Exists(path)) + + // Output: + // true +} +``` + + +### `Chdir` + +- Description: Changes the current working path to the given path. +- Format: + +```go +func Chdir(dir string) error +``` + +- Example: + +```go +func ExampleChdir() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Get current working directory + fmt.Println(gfile.Pwd()) + + // Changes the current working directory to the named directory. + gfile.Chdir(path) + + // Get current working directory + fmt.Println(gfile.Pwd()) + + // May Output: + // xxx/gf/os/gfile + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +## Path Operations + +### `Join` + +- Description: Joins multiple string paths with `/`. +- Format: + +```go +func Join(paths ...string) string +``` + +- Example: + +```go +func ExampleJoin() { + // init + var ( + dirPath = gfile.TempDir("gfile_example_basic_dir") + filePath = "file1" + ) + + // Joins string array paths with file separator of current system. + joinString := gfile.Join(dirPath, filePath) + + fmt.Println(joinString) + + // Output: + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +### `Abs` + +- Description: Returns the absolute path of the given path. + +- Format: + +```go +func Abs(path string) string +``` + +- Example: + +```go +func ExampleAbs() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get an absolute representation of path. + fmt.Println(gfile.Abs(path)) + + // Output: + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +### `RealPath` + +- Description: Gets the absolute path of the given path. + +- Note: Returns empty if the file does not exist. + +- Format: + +```go +func RealPath(path string) string +``` + +- Example: + +```go +func ExampleRealPath() { + // init + var ( + realPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + worryPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "worryFile") + ) + + // fetch an absolute representation of path. + fmt.Println(gfile.RealPath(realPath)) + fmt.Println(gfile.RealPath(worryPath)) + + // Output: + // /tmp/gfile_example_basic_dir/file1 + // +} +``` + + +### `SelfName` + +- Description: Gets the name of the current running program. + +- Format: + +```go +func SelfName() string +``` + +- Example: + +```go +func ExampleSelfName() { + + // Get file name of current running process + fmt.Println(gfile.SelfName()) + + // May Output: + // ___github_com_gogf_gf_v2_os_gfile__ExampleSelfName +} +``` + + +### `Basename` + +- Description: Gets the last element of the given path, including the extension. + +- Format: + +```go +func Basename(path string) string +``` + +- Example: + +```go +func ExampleBasename() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the last element of path, which contains file extension. + fmt.Println(gfile.Basename(path)) + + // Output: + // file.log +} +``` + + +### `Name` + +- Description: Gets the last element of the given path, excluding the extension. + +- Format: + +```go +func Name(path string) string +``` + +- Example: + +```go +func ExampleName() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the last element of path without file extension. + fmt.Println(gfile.Name(path)) + + // Output: + // file +} +``` + + +### `Dir` + +- Description: Gets the directory part of the given path, excluding the last element. + +- Format: + +```go +func Dir(path string) string +``` + +- Example: + +```go +func ExampleDir() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get all but the last element of path, typically the path's directory. + fmt.Println(gfile.Dir(path)) + + // Output: + // /tmp/gfile_example_basic_dir +} +``` + + +### `Ext` + +- Description: Gets the extension of the given path, including `.`. + +- Format: + +```go +func Ext(path string) string +``` + +- Example: + +```go +func ExampleExt() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the file name extension used by path. + fmt.Println(gfile.Ext(path)) + + // Output: + // .log +} +``` + + +### `ExtName` + +- Description: Gets the extension of the given path, excluding `.`. + +- Format: + +```go +func ExtName(path string) string +``` + +- Example: + +```go +func ExampleExtName() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the file name extension used by path but the result does not contains symbol '.'. + fmt.Println(gfile.ExtName(path)) + + // Output: + // log +} +``` + + +### `MainPkgPath` + +- Description: Gets the absolute path of the main file (entry point) location. + +- Note: + - `This method is only available in the development environment and is only effective in the source code development environment. It will display the source code path after building the binary.` + - `If the method is called for the first time in an asynchronous goroutine, it may not be able to get the path of the main package.` +- Format: + +```go +func MainPkgPath() string +``` + +- Example: + +```go +func Test() { + fmt.Println("main pkg path on main :", gfile.MainPkgPath()) + char := make(chan int, 1) + go func() { + fmt.Println("main pkg path on goroutine :", gfile.MainPkgPath()) + char <- 1 + }() + select { + case <-char: + } + // Output: + // /xxx/xx/xxx/xx + // /xxx/xx/xxx/xx +} +// Binary package +$ ./testDemo +main pkg path on main : /xxx/xx/xxx/xx +main pkg path on goroutine : /xxx/xx/xxx/xx +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" new file mode 100644 index 00000000000..7d86756e3aa --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-glog' +title: 'Logging' +sidebar_position: 5 +hide_title: true +description: "Implement the log management function through the glog module of the GoFrame framework, helping users master the methods and techniques for efficient log processing using the GoFrame framework. Gain a detailed understanding of modular design and the use of log recording." +keywords: [GoFrame, GoFrame framework, glog module, log management, log function, log component, log recording, log processing, development framework, modular design] +--- + +The log management function is implemented by the `glog` module. For details, please refer to the [Logging](../../核心组件/日志组件/日志组件.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" new file mode 100644 index 00000000000..3dabd64873e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" @@ -0,0 +1,30 @@ +--- +slug: '/docs/components/os-gtime' +title: 'Time' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Time Management, gtime, Date Formatting, PHP date, General Time Module, Time Extension, Time Date Method, Custom Format] +description: "The gtime module is a general time management module of the GoFrame framework, which extends the functionality of Golang's standard library time. It provides custom date formatting syntax and has good compatibility with PHP's date function format, making it more convenient for PHP developers to implement time management in Go." +--- + +## Introduction + +The general time management module encapsulates commonly used time/date-related methods and serves as a functional extension to the standard library `time`, providing more features. It supports custom date formatting syntax, which is inspired by the `PHP` `date` function syntax ( [http://php.net/manual/zh/function.date.php](http://php.net/manual/zh/function.date.php) ). +:::tip +The `gtime` time format syntax is very friendly for developers familiar with `PHP`. +::: +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gtime" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..aab461c02a2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,209 @@ +--- +slug: '/docs/components/os-gtime-common-funcs' +title: 'Time - Examples' +sidebar_position: 2 +hide_title: true +keywords: [time management, GoFrame, timestamp, date processing, global timezone, time format parsing, gtime object, utility methods, time conversion, time output example] +description: "Time management utility methods using the GoFrame framework, including methods to get the current timestamp, date, and time settings such as Timestamp, Date, and SetTimeZone, as well as how to parse common time format strings into the gtime.Time object through StrToTime. With the GoFrame framework, developers can conveniently perform various time format conversions and timezone settings." +--- + +Interface Documentation: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) + +The methods are quite simple, with the more commonly used methods as follows: + +1. `Timestamp` is used to obtain the current timestamp, while `TimestampMilli`, `TimestampMicro`, and `TimestampNano` are used to get the current millisecond, microsecond, and nanosecond values. +2. `Date` and `Datetime` are used to obtain the current date and current date-time. +3. `SetTimeZone` is used to set the global timezone for the current process. +4. For descriptions of other methods, please refer to the interface documentation. + +### Example 1: Basic Usage + +Create a time object and get the current timestamp. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + fmt.Println("Date :", gtime.Date()) + fmt.Println("Datetime :", gtime.Datetime()) + fmt.Println("Second :", gtime.Timestamp()) + fmt.Println("Millisecond:", gtime.TimestampMilli()) + fmt.Println("Microsecond:", gtime.TimestampMicro()) + fmt.Println("Nanosecond :", gtime.TimestampNano()) +} +``` + +After execution, the output is: + +``` +Date : 2018-07-22 +Datetime : 2018-07-22 11:52:22 +Second : 1532231542 +Millisecond: 1532231542688 +Microsecond: 1532231542688688 +Nanosecond : 1532231542688690259 +``` + +### Example 2: `StrToTime` + +In addition to using the `New` method, you can also use `StrToTime` to parse common time strings into a `gtime.Time` object. Common time strings include: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-14 04:51:34 +0805 LMT +2006-01-02T15:04:05Z07:00 +2014-01-17T01:19:15+08:00 +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17.897 +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018/10/31 - 16:38:46 +2018-02-09 +2018.02.09 + +01-Nov-2018 11:50:28 +01/Nov/2018 11:50:28 +01.Nov.2018 11:50:28 +01.Nov.2018:11:50:28 +Date connectors support '-', '/', '.' +``` + +Usage example: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + array := []string{ + "2017-12-14 04:51:34 +0805 LMT", + "2006-01-02T15:04:05Z07:00", + "2014-01-17T01:19:15+08:00", + "2018-02-09T20:46:17.897Z", + "2018-02-09 20:46:17.897", + "2018-02-09T20:46:17Z", + "2018-02-09 20:46:17", + "2018.02.09 20:46:17", + "2018-02-09", + "2017/12/14 04:51:34 +0805 LMT", + "2018/02/09 12:00:15", + "01/Nov/2018:13:28:13 +0800", + "01-Nov-2018 11:50:28 +0805 LMT", + "01-Nov-2018T15:04:05Z07:00", + "01-Nov-2018T01:19:15+08:00", + "01-Nov-2018 11:50:28 +0805 LMT", + "01/Nov/2018 11:50:28", + "01/Nov/2018:11:50:28", + "01.Nov.2018:11:50:28", + "01/Nov/2018", + } + cstLocal, _ := time.LoadLocation("Asia/Shanghai") + for _, s := range array { + if t, err := gtime.StrToTime(s); err == nil { + fmt.Println(s) + fmt.Println(t.UTC().String()) + fmt.Println(t.In(cstLocal).String()) + } else { + glog.Error(s, err) + } + fmt.Println() + } +} +``` + +In this example, some time strings are converted to `gtime.Time` objects using the `StrToTime` method, and both the `UTC` time and `CST` time (Shanghai timezone time) of the event are output. After execution, the output is: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-13 20:46:34 +2017-12-14 04:46:34 +0800 CST + +2006-01-02T15:04:05Z07:00 +2006-01-02 22:04:05 +2006-01-03 06:04:05 +0800 CST + +2014-01-17T01:19:15+08:00 +2014-01-16 17:19:15 +2014-01-17 01:19:15 +0800 CST + +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17 +2018-02-10 04:46:17.897 +0800 CST + +2018-02-09 20:46:17.897 +2018-02-09 12:46:17 +2018-02-09 20:46:17.897 +0800 CST + +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018-02-10 04:46:17 +0800 CST + +2018-02-09 20:46:17 +2018-02-09 12:46:17 +2018-02-09 20:46:17 +0800 CST + +2018.02.09 20:46:17 +2018-02-09 12:46:17 +2018-02-09 20:46:17 +0800 CST + +2018-02-09 +2018-02-08 16:00:00 +2018-02-09 00:00:00 +0800 CST + +2017/12/14 04:51:34 +0805 LMT +2017-12-13 20:46:34 +2017-12-14 04:46:34 +0800 CST + +2018/02/09 12:00:15 +2018-02-09 04:00:15 +2018-02-09 12:00:15 +0800 CST + +01/Nov/2018:13:28:13 +0800 +2018-11-01 05:28:13 +2018-11-01 13:28:13 +0800 CST + +01-Nov-2018 11:50:28 +0805 LMT +2018-11-01 03:45:28 +2018-11-01 11:45:28 +0800 CST + +01-Nov-2018T15:04:05Z07:00 +2018-11-01 22:04:05 +2018-11-02 06:04:05 +0800 CST + +01-Nov-2018T01:19:15+08:00 +2018-10-31 17:19:15 +2018-11-01 01:19:15 +0800 CST + +01-Nov-2018 11:50:28 +0805 LMT +2018-11-01 03:45:28 +2018-11-01 11:45:28 +0800 CST + +01/Nov/2018 11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01/Nov/2018:11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01.Nov.2018:11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01/Nov/2018 +2018-10-31 16:00:00 +2018-11-01 00:00:00 +0800 CST +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..b478f92ccea --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,721 @@ +--- +slug: '/docs/components/os-gtime-funcs' +title: 'Time - Methods' +sidebar_position: 4 +hide_title: true +keywords: [Time Management, GoFrame, Time Object, Time Format, Timezone Setting, Timestamp, Time Operations, Time Comparison, Leap Year Judgment, DateTime] +description: "Methods related to time management in the GoFrame framework, including how to create time objects, format time, set timezones, and obtain timestamps, among other functionalities, can help developers perform time operations and management more conveniently." +--- +:::tip +The following list of common methods might be updated slower than new code features. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) +::: +## `New` + +- Description: `New` creates and returns a `Time` object with the given parameters. +- Format: + +```go +func New(param ...interface{}) *Time +``` + +- Example: Create a time object. + +```go +func ExampleNew() { + t1 := gtime.New(time.Now()) + t2 := gtime.New("2018-08-08 08:08:08") + t3 := gtime.New(1533686888) + + fmt.Println(t1) + fmt.Println(t2) + fmt.Println(t3) + + // Output: + // 2021-11-18 14:18:27 + // 2018-08-08 08:08:08 + // 2018-08-08 08:08:08 +``` + + +## `Now` + +- Description: `Now` creates and returns the current time object. +- Format: + +```go +func Now() *Time +``` + +- Example: Get the current time object. + +```go +func ExampleNow() { + t := gtime.Now() + fmt.Println(t) + + // Output: + // 2021-11-06 13:41:08 +} +``` + + +## `Format` + +- Description: Format output time. +- Format: + +```go +func (t *Time) Format(format string) string +``` + +- Example: Format output time. The complete time format can be referred to in [Time - Format](时间管理-时间格式.md). + +```go +func ExampleTime_Format() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Format("Y-m-d")) + fmt.Println(gt1.Format("l")) + fmt.Println(gt1.Format("F j, Y, g:i a")) + fmt.Println(gt1.Format("j, n, Y")) + fmt.Println(gt1.Format("h-i-s, j-m-y, it is w Day z")) + fmt.Println(gt1.Format("D M j G:i:s T Y")) + + // Output: + // 2018-08-08 + // Wednesday + // August 8, 2018, 8:08 am + // 8, 8, 2018 + // 08-08-08, 8-08-18, 0831 0808 3 Wedam18 219 + // Wed Aug 8 8:08:08 CST 2018 +} +``` + + +## `String` + +- Description: Output as a string. +- Format: + +```go +func (t *Time) String() string +``` + +- Example: Output as a string type. + +```go +func ExampleTime_String() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.String() + + fmt.Println(t1) + fmt.Println(reflect.TypeOf(t1)) + + // Output: + // 2018-08-08 08:08:08 + // string +} +``` + + +## `Timestamp` + +- Description: Get the second-level timestamp of the current object. Corresponding methods include `TimestampMicro/TimestampMilli/TimestampNano`. +- Format: + +```go +func (t *Time) Timestamp() int64 +func Timestamp() int64 +``` + +- Example: Get the second-level timestamp of the current object. + +```go +func ExampleTime_Timestamp() { + t := gtime.Now() + + fmt.Println(t.Timestamp()) + fmt.Println(gtime.Timestamp()) + fmt.Println(t.TimestampMicro()) + fmt.Println(t.TimestampMilli()) + fmt.Println(t.TimestampNano()) + + // Output: + // 1533686888 + // 1533686888 + // 1533686888000 + // 1533686888000000 + // 1533686888000000000 +} +``` + + +## `ToZone` + +- Description: Set timezone. +- Format: + +```go +func (t *Time) ToZone(zone string) (*Time, error) +``` + +- Example: Get the second-level timestamp of the current object. + +```go +func ExampleTime_ToZone() { + gt1 := gtime.Now() + gt2, _ := gt1.ToZone("Asia/Shanghai") + gt3, _ := gt1.ToZone("Asia/Tokyo") + + fmt.Println(gt2) + fmt.Println(gt3) + + // May Output: + // 2021-11-11 17:10:10 + // 2021-11-11 18:10:10 +} +``` + + +## `SetTimeZone` + +- Description: Set timezone. +- Format: + +```go +func SetTimeZone(zone string) error +``` + +- Example: Set timezone. + +```go +func ExampleSetTimeZone() { + gtime.SetTimeZone("Asia/Shanghai") + fmt.Println(gtime.Datetime()) + + gtime.SetTimeZone("Asia/Tokyo") + fmt.Println(gtime.Datetime()) + // May Output: + // 2018-08-08 08:08:08 + // 2018-08-08 09:08:08 +} +``` + + +## `StrToTime` + +- Description: Convert time string to time object. +- Format: + +```go +func StrToTime(str string, format ...string) (*Time, error) +``` + +- Example: Convert time string to time object. + +```go +func ExampleStrToTime() { + res, _ := gtime.StrToTime("2006-01-02T15:04:05-07:00", "Y-m-d H:i:s") + fmt.Println(res) + + // May Output: + // 2006-01-02 15:04:05 +} +``` + + +## `Add` + +- Description: Add time to the current time object. +- Format: + +```go +func (t *Time) Add(d time.Duration) *Time +``` + +- Example: Add time to the current time object. + +```go +func ExampleTime_Add() { + gt := gtime.New("2018-08-08 08:08:08") + gt1 := gt.Add(time.Duration(10) * time.Second) + + fmt.Println(gt1) + + // Output: + // 2018-08-08 08:08:18 +} +``` + + +## `StartOfDay` + +- Description: Return the start time object of today. Similar methods include `StartOfHalf/StartOfHour/StartOfMonth/StartOfMinute/StartOfQuarter`, etc. +- Format: + +```go +func (t *Time) StartOfDay() *Time +``` + +- Example: Return the start time object of today. + +```go +func ExampleTime_StartOfDay() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.StartOfDay()) + + // Output: + // 2018-08-08 00:00:00 +} +``` + + +## `EndOfDay` + +- Description: Return the end time object of today. Similar methods include `EndOfHalf/EndOfHour/EndOfMonth/EndOfMinute/EndOfQuarter`, etc. +- Format: + +```go +func (t *Time) EndOfDay() *Time +``` + +- Example: Return the end time object of today. + +```go +func ExampleTime_EndOfDay() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.EndOfDay()) + + // Output: + // 2018-08-08 23:59:59 +} +``` + + +## `Month` + +- Description: Return the month index in a year. For example, January corresponds to 1. +- Format: + +```go +func (t *Time) Month() int +``` + +- Example: Return the month index in a year. + +```go +func ExampleTime_Month() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.Month() + + fmt.Println(t1) + + // Output: + // 8 +} +``` + + +## `Second` + +- Description: Return the number of seconds in the current minute. For example, 10:10:08 corresponds to 8 seconds. +- Format: + +```go +func (t *Time) Second() int +``` + +- Example: Return the number of seconds in the current minute. + +```go +func ExampleTime_Second() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.Second() + + fmt.Println(t1) + + // Output: + // 8 +} +``` + + +## `IsZero` + +- Description: Check if the time equals `0001-01-01 00:00:00`. Note it does not represent the timestamp 0, which is `1970-01-01 08:00:00`. +- Format: + +```go +func (t *Time) IsZero() bool +``` + +- Example: Check if the month index in a year. + +```go +func ExampleTime_IsZero() { + gt := gtime.New("0-0-0") + + fmt.Println(gt.IsZero()) + + // Output: + // true +} +``` + + +## `AddDate` + +- Description: Add specified year, month, and day to the current time object. +- Format: + +```go +func (t *Time) AddDate(years int, months int, days int) *Time +``` + +- Example: Add specified year, month, and day to the current time object. + +```go +func ExampleTime_AddDate() { + var ( + year = 1 + month = 2 + day = 3 + ) + gt := gtime.New("2018-08-08 08:08:08") + gt = gt.AddDate(year, month, day) + + fmt.Println(gt) + + // Output: + // 2019-10-11 08:08:08 +} +``` + + +## `Equal` + +- Description: Check if two time objects are equal. +- Format: + +```go +func (t *Time) Equal(u *Time) bool +``` + +- Example: Check if two time objects are equal. + +```go +func ExampleTime_Equal() { + gt1 := gtime.New("2018-08-08 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Equal(gt2)) + + // Output: + // true +} +``` + + +## `Before` + +- Description: Determine the order of two time objects. +- Format: + +```go +func (t *Time) Before(u *Time) bool +``` + +- Example: Determine the order of two time objects. + +```go +func ExampleTime_Before() { + gt1 := gtime.New("2018-08-07 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Before(gt2)) + + // Output: + // true +} +``` + + +## `After` + +- Description: Determine the order of two time objects. +- Format: + +```go +func (t *Time) After(u *Time) bool +``` + +- Example: Determine the order of two time objects. + +```go +func ExampleTime_After() { + gt1 := gtime.New("2018-08-07 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.After(gt2)) + + // Output: + // false +} +``` + + +## `Layout` + +- Description: Format output time. +- Format: + +```go +func (t *Time) Layout(layout string) string +``` + +- Example: Format output time. + +```go +func ExampleTime_Layout() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Layout("2006-01-02")) + + // Output: + // 2018-08-08 +} +``` + + +## `IsLeapYear` + +- Description: Check if it's a leap year. +- Format: + +```go +func (t *Time) IsLeapYear() bool +``` + +- Example: Check if it's a leap year. + +```go +func ExampleTime_IsLeapYear() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.IsLeapYear()) + + // Output: + // false +} +``` + + +## `Date` + +- Description: Get the date. +- Format: + +```go +func Date() string +``` + +- Example: Get the date. + +```go +func ExampleDate() { + fmt.Println(gtime.Date()) + + // May Output: + // 2006-01-02 +} +``` + + +## `Datetime` + +- Description: Get the datetime. +- Format: + +```go +func Datetime() string +``` + +- Example: Get the datetime. + +```go +func ExampleDatetime() { + fmt.Println(gtime.Datetime()) + + // May Output: + // 2006-01-02 15:04:05 +} +``` + + +## `ISO8601` + +- Description: Return time in ISO8601 format. +- Format: + +```go +func ISO8601() string +``` + +- Example: + +```go +func ExampleISO8601() { + fmt.Println(gtime.ISO8601()) + + // May Output: + // 2006-01-02T15:04:05-07:00 +} +``` + + +## `RFC822` + +- Description: Return time in RFC822 format. +- Format: + +```go +func RFC822() string +``` + +- Example: + +```go +func ExampleRFC822() { + fmt.Println(gtime.RFC822()) + + // May Output: + // Mon, 02 Jan 06 15:04 MST +} +``` + + +## `StrToTimeFormat` + +- Description: `StrToTimeFormat` returns time object based on the input time string and format. +- Format: + +```go +func StrToTimeFormat(str string, format string) (*Time, error) +``` + +- Example: + +```go +func ExampleStrToTimeFormat() { + res, _ := gtime.StrToTimeFormat("2006-01-02 15:04:05", "Y-m-d H:i:s") + fmt.Println(res) + + // Output: + // 2006-01-02 15:04:05 +} +``` + + +## `StrToTimeLayout` + +- Description: `StrToTimeLayout` returns time object based on the input time string and format. +- Format: + +```go +func StrToTimeLayout(str string, layout string) (*Time, error) +``` + +- Example: + +```go +func ExampleStrToTimeLayout() { + res, _ := gtime.StrToTimeLayout("2018-08-08", "2006-01-02") + fmt.Println(res) + + // Output: + // 2018-08-08 00:00:00 +} +``` + + +## `MarshalJSON` + +- Description: `MarshalJSON` overrides the method in `json.Marshal`. +- Format: + +```go +func (t *Time) MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleTime_MarshalJSON() { + type Person struct { + Name string `json:"name"` + Birthday *gtime.Time `json:"birthday"` + } + p := new(Person) + p.Name = "goframe" + p.Birthday = gtime.New("2018-08-08 08:08:08") + j, _ := json.Marshal(p) + fmt.Println(string(j)) + + // Output: + // {"name":"xiaoming","birthday":"2018-08-08 08:08:08"} +} +``` + + +## `UnmarshalJSON` + +- Description: `UnmarshalJSON` overrides the method in `json.Unmarshal`. +- Format: + +```go +func (t *Time) UnmarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleTime_MarshalJSON() { + type Person struct { + Name string `json:"name"` + Birthday *gtime.Time `json:"birthday"` + } + p := new(Person) + p.Name = "goframe" + p.Birthday = gtime.New("2018-08-08 08:08:08") + j, _ := json.Marshal(p) + fmt.Println(string(j)) + + // Output: + // {"name":"xiaoming","birthday":"2018-08-08 08:08:08"} +} +``` + + +## `WeekOfYear` + +- Description: `WeekOfYear` returns the current week number of the year, starting from 1. Similar methods include `DayOfYear/DaysInMonth`. +- Format: + +```go +func (t *Time) WeeksOfYear() int +``` + +- Example: + +```go +func ExampleTime_WeeksOfYear() { + gt1 := gtime.New("2018-01-08 08:08:08") + + fmt.Println(gt1.WeeksOfYear()) + + // Output: + // 2 +}D +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" new file mode 100644 index 00000000000..5f08be42c47 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" @@ -0,0 +1,115 @@ +--- +slug: '/docs/components/os-gtime-timezone' +title: 'Time - Time Zone' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, Time Zone Settings, SetTimeZone, Time Management, gtime, Standard Library, Time Conversion, Log Output, Global Settings, Programming Guide] +description: "Use the gtime component in the GoFrame framework for global time zone settings, explain the usage limitations and precautions of the SetTimeZone method, and provide code examples to demonstrate how to correctly manage and convert time in programs, especially in business scenarios involving multiple time zones." +--- + +## `SetTimeZone` Setting Global Time Zone + +```go +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Set the global time zone for the process + err := gtime.SetTimeZone("Asia/Tokyo") + if err != nil { + panic(err) + } + + // Use gtime to get the current time + fmt.Println(gtime.Now().String()) + + // Use standard library to get the current time + fmt.Println(time.Now().String()) +} +``` + +After execution, the output result is: + +```html +2023-01-06 15:27:38 +2023-01-06 15:27:38.753909 +0900 JST m=+0.002758145 +``` + +## Time Zone Settings Precautions + +### `SetTimeZone` Method Multiple Calls Error + +The `SetTimeZone` method allows setting the global time zone only once. If called multiple times with different time zones, subsequent calls will fail and return an `error`. + +### Initialization Issue with the `time` Package in Business Projects + +The global setting of the program's time zone must be called before importing the standard library's `time` package, as it initializes upon import, preventing further global time zone changes. Time zone conversions on specific time objects can only be done using the `ToLocation` method (or the standard library's `In` method). An example of converting using `ToLocation`: + +```go +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Set the global time zone for the process + err := gtime.SetTimeZone("Asia/Tokyo") + if err != nil { + panic(err) + } + + // Use gtime to get the current time + fmt.Println(gtime.Now()) + + // Use standard library to get the current time + fmt.Println(time.Now()) + + // Perform time zone conversion on a specific time object + local, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + panic(err) + } + fmt.Println(gtime.Now().ToLocation(local)) +} +``` + +After execution, the terminal outputs: + +```html +2023-01-06 15:37:38 +2023-01-06 15:37:38.753909 +0900 JST m=+0.002758145 +2023-01-06 14:37:38 +``` + +In business projects, there are often many business packages `import` before the `main` package, which can cause initialization issues with the `time` package. Therefore, if you need to set the time zone globally, it is recommended to call the `SetTimeZone` method through an independent package and execute `import` at the very beginning of the `main` package to avoid initialization issues with the `time` package. For example: + +Related reference link: [https://stackoverflow.com/questions/54363451/setting-timezone-globally-in-golang](https://stackoverflow.com/questions/54363451/setting-timezone-globally-in-golang) + +```go +package main + +import ( + _ "boot/time" + + "fmt" + "time" +) + +func main() { + // Use gtime to get the current time + fmt.Println(gtime.Now().String()) + + // Use standard library to get the current time + fmt.Println(time.Now().String()) +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..3e31a71222d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/components/os-gtime-time' +title: 'Time - Object' +sidebar_position: 1 +hide_title: true +keywords: [time management, time object, GoFrame, timestamp, formatting, standard library, custom time, time string, chained operations, gtime] +description: "Create and manage time objects with GoFrame, including methods to create gtime.Time objects using standard library time.Time objects, Unix timestamps, and time strings. It also explains how to format time using custom and standard library formats, and demonstrates examples of chained operations with time objects to help developers process time data more efficiently." +--- + +## Time Object + +You can create a `gtime.Time` object through a standard library `time.Time` object, Unix timestamp, time string (e.g., `2018-07-18 12:01:00`), or custom time string (requires a given format, supports custom formats and standard library formats). + +## Creating Objects + +A `gtime.Time` object can be created using the `gtime.New` method, which supports creating objects from `time.Time`, timestamps, and time strings. Timestamps support time lengths in nanoseconds. For example: + +```go +// Create from a time string +gtime.New("2020-10-24 12:00:00") +// Create from a time.Time object +gtime.New(time.Now()) +// Create from a timestamp in seconds +gtime.New(1603710586) +// Create from a timestamp in nanoseconds +gtime.New(1603710586660409000) +``` + +Additionally, time strings support common types such as: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-14 04:51:34 +0805 LMT +2006-01-02T15:04:05Z07:00 +2014-01-17T01:19:15+08:00 +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17.897 +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018/10/31 - 16:38:46 +2018-02-09 +2018.02.09 + +01-Nov-2018 11:50:28 +01/Nov/2018 11:50:28 +01.Nov.2018 11:50:28 +01.Nov.2018:11:50:28 +Date separators support '-', '/', '.' +``` + +## Examples + +### Example 1: Custom Formatting Syntax + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + formats := []string{ + "Y-m-d H:i:s.u", + "D M d H:i:s T O Y", + "\\T\\i\\m\\e \\i\\s: h:i:s a", + "2006-01-02T15:04:05.000000000Z07:00", + } + t := gtime.Now() + for _, f := range formats { + fmt.Println(t.Format(f)) + } +} +``` + +In this example, we specified four `format` styles and converted the current time to these styles for printing. The output is as follows: + +```html +2018-07-22 11:17:13.797 +Sun Jul 22 11:17:13 CST +0800 2018 +Time is: 11:17:13 am +2006-01-02CST15:04:05.000000000Z07:00 +``` + +Noteworthy points in this example: + +1. When letters conflict with formatting characters, you can escape the character with `\` to indicate to the parser it's a regular letter, not a format character. Hence, the third string outputs as: `Time is: 11:17:13 am` +2. The `Format` method accepts custom formatting syntax (e.g., `Y-m-d H:i:s`), not the standard library syntax (e.g., `2006-01-02 15:04:05`), leading to the fourth string being output as-is. + +### Example 2: Standard Library Formatting Syntax + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + formats := []string{ + "2006-01-02 15:04:05.000", + "Mon Jan _2 15:04:05 MST 2006", + "Time is: 03:04:05 PM", + "2006-01-02T15:04:05.000000000Z07:00 MST", + } + t := gtime.Now() + for _, f := range formats { + fmt.Println(t.Layout(f)) + } +} +``` + +In this example, we use four standard library time formatting syntaxes to format the current time and output the result to the terminal. The output is: + +```html +2018-07-22 11:28:13.945 +Sun Jul 22 11:28:13 CST 2018 +Time is: 11:28:13 AM +2018-07-22T11:28:13.945153275+08:00 CST +``` + +Key points: + +1. Custom formatting syntax and standard library formatting syntax are not conflicting. They use `Format` and `Layout` methods respectively and are independent and non-interchangeable. +2. The standard library's formatting syntax has its unique complexities. + +### Example 3: Chained Operations on Time Objects + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + // This time last year, system time + fmt.Println(gtime.Now().AddDate(-1, 0, 0).Format("Y-m-d")) + + // This time last year, UTC time + fmt.Println(gtime.Now().AddDate(-1, 0, 0).Format("Y-m-d H:i:s T")) + fmt.Println(gtime.Now().AddDate(-1, 0, 0).UTC().Format("Y-m-d H:i:s T")) + + // Midnight on the 1st of next month + fmt.Println(gtime.Now().AddDate(0, 1, 0).Format("Y-m-01 00:00:00")) + + // 1 hour ago + fmt.Println(gtime.Now().Add(-time.Hour).Format("Y-m-d H:i:s")) +} +``` + +The output is: + +``` +2020-09-19 +2020-09-19 15:51:48 CST +2020-09-19 07:51:48 UTC +2021-10-01 00:00:00 +2021-09-19 14:51:48 +``` + +This example is straightforward and needs no further elaboration. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..220a4e5cbea --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/components/os-gtime-format' +title: 'Time - Format' +sidebar_position: 0 +hide_title: true +keywords: [time management, time format, GoFrame, gtime, time conversion, custom format, date format, time syntax, timestamp, timezone] +description: "The time management module in the GoFrame framework focuses on the custom time format feature of the gtime.Time object. By comparing it to the Format method of the standard library time.Time, gtime provides the Layout method to achieve standard format date conversion, listing the details of various time format syntax supported by the gtime module, helping developers efficiently manage and convert date and time." +--- +:::warning +Note: The `gtime.Time` object uses the `Format` method to achieve custom formatted date and time conversion, which conflicts with the `Format` method of the standard library `time.Time`. In the `gtime.Time` object, the `Layout` method is used to implement the `Format` format of the standard library `time.Time`, for example: `t.Layout('2006-01-02 15:04:05')`. +::: +Below is the list of time format syntax supported by the `gtime` module: + +| Format | Description | Example Return Value | +| --- | --- | --- | +| **Day** | -- | -- | +| `d` | Day of the month, 2 digits with leading zeros | 01 to 31 | +| `D` | Day of the week, textual representation, 3 letters | Mon to Sun | +| `N` | ISO-8601 numeric representation of the day of the week | 1 (Monday) to 7 (Sunday) | +| `j` | Day of the month without leading zeros | 1 to 31 | +| `l` | ("L" lowercase) Full textual representation of the day of the week | Sunday to Saturday | +| `S` | English suffix for the day of the month, 2 characters | st, nd, rd, or th. Can be used with j | +| `w` | Numeric representation of the day of the week | 0 (Sunday) to 6 (Saturday) | +| `z` | Day of the year | 0 to 365 | +| **Week** | -- | -- | +| `W` | ISO-8601 week number of the year, starting on Monday | e.g., 42 (42nd week of the year) | +| **Month** | -- | -- | +| `F` | Full textual representation of a month | January to December | +| `m` | Numeric representation of a month, with leading zeros | 01 to 12 | +| `M` | Short textual representation of a month, 3 letters | Jan to Dec | +| `n` | Numeric representation of a month, without leading zeros | 1 to 12 | +| `t` | Number of days in the given month | 28 to 31 | +| **Year** | -- | -- | +| `Y` | Full numeric representation of a year, 4 digits | e.g., 1999 or 2003 | +| `y` | Two-digit representation of a year | e.g., 99 or 03 | +| **Time** | -- | -- | +| `a` | Lowercase Ante meridiem and Post meridiem | am or pm | +| `A` | Uppercase Ante meridiem and Post meridiem | AM or PM | +| `g` | 12-hour format of an hour, without leading zeros | 1 to 12 | +| `G` | 24-hour format of an hour, without leading zeros | 0 to 23 | +| `h` | 12-hour format of an hour, with leading zeros | 01 to 12 | +| `H` | 24-hour format of an hour, with leading zeros | 00 to 23 | +| `i` | Minutes with leading zeros | 00 to 59 | +| `s` | Seconds with leading zeros | 00 to 59 | +| `u` | Milliseconds (3 digits) | e.g., 000, 123, 239 | +| `U` | UNIX Timestamp (seconds) | e.g., 1559648183 | +| **Timezone** | -- | -- | +| `O` | Difference to Greenwich time (GMT) in hours | e.g., +0200 | +| `P` | Difference to Greenwich time (GMT) with colon between hours and minutes | e.g., +02:00 | +| `T` | Timezone abbreviation | e.g., UTC, GMT, CST | +| **Date** | -- | -- | +| `c` | ISO 8601 date | e.g., 2004-02-12T15:19:21+00:00 | +| `r` | RFC 822 formatted date | e.g., Thu, 21 Dec 2000 16:01:07 +0200 | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" new file mode 100644 index 00000000000..54a1167572e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/os-gbuild' +title: 'Build Information' +sidebar_position: 17 +hide_title: true +keywords: [GoFrame, GoFrame framework, gf build, build information, gbuild, program build, Go language, API documentation, software development, code import] +description: "Use the gbuild module in the GoFrame framework to obtain program build information. After building the program with the gf build command, you can access the build information through the gbuild module. This module is part of the GoFrame framework, supporting Go language software developers to quickly retrieve and utilize build data, providing simple and easy-to-use API documentation." +--- + +Used to obtain build information for programs built with the `gf build` command. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gbuild" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gbuild](https://pkg.go.dev/github.com/gogf/gf/v2/os/gbuild) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" new file mode 100644 index 00000000000..45a854977f0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gview' +title: 'Template Engine' +sidebar_position: 15 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Template Engine, gview, gview Module, Core Component, Template Engine Implementation, Module Implementation, gview Usage, gview Functionality] +description: "The template engine component gview in the GoFrame framework is detailed, explaining the functions and usage methods of the gview module. By explaining the core component template engine, it helps users understand and apply the gview module to achieve complex template engine functionalities." +--- + +The template engine is implemented by the `gview` module, please refer to the [Template Engine](../../核心组件/模板引擎/模板引擎.md) section for details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" new file mode 100644 index 00000000000..240c4038c4e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" @@ -0,0 +1,113 @@ +--- +slug: '/docs/components/os-genv' +title: 'Environment' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame,Environment Variable Management,genv,SetMap,GetWithCmd,GoFrame Framework,Environment Variable Setting,Command Line Options,Delete Environment Variables,Batch Setting of Environment Variables] +description: "The genv environment variable management component in the GoFrame framework, including how to batch set environment variables and how to obtain environment variables through command-line options. When an environment variable does not exist, it supports reading from command-line options. Additionally, it covers adding, deleting, and naming conversion rules for environment variables." +--- + +Environment variable management component. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/genv" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/genv](https://pkg.go.dev/github.com/gogf/gf/v2/os/genv) + +## `SetMap` + +```go +func SetMap(m map[string]string) error +``` + +This method is used for batch setting of environment variables. Usage example: + +``` +genv.SetMap(g.MapStrStr{ + "APPID": "order", + "THREAD": "16", + "ENDPOINTS": "127.0.0.1:6379", +}) +``` + +## `GetWithCmd` + +```go +func GetWithCmd(key string, def ...interface{}) *gvar.Var +``` + +This method is used to get the specified option value in the environment variable. If the environment variable does not exist, it will read from the command-line option. However, the naming rules for both will be different. For example: `genv.GetWithCmd("gf.debug")` will first try to read the value of the `GF_DEBUG` environment variable, and if it does not exist, it will go to the `gf.debug` option in the command line. + +Note the naming conversion rules: + +- Environment variables convert names to uppercase and `.` characters to `_` characters. +- In the command line, names are converted to lowercase and `_` characters to `.` characters. + +## `All` + +```go +func All() []string +``` + +This method returns the string in the environment variable and returns it in the form of `key=value`. + +## `Map` + +```go +func Map() map[string]string +``` + +This method returns the string in the environment variable and returns it in the form of `map`. + +## `Get` + +```go +func Get(key string, def ...interface{}) *gvar.Var +``` + +This method is used to create a generic type environment variable. If the given `key` does not exist, it returns a default generic type environment variable. + +## `Set` + +```go +func Set(key, value string) error +``` + +This method sets environment variables with the given `key` and `value`, and returns an `Error` type if there is an error. + +## `SetMap` + +```go +func SetMap(m map[string]string) error +``` + +This method stores environment variables through a parameter of type `map`. + +## `Contains` + +```go +func Contains(key string) bool +``` + +This method checks whether the `key` exists in the environment variable. + +## `Remove` + +```go +func Remove(key ...string) error +``` + +This method can delete one or more environment variables. + +## `Build` + +```go +func Build(m map[string]string) []string +``` + +This method constructs and returns the `map` parameter in the form of an array. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" new file mode 100644 index 00000000000..d575fe37821 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/os' +title: 'System' +sidebar_position: 1 +hide_title: true +keywords: [System Management, Operating System, GoFrame, GoFrame Framework, System Component, Platform Support, System Tools, Software Development, Technical Documentation, Framework Usage] +description: "Modules related to the operating system in the GoFrame framework, including how to manage and use operating system features on different platforms, providing system-level tools and components to help developers with more efficient software development." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" new file mode 100644 index 00000000000..8cffb6e6f58 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcache' +title: 'Caching' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gcache, Cache Management, Web Development, Backend Framework, Open Source, High Performance, Go Language, Component] +description: "Implement cache management through the gcache module in the GoFrame framework. Learn how to use this module in your web development projects to improve performance and efficiency, and gain in-depth guidance on caching strategies, configuration, and best practices." +--- + +Cache management is implemented by the `gcache` module. For details, please refer to the [Caching](../../核心组件/缓存管理/缓存管理.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" new file mode 100644 index 00000000000..d0a3d37d8a6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gres' +title: 'Resource' +sidebar_position: 10 +hide_title: true +description: "Resource management techniques in the GoFrame framework, discussing the usage of the gres module in detail. By referring to related core component documentation, users can optimize site resource management and improve overall website performance and presentation." +keywords: [resource management, GoFrame, gres module, component documentation, website performance, site resources, core components, module implementation, framework technology, performance optimization] +--- + +Resource management is implemented by the `gres` module. Please refer to the [Resource](../../核心组件/资源管理/资源管理.md) section for details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" new file mode 100644 index 00000000000..a4166f94dec --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" @@ -0,0 +1,37 @@ +--- +slug: '/docs/components/os-gproc' +title: 'Process' +sidebar_position: 14 +hide_title: true +keywords: [process management, inter-process communication, local socket, gproc module, GoFrame, shell commands, asynchronous execution, subprocess management, gogf, process resources] +description: "Methods for implementing process management and inter-process communication using the gproc module of the GoFrame framework. gproc uses local socket mechanisms for communication and provides various interfaces such as Shell, ShellExec, ShellRun to execute shell commands in different ways. With the help of goroutines, asynchronous execution can be achieved. In this document, you will learn how to use the Manager object for multi-subprocess management, as well as how to obtain and control specific process resources." +--- + +## Introduction +Process management and inter-process communication are implemented through the `gproc` module, where inter-process communication uses a local socket communication mechanism. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/os/gproc" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gproc](https://pkg.go.dev/github.com/gogf/gf/v2/os/gproc) + +**Brief Description**: + +1. The `Manager` object is a process management object that can manage multiple subprocesses (the current executing process is the parent process) simultaneously; +2. `Process` is a process object, representing a specific execution or acquired process resource; +3. We can use `Shell`, `ShellExec`, `ShellRun` to execute shell commands: + - `Shell` represents a native shell command execution method with custom input and output control; + - `ShellExec` will return the output result content after executing the command; + - `ShellRun` will directly output the return content to standard output after executing the command; + - We can use `goroutine` to achieve asynchronous execution, such as: `go ShellRun(...)`, etc. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" new file mode 100644 index 00000000000..264d2fa733b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" @@ -0,0 +1,156 @@ +--- +slug: '/docs/components/os-gproc-signal' +title: 'Process - Signal' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,signal listening,process management,gproc component,GoFrame framework,signal handling,smooth program exit,Go language,signal callback,AddSigHandler] +description: "Using the gproc component in the GoFrame framework to implement signal listening and handling, addressing the issue of redundant signal processing logic among multiple components and the inability to exit programs smoothly. By unified signal registration and callback processing, it ensures that each component can effectively receive exit signals and perform deconstruction, making signal processing logic more rigorous." +--- + +## Introduction + +The `gproc` component provides a unified signal listening and callback processing feature, aimed at resolving the pain points of redundant signal processing logic among multiple different components in a program and the inability to destruct smoothly after receiving exit signals. Without a component for unified exit signal listening, when multiple components use `goroutine` for asynchronous signal listening, the main `goroutine` often exits directly upon receiving an exit signal or waits for an unpredictable amount of time to exit, which prevents the program from exiting smoothly and may trigger some unexpected issues. `gproc` enables each component to receive exit signals effectively and perform corresponding deconstruction processing through unified signal registration and callback processing, ensuring that the program's signal processing logic is more rigorous. + +Relevant methods: + +```go +// AddSigHandler adds custom signal handler for custom one or more signals. +func AddSigHandler(handler SigHandler, signals ...os.Signal) + +// AddSigHandlerShutdown adds custom signal handler for shutdown signals: +// syscall.SIGINT, +// syscall.SIGQUIT, +// syscall.SIGKILL, +// syscall.SIGTERM, +// syscall.SIGABRT. +func AddSigHandlerShutdown(handler ...SigHandler) + +// Listen blocks and does signal listening and handling. +func Listen() +``` + +Brief Introduction: + +- The `AddSigHandler` method is used for adding listeners and corresponding callback function registrations for specified signals. +- The `AddSigHandlerShutdown` method is used for adding listeners and corresponding callback function registrations for common process exit signals, allowing registration of multiple `SigHandler`. +- The `Listen` method is for blocking signal listening and automatically executing callback function calls. + +Let's look at two examples. + +## Example 1, Using Standard Library Signal Listening + +The common code logic for using the standard library signal listening mechanism is as follows: + +```go +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +func signalHandlerForMQ() { + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") + return + } +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + go signalHandlerForMQ() + + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) + return + } +} +``` + +We execute it via the `go run` command, then exit using the `Ctrl+C` shortcut (`Command+C` for Mac users). + +```bash +$ go run signal_handler.go +Process start, pid: 21928 +^CMainProcess is shutting down due to signal: interrupt +MQ is shutting down due to signal: interrupt +``` + +As you can see, unfortunately, the `MQ` `goroutine` is forcibly closed before it is fully exited. + +## Example 2, Using `gproc` Signal Listening + +The improved signal listening mechanism using the `gproc` component is as follows: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gproc" + "os" + "time" +) + +func signalHandlerForMQ(sig os.Signal) { + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") +} + +func signalHandlerForMain(sig os.Signal) { + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + gproc.AddSigHandlerShutdown( + signalHandlerForMQ, + signalHandlerForMain, + ) + gproc.Listen() +} +``` + +We execute it via the `go run` command, then exit using the `Ctrl+C` shortcut (`Command+C` for Mac users). + +```bash +$ go run signal_handler_gproc.go +Process start, pid: 22876 +^CMQ is shutting down due to signal: interrupt +MainProcess is shutting down due to signal: interrupt +MQ is shut down smoothly +``` + +See the difference! All signal listening functions complete normally, and then the process exits smoothly. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..f2c6f784173 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/components/os-gproc-example' +title: 'Process - Usage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, process management, Shell commands, main process, subprocess, gproc, multi-process management, golang, programming examples] +description: "Process management under the GoFrame framework, including how to execute Shell commands, determine the relationship between the main process and the subprocess, and the basic usage of multi-process management. Demonstrates the use of the gproc package through example code, such as creating subprocesses, managing existing processes, and implementing monitoring and control of specific processes in a Linux environment." +--- + +## Execute Shell Commands + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + r, err := gproc.ShellExec(gctx.New(), `sleep 3; echo "hello gf!";`) + fmt.Println("result:", r) + fmt.Println(err) +} +``` + +After execution, you can see that the program waits for 3 seconds, and the output result is: + +``` +result: hello gf! + + +``` + +## Main Process and Subprocess + +Processes created by the `gproc.Manager` object are marked as subprocesses by default. In a subprocess program, you can use the `gproc.IsChild()` method to determine if it is a subprocess. + +```go +package main + +import ( + "os" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + var ctx = gctx.New() + if gproc.IsChild() { + g.Log().Printf(ctx, "%d: Hi, I am child, waiting 3 seconds to die", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 1", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 2", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 3", gproc.Pid()) + } else { + m := gproc.NewManager() + p := m.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Start(ctx) + p.Wait() + g.Log().Printf(ctx, "%d: child died", gproc.Pid()) + } +} +``` + +After execution, the terminal prints the following result: + +```html +2018-05-18 14:35:41.360 28285: Hi, I am child, waiting 3 seconds to die +2018-05-18 14:35:42.361 28285: 1 +2018-05-18 14:35:43.361 28285: 2 +2018-05-18 14:35:44.361 28285: 3 +2018-05-18 14:35:44.362 28278: child died +``` + +## Multi-process Management + +In addition to creating subprocesses and managing them, `gproc` can also manage other processes not created by itself. `gproc` can manage multiple processes simultaneously. Here we demonstrate the management function with a single process as an example. + +1. We use the `gedit` software (a commonly used text editor on Linux) to open a file at random. In the process, we see that the process ID of this gedit is `28536`. + +```shell + $ ps aux | grep gedit + john 28536 3.6 0.6 946208 56412 ? Sl 14:39 0:00 gedit /home/john/Documents/text +``` + +2. Our program is as follows: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + pid := 28536 + m := gproc.NewManager() + m.AddProcess(pid) + m.KillAll() + m.WaitAll() + fmt.Printf("%d was killed\n", pid) +} +``` + +After execution, `gedit` is closed, and the terminal output is: + +``` +28536 was killed +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" new file mode 100644 index 00000000000..7abb4635dc3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" @@ -0,0 +1,84 @@ +--- +slug: '/docs/components/os-gproc-communication-between-processes' +title: 'Process - Communication' +sidebar_position: 1 +hide_title: true +keywords: [Process Management, Inter-process Communication, GoFrame, gproc, Socket Communication, Shared Memory, Signal, Pipeline, Shared File, GoFrame Framework] +description: "The gproc component in the GoFrame framework is used for the mechanism of inter-process communication, including common inter-process communication methods such as signals, pipelines, shared memory, etc. It emphasizes that gproc implements a stable and general inter-process communication method through Socket, and explains basic usage examples of message transmission using the Send and Receive methods." +--- +:::danger +The inter-process communication feature provided by the `gproc` component is experimental! +::: +> Do not communicate by sharing memory; instead, share memory by communicating. + +There are `5` common methods of inter-process communication: `pipe/signal/shared memory/shared file/Socket`, each typically having its preferred usage scenarios. + +- `Signal`: Signals are commonly used in `*nix` systems, with poor cross-platform capabilities and simple methods and contents for information transmission. +- `Pipe`: Including **ordinary pipes** and **named pipes**, this method is commonly used in parent-child process communication scenarios and is not very suitable for communication between unrelated processes. +- `Shared Memory/Shared File`: In terms of concurrent architecture design, we try to minimize the use of `lock mechanisms`, including shared memory (memory locks)/shared files (file locks), which actually require lock mechanisms to ensure the correctness of data flow. The maintenance complexity often outweighs the benefits brought by the lock mechanisms. + +The primary mechanism that `gproc` implements for inter-process communication is `Socket`, which has the advantage of functionality stability and general usage scenarios. + +The API for inter-process communication in `gproc` is extremely simple, achieved through the following two methods: + +```go +func Send(pid int, data []byte) error +func Receive() *Msg +``` + +We use the `Send` method to send data to a specified process (each call is equivalent to sending a message), and in the specified process, we can obtain the data through the `Receive` method. The `Receive` method offers a message queue-like approach to receive data from other processes. It will `block` and wait when the queue is empty. + +Let's look at a basic usage example of inter-process communication: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "os" + "time" +) + +var ( + ctx = gctx.New() +) + +func main() { + fmt.Printf("%d: I am child? %v\n", gproc.Pid(), gproc.IsChild()) + if gproc.IsChild() { + gtimer.SetInterval(ctx, time.Second, func(ctx context.Context) { + err := gproc.Send(gproc.PPid(), []byte(gtime.Datetime())) + if err != nil { + return + } + }) + select {} + } else { + m := gproc.NewManager() + p := m.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Start(ctx) + for { + msg := gproc.Receive() + fmt.Printf("receive from %d, data: %s\n", msg.SenderPid, string(msg.Data)) + } + } +} +``` + +In this example, our main process creates a child process upon startup. The child process sends the current time to the main process every second, and the main process outputs the received parameters from the child process to the terminal. After execution, the content output in the terminal is as follows: + +``` +29978: I am child? false +29984: I am child? true +receive from 29984, data: 2018-05-18 15:01:00 +receive from 29984, data: 2018-05-18 15:01:01 +receive from 29984, data: 2018-05-18 15:01:02 +receive from 29984, data: 2018-05-18 15:01:03 +receive from 29984, data: 2018-05-18 15:01:04 +... +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..ee35fa435f4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,66 @@ +--- +slug: '/docs/components/os-gproc-tracing' +title: 'Process - Tracing' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Process Management, Tracing, OpenTelemetry, Cross-Process, Main Process, Subprocess, gproc, os-gproc-tracing] +description: "Methods for process management and tracinging using the GoFrame framework. By utilizing the OpenTelemetry specification, it supports cross-process tracinging features, making it very suitable for temporarily running processes. The example code demonstrates how to start a subprocess in the main process and pass trace information, showcasing the powerful capabilities of the GoFrame framework in process management." +--- + +## Introduction + +The process management component supports cross-process tracinging features, which are especially useful for some temporarily running processes. The overall tracinging of the framework adopts the `OpenTelemetry` specification. + +## Usage Example + +### Main Process + +`main.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is main process`) + if err := gproc.ShellRun(ctx, `go run sub.go`); err != nil { + panic(err) + } +} +``` + +### Subprocess + +`sub.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is sub process`) +} +``` + +### Execution Result + +After execution, the terminal outputs the following: + +```bash +$ go run main.go +2022-06-21 20:35:06.196 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is main process +2022-06-21 20:35:07.482 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is sub process +``` + +As you can see, the trace information has been automatically passed to the subprocess. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" new file mode 100644 index 00000000000..0e568c07dde --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcfg' +title: 'Configuration' +sidebar_position: 11 +hide_title: true +keywords: [Configuration Management, GoFrame, gcfg module, core components, application configuration, GoFrame framework, flexible configuration, development framework, system configuration, configuration files] +description: "Use the gcfg module in the GoFrame framework for configuration management. The gcfg module supports flexible application and system configuration, helping developers to manage and organize configuration files more efficiently, ensuring the stability and flexibility of the application." +--- + +Configuration management is implemented by the `gcfg` module. For details, please refer to the [Configuration](../../核心组件/配置管理/配置管理.md) section. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..d992f8060bd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" @@ -0,0 +1,16 @@ +--- +slug: '/docs/components' +title: 'Components Category' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Core Module, Community Module, gf Main Repository, Module List, Lightweight Framework, Module Maintenance, gogf Space, Module Documentation] +description: "This document introduces the module list in the GoFrame framework, including core modules and community modules. Core modules are maintained by the gf main repository, simple and easy to use, while community modules are contributed by the community and stored under the gogf space. For detailed information, please refer to the source README file and related core module documentation." +--- + +The module list contains documentation introducing most of the core and community modules of the framework. + + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" new file mode 100644 index 00000000000..b013c603655 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gbase64' +title: 'BASE64' +sidebar_position: 2 +hide_title: true +keywords: [BASE64,GoFrame,Encoding,Decoding,gbase64,GoFrame Framework,API Documentation,Encoding Parsing,Go Language,Software Development] +description: "Use BASE64 encoding and decoding functions, based on the GoFrame framework's gbase64 package, providing encoding and decoding methods. Learn more through GitHub links and API documentation, a practical component for Go language developers." +--- + +`BASE64` Encoding and Parsing. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gbase64" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbase64](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbase64) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" new file mode 100644 index 00000000000..ef8d522e8cd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-ghtml' +title: 'HTML' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, HTML Encoding, ghtml, Go Language, Encoding and Decoding, API Documentation, Web Development, Software Development, Programming] +description: "Perform HTML encoding and decoding in projects built with the GoFrame framework. By importing relevant packages, HTML content can be easily processed. An official API documentation link is provided at the end to further help developers understand and utilize." +--- + +HTML encoding and decoding. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/ghtml" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghtml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghtml) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" new file mode 100644 index 00000000000..9ccdc8a4123 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gini' +title: 'INI' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame Framework,INI,Data Format,Encoding,Decoding,gini,API Documentation,Usage,pkg.go.dev] +description: "Methods for encoding and decoding INI data format using the GoFrame framework. It provides a detailed explanation on how to perform INI encoding and decoding through the gini package in the GoFrame framework and offers links to related API documentation for developers' reference." +--- + +INI data format encoding and decoding. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gini" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gini](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gini) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" new file mode 100644 index 00000000000..6dfcfcd049b --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gtoml' +title: 'TOML' +sidebar_position: 9 +hide_title: true +keywords: [TOML, Encoding and Decoding, gtoml, GoFrame, Data Format, GoFrame Framework, API Documentation, Decode, gogf, Import] +description: "Methods for encoding and decoding the TOML data format using the GoFrame framework. By introducing the gogf gtoml package, it is easy to perform encoding and decoding operations on data in TOML format. The article also provides a link to the API documentation to help developers gain deeper insights into the usage details of gtoml." +--- + +`TOML` data format encoding and decoding. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gtoml" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gtoml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gtoml) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" new file mode 100644 index 00000000000..0d92fce8dd4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/components/encoding-gurl' +title: 'URL' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame Framework,URL Encoding and Decoding,gurl,URL Parameter Construction,URL Parameter Encoding,URL Parameter Decoding,URL Parsing,Encoding and Decoding,Go Language] +description: "Perform URL encoding and decoding using the gurl package in the GoFrame framework, including how to construct URL parameters, encode and decode URL parameters, and parse URLs to obtain their different components. These features are very useful for network programming and data transmission in the Go language, suitable for developers with similar needs to reference." +--- + +`URL` encoding and decoding. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gurl" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gurl](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gurl) + +## `URL` Parameter Construction + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "net/url" +) + +func main() { + // Construct URL parameters + values := url.Values{} + values.Add("name", "gopher") + values.Add("limit", "20") + values.Add("page", "7") + + // Generate URL encoded query string limit=20&name=gopher&page=7 + urlStr := gurl.BuildQuery(values) + fmt.Println(urlStr) +} +``` + +After execution, the output will be: + +``` +limit=20&name=gopher&page=7 +``` + +## `URL` Parameter Encoding and Decoding + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "log" +) + +func main() { + // Encode string for safe placement in URL queries. + encodeStr := gurl.Encode("limit=20&name=gopher&page=7") + fmt.Println(encodeStr) + + // Perform URL decoding + decodeStr, err := gurl.Decode("limit%3D20%26name%3Dgopher%26page%3D7") + if err != nil { + log.Fatal(err) + } + fmt.Println(decodeStr) +} +``` + +After execution, the output will be: + +``` +limit%3D20%26name%3Dgopher%26page%3D7 +limit=20&name=gopher&page=7 +``` + +## Parsing `URL` + +`component` parameter value options: + +| Parameter Value | Description | +| --- | --- | +| -1 | all | +| 1 | scheme | +| 2 | host | +| 4 | port | +| 8 | user | +| 16 | pass | +| 32 | path | +| 64 | query | +| 128 | fragment | + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "log" +) + +func main() { + // Parse URL and return its components + data, err := gurl.ParseURL("http://127.0.0.1:8199/goods?limit=20&name=gopher&page=7", -1) + if err != nil { + log.Fatal(err) + } + fmt.Println(data) + fmt.Println(data["host"]) + fmt.Println(data["query"]) + fmt.Println(data["path"]) + fmt.Println(data["scheme"]) + fmt.Println(data["fragment"]) +} +``` + +After execution, the output will be: + +``` +map[fragment: host:127.0.0.1 pass: path:/goods port:8199 query:limit=20&name=gopher&page=7 scheme:http user:] +127.0.0.1 +limit=20&name=gopher&page=7 +/goods +http +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" new file mode 100644 index 00000000000..d4f97556362 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gxml' +title: 'XML' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame framework, XML, XML encoding and decoding, data format, gxml, encoding parsing, Go language, programming, interface documentation] +description: "Using gxml in the GoFrame framework for encoding and decoding XML data formats. Provides basic usage of the gxml library and links to related interface documentation to help developers handle XML data easily in Go language projects. Specific implementations include import statements and examples of calling the gxml library." +--- + +XML data format encoding and decoding. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gxml" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gxml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gxml) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" new file mode 100644 index 00000000000..145df4d385d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gyaml' +title: 'YAML' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame Framework, YAML, Encoding, Decoding, gyaml, Data Format, Go Language, API Documentation, Encoding Parsing] +description: "Methods for encoding and decoding YAML data format, using the gyaml library under the GoFrame framework for encoding and parsing. By importing the github.com/gogf/gf/v2/encoding/gyaml package, you can easily handle YAML formatted data. In addition, links to API documentation are provided for user reference." +--- + +`YAML` data format encoding and parsing. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gyaml" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gyaml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gyaml) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" new file mode 100644 index 00000000000..b9d9d920b1c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" @@ -0,0 +1,182 @@ +--- +slug: '/docs/components/encoding-gbinary' +title: 'Binary' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gbinary, binary encoding/decoding, data conversion, network communication, data encoding, decoding, integer processing, bit manipulation] +description: "The gbinary package in the GoFrame framework provides encoding and decoding functionality for converting between binary data and various data types. It is widely used in network communication and data file operations. It supports precise bit processing for integer data and offers a range of encoding and decoding interfaces to ensure efficient data conversion across different types and platforms." +--- + +## Introduction + +The `GoFrame` framework provides an independent binary data operation package `gbinary`, which is mainly used for mutual conversion between various data types and `[]byte` binary types; it also provides precise bit processing functions for integer data. It is commonly used for data encoding/decoding during network communication and data file operations. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gbinary" +``` + +**Interface Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbinary](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbinary) + +The interface documentation for binary data structure conversion processing is as follows: + +```go +func Encode(vs ...interface{}) ([]byte, error) +func EncodeInt(i int) []byte +func EncodeInt8(i int8) []byte +func EncodeInt16(i int16) []byte +func EncodeInt32(i int32) []byte +func EncodeInt64(i int64) []byte +func EncodeUint(i uint) []byte +func EncodeUint8(i uint8) []byte +func EncodeUint16(i uint16) []byte +func EncodeUint32(i uint32) []byte +func EncodeUint64(i uint64) []byte +func EncodeBool(b bool) []byte +func EncodeFloat32(f float32) []byte +func EncodeFloat64(f float64) []byte +func EncodeString(s string) []byte + +func Decode(b []byte, vs ...interface{}) error +func DecodeToInt(b []byte) int +func DecodeToInt8(b []byte) int8 +func DecodeToInt16(b []byte) int16 +func DecodeToInt32(b []byte) int32 +func DecodeToInt64(b []byte) int64 +func DecodeToUint(b []byte) uint +func DecodeToUint8(b []byte) uint8 +func DecodeToUint16(b []byte) uint16 +func DecodeToUint32(b []byte) uint32 +func DecodeToUint64(b []byte) uint64 +func DecodeToBool(b []byte) bool +func DecodeToFloat32(b []byte) float32 +func DecodeToFloat64(b []byte) float64 +func DecodeToString(b []byte) string +``` + +The interface documentation for bit-level processing support is as follows: + +```go +func EncodeBits(bits []Bit, i int, l int) []Bit +func EncodeBitsWithUint(bits []Bit, ui uint, l int) []Bit +func EncodeBitsToBytes(bits []Bit) []byte +func DecodeBits(bits []Bit) uint +func DecodeBitsToUint(bits []Bit) uint +func DecodeBytesToBits(bs []byte) []Bit +``` + +The `Bit` type represents a binary digit (0 or 1), defined as follows: + +```go +type Bit int8 +``` + +## Usage Example + +Let's look at a comprehensive example of binary operations, demonstrating most binary conversion operations. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + // Use gbinary.Encode to perform binary packing on basic data types + if buffer := gbinary.Encode(18, 300, 1.01); buffer != nil { + // glog.Error(err) + } else { + fmt.Println(buffer) + } + + // Use gbinary.Decode for integer binary unpacking. Note that the second and subsequent parameters are pointers to fixed-length integer variables. + // Examples include: int8/16/32/64, uint8/16/32/64, float32/64 + // 1.01 here defaults to float64 type (on a 64-bit system) + if buffer := gbinary.Encode(18, 300, 1.01); buffer != nil { + // glog.Error(err) + } else { + var i1 int8 + var i2 int16 + var f3 float64 + if err := gbinary.Decode(buffer, &i1, &i2, &f3); err != nil { + glog.Error(gctx.New(), err) + } else { + fmt.Println(i1, i2, f3) + } + } + + // Encode/Decode int, automatically recognize variable length + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(1))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(300))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(70000))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(2000000000))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(500000000000))) + + // Encode/Decode uint, automatically recognize variable length + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(1))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(300))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(70000))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(2000000000))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(500000000000))) + + // Encode/Decode int8/16/32/64 + fmt.Println(gbinary.DecodeToInt8(gbinary.EncodeInt8(int8(100)))) + fmt.Println(gbinary.DecodeToInt16(gbinary.EncodeInt16(int16(100)))) + fmt.Println(gbinary.DecodeToInt32(gbinary.EncodeInt32(int32(100)))) + fmt.Println(gbinary.DecodeToInt64(gbinary.EncodeInt64(int64(100)))) + + // Encode/Decode uint8/16/32/64 + fmt.Println(gbinary.DecodeToUint8(gbinary.EncodeUint8(uint8(100)))) + fmt.Println(gbinary.DecodeToUint16(gbinary.EncodeUint16(uint16(100)))) + fmt.Println(gbinary.DecodeToUint32(gbinary.EncodeUint32(uint32(100)))) + fmt.Println(gbinary.DecodeToUint64(gbinary.EncodeUint64(uint64(100)))) + + // Encode/Decode string + fmt.Println(gbinary.DecodeToString(gbinary.EncodeString("I'm string!"))) +} +``` + +The result of the above program execution is: + +``` +[18 44 1 41 92 143 194 245 40 240 63] +18 300 1.01 +1 +300 +70000 +2000000000 +500000000000 +1 +300 +70000 +2000000000 +500000000000 +100 +100 +100 +100 +100 +100 +100 +100 +I'm string! +``` + +1. **Encoding** + +The `gbinary.Encode` method is a very powerful and flexible method that can convert all basic types to binary type (`[]byte`). Inside the `gbinary.Encode` method, the variable length is calculated automatically, using the smallest binary length to store the binary value of the variable. For example, for an `int` type variable with a value of `1`, `gbinary.Encode` will use only `1` byte to store it, while a variable with a value of `300` will use `2` bytes to store it, minimizing the storage space of the binary result. Therefore, when parsing, pay great attention to the length of `[]byte`. It is recommended to use fixed-length basic types like `int8/16/32/64` when encoding/decoding binary to ensure the correct variable form is used during parsing, reducing the chance of errors. + +The `gbinary` package also provides a series of `gbinary.Encode*` methods for converting basic data types to binary. In particular, `gbinary.EncodeInt/gbinary.EncodeUint` internally recognizes the variable value size and returns a variable-length `[]byte` value, with a length range of `1/2/4/8`. + +2. **Decoding** + +In binary type parsing operations, the length of the binary (`[]byte` length) is crucial. Only with the correct length can correct parsing be executed. Therefore, the length of the variables given to `gbinary.Decode` must be of a specific length type, such as `int8/16/32/64`, `uint8/16/32/64`, or `float32/64`. If the second variable address given corresponds to the `int/uint` variable type, which has an indeterminate length, parsing will fail. + +Furthermore, the `gbinary` package also provides a series of `gbinary.DecodeTo*` methods for converting binary to specific data types. Importantly, the `gbinary.DecodeToInt/gbinary.DecodeToUint` methods automatically recognize and parse the binary length, supporting binary parameter lengths in the range of `1-8`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" new file mode 100644 index 00000000000..4087e32c2f6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gcompress' +title: 'Compression' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame framework,gcompress,compression,decompression,binary data,Zlib,GZip,API documentation,GoFrame encoding] +description: "Compression and decompression functionality for binary data, specifically the use of Zlib and GZip algorithms. Through the GoFrame framework, users can easily implement data compression and decompression. For specific implementations, please refer to the API documentation. This page provides detailed call examples and links to related technical documentation to help developers get started quickly." +--- + +Binary data compression/decompression, supporting `Zlib`/ `GZip` algorithms. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gcompress" +``` + +**API documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcompress](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcompress) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" new file mode 100644 index 00000000000..77823c74e98 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" @@ -0,0 +1,61 @@ +--- +slug: '/docs/components/encoding-gcharset' +title: 'Charset Conversion' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Character Encoding Conversion, gcharset, GBK, UTF-8, Character Set Conversion, Encoding Conversion Module, Chinese Character Set, Program Example] +description: "The character encoding conversion module gcharset in the GoFrame framework supports conversions of common character sets such as GBK and UTF-8, providing developers with flexible character set compatibility. By importing the relevant package, developers can achieve conversions between different character sets to meet the needs of multilingual and international users, enhancing the internationalization and localization capabilities of applications." +--- + +The `GoFrame` framework provides a powerful character encoding conversion module `gcharset`, supporting mutual conversion of common character sets. + +Supported character sets: + +| Encoding | Character Set | +| --- | --- | +| **Chinese** | `GBK/GB18030/GB2312/Big5` | +| **Japanese** | `EUCJP/ISO2022JP/ShiftJIS` | +| **Korean** | `EUCKR` | +| **Unicode** | `UTF-8/UTF-16/UTF-16BE/UTF-16LE` | +| **Other Encodings** | `macintosh/IBM*/Windows*/ISO-*` | + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gcharset" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcharset](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcharset) + +**Usage Example**: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gcharset" +) + +func main() { + var ( + src = "~{;(F#,6@WCN^O`GW!#" + srcCharset = "GB2312" + dstCharset = "UTF-8" + ) + str, err := gcharset.Convert(dstCharset, srcCharset, src) + if err != nil { + panic(err) + } + fmt.Println(str) +} +``` + +After execution, the terminal output is: + +``` +花间一壶酒,独酌无相亲。 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" new file mode 100644 index 00000000000..bb9563d30eb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" @@ -0,0 +1,110 @@ +--- +slug: '/docs/components/encoding-ghash' +title: 'Classic Hash Functions' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Hash Functions, Go Language, ghash, Encoding, Benchmark, Repeated Tests, uint32, uint64] +description: "Implementation of classic hash functions in Go language, providing ways to use hash functions for uint32 and uint64 types. Through the GoFrame framework, users can implement hash functions more efficiently. The document includes detailed API documentation and benchmark results to help users optimize and understand encoding performance. Additionally, simple repeatability tests demonstrate the characteristics and performance of different hash functions." +--- + +## Introduction + +Common classic hash functions implemented in Go language are provided, offering hash functions for `uint32` and `uint64` types. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/ghash" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghash](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghash) + +## Benchmark + +``` +goos: darwin +goarch: amd64 +pkg: github.com/gogf/gf/v2/encoding/ghash +cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz +Benchmark_BKDR +Benchmark_BKDR-12 39315165 26.88 ns/op +Benchmark_BKDR64 +Benchmark_BKDR64-12 62891215 22.61 ns/op +Benchmark_SDBM +Benchmark_SDBM-12 49689925 25.40 ns/op +Benchmark_SDBM64 +Benchmark_SDBM64-12 48860472 24.38 ns/op +Benchmark_RS +Benchmark_RS-12 39463418 25.52 ns/op +Benchmark_RS64 +Benchmark_RS64-12 53318370 19.45 ns/op +Benchmark_JS +Benchmark_JS-12 53751033 23.20 ns/op +Benchmark_JS64 +Benchmark_JS64-12 62440287 19.25 ns/op +Benchmark_PJW +Benchmark_PJW-12 42439626 27.85 ns/op +Benchmark_PJW64 +Benchmark_PJW64-12 37491696 33.28 ns/op +Benchmark_ELF +Benchmark_ELF-12 38034584 31.74 ns/op +Benchmark_ELF64 +Benchmark_ELF64-12 44047201 27.58 ns/op +Benchmark_DJB +Benchmark_DJB-12 46994352 22.60 ns/op +Benchmark_DJB64 +Benchmark_DJB64-12 61980186 19.19 ns/op +Benchmark_AP +Benchmark_AP-12 29544234 40.58 ns/op +Benchmark_AP64 +Benchmark_AP64-12 28123783 42.48 ns/op +``` + +## Repeated Tests + +Test results are influenced by the test content's associativity and randomness. Here, I perform simple repeatability tests by traversing the range of `uint64` values, which is not rigorous in itself and is meant for fun reference only. + +```go +package main + +import ( + "encoding/binary" + "fmt" + "math" + + "github.com/gogf/gf/v2/encoding/ghash" +) + +func main() { + var ( + m = make(map[uint64]struct{}) + b = make([]byte, 8) + ok bool + hash uint64 + ) + for i := uint64(0); i < math.MaxUint64; i++ { + binary.LittleEndian.PutUint64(b, i) + hash = ghash.PJW64(b) + if _, ok = m[hash]; ok { + fmt.Println("repeated found:", i) + break + } + m[hash] = struct{}{} + } +} +``` + +The test results are as follows: + +| Hash Function | Repeat Location | Remarks | +| --- | --- | --- | +| `AP64` | `8388640` | | +| `BKDR64` | `33536` | | +| `DJB64` | `8448` | | +| `ELF64` | `4096` | | +| `JS64` | `734` | | +| `PJW64` | `2` | | +| `RS64` | - | 32G Memory OOM | +| `SDBM64` | - | 32G Memory OOM | \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" new file mode 100644 index 00000000000..38d7426a0dd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/encoding' +title: 'Codec' +sidebar_position: 3 +hide_title: true +keywords: [encoding, decoding, data conversion, information processing, programming technology, character encoding, GoFrame, GoFrame framework, encoding standards, data compression] +description: "The fundamental concepts and applications of encoding and decoding, exploring how to achieve efficient data conversion and information processing using the GoFrame framework. It covers various encoding standards and their practical applications in programming technology, helping developers master character encoding and data compression techniques." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" new file mode 100644 index 00000000000..23c2f504abc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/encoding-gjson-faq' +title: 'General Codec - FAQ' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame, GoFrame Framework, JSON, Encoding, Decoding, Large Number Precision, gjson, Go Language, FAQ, Problem Solving] +description: "The potential issue of loss of large number precision when handling JSON data with the GoFrame framework, along with specific solution example code. By adjusting gjson options, precision loss can be avoided to ensure data accuracy. Additionally, related links are provided for further reference." +--- + +## Precision Loss of Large Numbers in JSON + +### Problem Description + +```go +package main + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + str := `{"Id":1492404095703580672,"Name":"Jason"}` + strJson := gjson.New(str) + g.Dump(strJson) +} +``` + +The output is: + +``` +"{\"Id\":1492404095703580700,\"Name\":\"Jason\"}" +``` + +### Solution + +```go +package main + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + str := `{"Id":1492404095703580672,"Name":"Jason"}` + strJson := gjson.NewWithOptions(str, gjson.Options{ + StrNumber: true, + }) + g.Dump(strJson) +} +``` + +The output is: + +``` +"{\"Id\":1492404095703580672,\"Name\":\"Jason\"}" +``` + +### Related Links + +- [https://github.com/gogf/gf/issues/1603](https://github.com/gogf/gf/issues/1603) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..2a221c0a372 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" @@ -0,0 +1,37 @@ +--- +slug: '/docs/components/encoding-gjson-struct-converting' +title: 'General Codec - Struct' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame Framework, Struct Conversion, JSON Parsing, Data Format Conversion, Object Conversion, gjson Library, Encoding, Go Language, Data Scanning, User Struct] +description: "Using the Struct method in the GoFrame framework to convert JSON data into a specified data format or object. The example demonstrates how to parse JSON data and use the gjson library to scan it as a custom user struct. This type of data format conversion is very useful when dealing with complex data structures, especially in Go programming." +--- + +## `Struct` Conversion + +The `Struct` method is used to convert the entire data content contained in `Json` into a specified data format or object. + +``` +data := + ` +{ + "count" : 1, + "array" : ["John", "Ming"] +}` +if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) +} else { + type Users struct { + Count int + Array []string + } + users := new(Users) + if err := j.Scan(users); err != nil { + panic(err) + } + fmt.Printf(`%+v`, users) +} + +// Output: +// &{Count:1 Array:[John Ming]} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" new file mode 100644 index 00000000000..be071fe2dc7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" @@ -0,0 +1,35 @@ +--- +slug: '/docs/components/encoding-gjson' +title: 'General Codec' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, gjson, data encoding, data parsing, data format conversion, JSON, XML, dynamic creation, data hierarchy retrieval] +description: "The gjson module in the GoFrame framework provides powerful data encoding and parsing capabilities, supporting the mutual conversion of multiple data formats, including JSON, XML, INI, YAML, TOML, etc. The gjson module is especially suitable for scenarios that require data hierarchy retrieval and dynamic creation or modification of data objects. It also supports runtime data modification, making it an ideal choice for building websites." +--- + +The `gjson` module implements powerful data encoding/parsing functions, supporting data hierarchy retrieval, dynamic creation and modification of `Json` objects, and features such as parsing and conversion of common data formats. + +Features: + +1. Supports data hierarchy retrieval; +2. Supports runtime data modification; +3. Supports dynamic creation of hierarchical data structures and conversion to supported data formats; +4. Supports conversion among `JSON`, `XML`, `INI`, `YAML/YML`, `TOML`, `PROPERTIES`, `Struct` data formats; +:::info +Note that the `gjson` package supports reading, writing, and conversion of multiple data formats, not limited to the `json` format. +::: +## **Usage**: + +```go +import "github.com/gogf/gf/v2/encoding/gjson" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" new file mode 100644 index 00000000000..0f5785579d7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" @@ -0,0 +1,77 @@ +--- +slug: '/docs/components/encoding-gjson-dynamic-creating-and-editing' +title: 'General Codec - Dynamic Creation and Modification' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame framework, gjson, dynamic creation, dynamic modification, data structure, JSON parsing, encoding, decoding, Go language] +description: "Using gjson in the GoFrame framework for dynamic data creation and modification. gjson not only parses and retrieves unknown data structures flexibly but also dynamically creates and edits data content. Through specific examples, methods for setting data, creating arrays, and modifying JSON content are demonstrated, making the encoding and parsing of data structures more flexible and convenient." +--- + +`gjson` can not only flexibly parse and retrieve unknown data structure content but also dynamically create and modify data structure content. + +## Dynamic Creation + +### Example 1, Simple Usage + +```go +func main() { + j := gjson.New(nil) + j.Set("name", "John") + j.Set("score", 99.5) + fmt.Printf( + "Name: %s, Score: %v\n", + j.Get("name").String(), + j.Get("score").Float32(), + ) + fmt.Println(j.MustToJsonString()) + + // Output: + // Name: John, Score: 99.5 + // {"name":"John","score":99.5} +} +``` + +### Example 2, Creating Arrays + +```go +func main() { + j := gjson.New(nil) + for i := 0; i < 5; i++ { + j.Set(fmt.Sprintf(`%d.id`, i), i) + j.Set(fmt.Sprintf(`%d.name`, i), fmt.Sprintf(`student-%d`, i)) + } + fmt.Println(j.MustToJsonString()) + + // Output: + // [{"id":0,"name":"student-0"},{"id":1,"name":"student-1"},{"id":2,"name":"student-2"},{"id":3,"name":"student-3"},{"id":4,"name":"student-4"}] +} +``` + +## Dynamic Modification + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 59} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.Set("users.list.1.score", 100) + fmt.Println("John Score:", j.Get("users.list.1.score").Float32()) + fmt.Println(j.MustToJsonString()) + } + // Output: + // John Score: 100 + // {"users":{"count":2,"list":[{"name":"Ming","score":60},{"name":"John","score":100}]}} +} +``` + +`JSON` data can be read through `gjson`, and the internal variables can be changed using the `Set` method. Of course, you can also `add/delete` content. When you need to delete content, just set the value to `nil`. The runtime modification features of the `gjson` package are very powerful, and with the support of this feature, the encoding/decoding of various data structures becomes extremely flexible and convenient. diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" new file mode 100644 index 00000000000..5b857c2c0e8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/components/encoding-gjson-creation' +title: 'General Codec - Creation' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gjson, Object Creation, JSON, XML, Data Format, Struct Objects, Go Development, Data Parsing] +description: "Using the gjson module of the GoFrame framework to create JSON objects. Supports multiple data formats such as JSON and XML, and provides New and Load* methods for users to call. Demonstrates methods for creating JSON objects from JSON, XML, and struct objects with sample code to help developers understand and apply." +--- + +The `gjson` module not only supports creating `Json` objects from the basic `JSON` data format but also supports creating `Json` objects from common data formats. Supported data formats include: `JSON`, `XML`, `INI`, `YAML`, `TOML`, `PROPERTIES`. Additionally, it also supports creating `Json` objects directly from `struct` objects. + +The commonly used methods for object creation are `New` and `Load*`. For more methods, please refer to the interface documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +### Creating with the `New` Method + +#### Creating from `JSON` Data + +```go +jsonContent := `{"name":"john", "score":"100"}` +j := gjson.New(jsonContent) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` + +#### Creating from `XML` Data + +```go +jsonContent := `john100` +j := gjson.New(jsonContent) +// Note that there's root node in the XML content. +fmt.Println(j.Get("doc.name")) +fmt.Println(j.Get("doc.score")) +// Output: +// john +// 100 +``` + +#### Creating from a `Struct` Object + +```go +type Me struct { + Name string `json:"name"` + Score int `json:"score"` +} +me := Me{ + Name: "john", + Score: 100, +} +j := gjson.New(me) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` + +#### Custom `Struct` Conversion Tags + +```go +type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string +} +me := Me{ + Name: "john", + Score: 100, + Title: "engineer", +} +// The parameter specifies custom priority tags for struct conversion to map, +// multiple tags joined with char ','. +j := gjson.NewWithTag(me, "tag") +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +fmt.Println(j.Get("Title")) +// Output: +// john +// 100 +// engineer +``` + +### Creating with the `Load*` Method + +The most commonly used methods are `Load` and `LoadContent`, the former reads from a file path, and the latter creates a `Json` object from given content. The methods automatically recognize the data format internally, and automatically parse and convert them to `Json` objects. + +#### Creating with the `Load` Method + +1. `JSON` File + +```go + jsonFilePath := gtest.DataPath("json", "data1.json") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) +``` + +2. `XML` File + +```go + jsonFilePath := gtest.DataPath("xml", "data1.xml") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("doc.name")) + fmt.Println(j.Get("doc.score")) +``` + +#### Creating with `LoadContent` + +```go +jsonContent := `{"name":"john", "score":"100"}` +j, _ := gjson.LoadContent(jsonContent) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" new file mode 100644 index 00000000000..67a2e5144f1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" @@ -0,0 +1,129 @@ +--- +slug: '/docs/components/encoding-gjson-nested-visiting' +title: 'General Codec - Nested Visiting' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gjson, Data Hierarchical Access, JSON Decoding, Go Language, Encoding, Hierarchical Separator, Conflict Detection, Performance Optimization] +description: "Using gjson for hierarchical access in the GoFrame framework, accessing unknown data structures through flexible hierarchical separators. Includes setting custom separator characters and handling cases where key names themselves contain hierarchical symbols. Additionally, discusses the impact of modifying reference type variables in Go language and its effect on underlying data." +--- + +## Nested Visiting + +`gjson` supports hierarchical retrieval access to data content, with the default hierarchical separator being ".". This feature makes it very convenient for developers to flexibly access unknown data structures. + +### Example 1, Basic Usage + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + fmt.Println("John Score:", j.Get("users.list.1.score")) + } + // Output: + // John Score: 99.5 +} +``` + +As you can see, the `gjson.Json` object can retrieve the corresponding variable information through very flexible hierarchical filtering functions ( `j.GetFloat32("users.list.1.score")`). + +### Example 2, Custom Hierarchical Separator + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.SetSplitChar('#') + fmt.Println("John Score:", j.Get("users#list#1#score")) + } + // Output: + // John Score: 99.5 +} +``` + +As you can see, we can set our custom separator using the `SetSplitChar` method. + +### Example 3, Handling the Case Where Key Names Themselves Contain Hierarchical Symbols "." + +```go +func main() { + data := + `{ + "users" : { + "count" : 100 + }, + "users.count" : 101 + }` + if j, err := gjson.DecodeToJson(data); err != nil { + glog.Error(gctx.New(), err) + } else { + j.SetViolenceCheck(true) + fmt.Println("Users Count:", j.Get("users.count")) + } + // Output: + // Users Count: 101 +} +``` + +The result printed after running is `101`. When the key name contains ".", we can set conflict detection through `SetViolenceCheck`, and the retrieval priority will follow: key name->hierarchical, so there won't be ambiguity. However, when the conflict detection switch is on, the retrieval efficiency will be lower, and it is off by default. + +## Precautions + +As everyone knows, in `Golang`, the `map/slice` type is actually a "reference type" (also known as a "pointer type"), so when you modify the key-value/index items of this type of variable, it will also modify its corresponding underlying data. + +For efficiency, the `gjson` package does not make value copies for the data type when some retrieval methods return `map/slice`, so when you modify the returned data, it will also modify the underlying data of `gjson`. + +For example: + +```go +func main() { + jsonContent := `{"map":{"key":"value"}, "slice":[59,90]}` + j, _ := gjson.LoadJson(jsonContent) + m := j.Get("map") + mMap := m.Map() + fmt.Println(mMap) + + // Change the key-value pair. + mMap["key"] = "john" + + // It changes the underlying key-value pair. + fmt.Println(j.Get("map").Map()) + + s := j.Get("slice") + sArray := s.Array() + fmt.Println(sArray) + + // Change the value of specified index. + sArray[0] = 100 + + // It changes the underlying slice. + fmt.Println(j.Get("slice").Array()) + + // output: + // map[key:value] + // map[key:john] + // [59 90] + // [100 90] +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..201d7e0c6f1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/components/encoding-gjson-format-converting' +title: 'General Codec - Data Format' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame Framework, Data Format Conversion, gjson, JSON, XML, YAML, TOML, Codec, API Documentation] +description: "Data format conversion using the GoFrame framework, including mutual conversion among JSON, XML, YAML, TOML, and other formats, with an example code provided. Using the gjson library, the Must* methods can ensure safe data format conversion." +--- + +There are many methods for data format conversion, please refer to the API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +It is important to note that there are some `Must*` conversion methods which ensure conversion to the specified data format, otherwise a `panic` occurs. + +Let's look at an example to illustrate. + +``` +data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } +}` +if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) +} else { + fmt.Println("JSON:") + fmt.Println(j.MustToJsonString()) + fmt.Println("======================") + + fmt.Println("XML:") + fmt.Println(j.MustToXmlString()) + fmt.Println("======================") + + fmt.Println("YAML:") + fmt.Println(j.MustToYamlString()) + fmt.Println("======================") + + fmt.Println("TOML:") + fmt.Println(j.MustToTomlString()) +} + +// Output: +// JSON: +// {"users":{"array":["John","Ming"],"count":1}} +// ====================== +// XML: +// JohnMing1 +// ====================== +// YAML: +// users: +// array: +// - John +// - Ming +// count: 1 +// +// ====================== +// TOML: +// [users] +// array = ["John", "Ming"] +// count = 1.0 +``` + +`gjson` supports converting `JSON` to other common data formats, currently supporting mutual conversion among: `JSON`, `XML`, `INI`, `YAML/YML`, `TOML`, `PROPERTIES`, and `Struct` data formats. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..b8b15d72290 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,2651 @@ +--- +slug: '/docs/components/encoding-gjson-funcs' +title: 'General Codec - Methods' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame, Json Object, Encoding and Decoding, gjson Methods, GoFrame Framework, Data Level Access, Concurrency Safety, NewWithTag, Code Examples, Content Formats] +description: "Various methods for general encoding and decoding using the GoFrame framework, including the creation of Json objects, data access, and format conversion. Provides detailed descriptions and code examples of methods such as New, Load, Encode, and Decode, helping to understand efficient data format handling in GoFrame." +--- +:::tip +The following list of commonly used methods may be updated later than new code features. For more methods and examples, please refer to the code documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) +::: +### `New` + +- Description: `New` can create a `Json` object with any type of value `data`. However, due to data access reasons, `data` should be a `map` or `slice`, otherwise it is meaningless. + +- Note: The `safe` parameter determines whether the `Json` object is concurrent-safe, defaulting to `false`. +- Format: + +```go +func New(data interface{}, safe ...bool) *Json +``` + +- Example: + +```go +func ExampleNew() { + jsonContent := `{"name":"john", "score":"100"}` + j := gjson.New(jsonContent) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +### `NewWithTag` + +- Description: `NewWithTag` can create a `Json` object with any type of value `data`. However, due to data access reasons, `data` should be a `map` or `slice`, otherwise it is meaningless. + +- Note: The `tgts` parameter specifies the priority of tag names when converting structs to maps, with multiple tags separated by `','`. +- The `safe` parameter determines whether the `Json` object is concurrent-safe, defaulting to `false`. +- Format: + +```go +func NewWithTag(data interface{}, tags string, safe ...bool) *Json +``` + +- Example: + +```go +func ExampleNewWithTag() { + type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string + } + me := Me{ + Name: "john", + Score: 100, + Title: "engineer", + } + j := gjson.NewWithTag(me, "tag", true) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(j.Get("Title")) + + // Output: + // john + // 100 + // engineer +} +``` + +### `NewWithOptions` + +- Description: `NewWithOptions` can create a `Json` object with any type of value `data`. However, due to data access reasons, `data` should be a `map` or `slice`, otherwise it is meaningless. + +- Format: + +```go +func NewWithOptions(data interface{}, options Options) *Json +``` + +- Example: + +```go +func ExampleNewWithOptions() { + type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string + } + me := Me{ + Name: "john", + Score: 100, + Title: "engineer", + } + + j := gjson.NewWithOptions(me, gjson.Options{ + Tags: "tag", + }) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(j.Get("Title")) + + // Output: + // john + // 100 + // engineer +} +``` + +```go +func ExampleNewWithOptions_UTF8BOM() { + jsonContent := `{"name":"john", "score":"100"}` + + content := make([]byte, 3, len(jsonContent)+3) + content[0] = 0xEF + content[1] = 0xBB + content[2] = 0xBF + content = append(content, jsonContent...) + + j := gjson.NewWithOptions(content, gjson.Options{ + Tags: "tag", + }) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +### `Load` + +- Description: `Load` loads content from the specified file `path` and creates a `Json` object from it. + +- Format: + +```go +func Load(path string, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoad() { + jsonFilePath := gtest.DataPath("json", "data1.json") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + notExistFilePath := gtest.DataPath("json", "data2.json") + j2, _ := gjson.Load(notExistFilePath) + fmt.Println(j2.Get("name")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoad_Xml() { + jsonFilePath := gtest.DataPath("xml", "data1.xml") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("doc.name")) + fmt.Println(j.Get("doc.score")) +} +``` + +### `LoadJson` + +- Description: `LoadJson` creates a `Json` object from the given content in `JSON` format. + +- Format: + +```go +func LoadJson(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadJson() { + jsonContent := `{"name":"john", "score":"100"}` + j, _ := gjson.LoadJson(jsonContent) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +### `LoadXml` + +- Description: `LoadXml` creates a `Json` object from the given content in `XML` format. + +- Format: + +```go +func LoadXml(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadXml() { + xmlContent := ` + + john + 100 + ` + j, _ := gjson.LoadXml(xmlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + +### LoadIni + +- Description: `LoadIni` creates a `Json` object from the given content in `INI` format. + +- Format: + +```go +func LoadIni(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadIni() { + iniContent := ` + [base] + name = john + score = 100 + ` + j, _ := gjson.LoadIni(iniContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + +### `LoadYaml` + +- Description: `LoadYaml` creates a `Json` object from the given content in `YAML` format. + +- Format: + +```go +func LoadYaml(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadYaml() { + yamlContent := + `base: + name: john + score: 100` + + j, _ := gjson.LoadYaml(yamlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + +### `LoadToml` + +- Description: `LoadToml` creates a `Json` object from the given content in `TOML` format. + +- Format: + +```go +func LoadToml(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadToml() { + tomlContent := + `[base] + name = "john" + score = 100` + + j, _ := gjson.LoadToml(tomlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + +### `LoadContent` + +- Description: `LoadContent` creates a `Json` object based on the given content. It automatically checks the data type of `content`, supporting content types such as `JSON, XML, INI, YAML, and TOML`. + +- Format: + +```go +func LoadContent(data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadContent() { + jsonContent := `{"name":"john", "score":"100"}` + + j, _ := gjson.LoadContent(jsonContent) + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoadContent_UTF8BOM() { + jsonContent := `{"name":"john", "score":"100"}` + + content := make([]byte, 3, len(jsonContent)+3) + content[0] = 0xEF + content[1] = 0xBB + content[2] = 0xBF + content = append(content, jsonContent...) + + j, _ := gjson.LoadContent(content) + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoadContent_Xml() { + xmlContent := ` + + john + 100 + ` + + x, _ := gjson.LoadContent(xmlContent) + + fmt.Println(x.Get("base.name")) + fmt.Println(x.Get("base.score")) + + // Output: + // john + // 100 +} +``` + +### LoadContentType + +- Description: `LoadContentType` creates a `Json` object based on the given content and type. Supported content types are `Json, XML, INI, YAML, and TOML`. + +- Format: + +```go +func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) +``` + +- Example: + +```go +func ExampleLoadContentType() { + jsonContent := `{"name":"john", "score":"100"}` + xmlContent := ` + + john + 100 + ` + + j, _ := gjson.LoadContentType("json", jsonContent) + x, _ := gjson.LoadContentType("xml", xmlContent) + j1, _ := gjson.LoadContentType("json", "") + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(x.Get("base.name")) + fmt.Println(x.Get("base.score")) + fmt.Println(j1.Get("")) + + // Output: + // john + // 100 + // john + // 100 +} +``` + +### `IsValidDataType` + +- Description: `IsValidDataType` checks if the given `dataType` is a valid content type for loading. + +- Format: + +```go +func IsValidDataType(dataType string) bool +``` + +- Example: + +```go +func ExampleIsValidDataType() { + fmt.Println(gjson.IsValidDataType("json")) + fmt.Println(gjson.IsValidDataType("yml")) + fmt.Println(gjson.IsValidDataType("js")) + fmt.Println(gjson.IsValidDataType("mp4")) + fmt.Println(gjson.IsValidDataType("xsl")) + fmt.Println(gjson.IsValidDataType("txt")) + fmt.Println(gjson.IsValidDataType("")) + fmt.Println(gjson.IsValidDataType(".json")) + + // Output: + // true + // true + // true + // false + // false + // false + // false + // true +} +``` + +### `Valid` + +- Description: `Valid` checks if `data` is a valid `JSON` data type. The `data` parameter specifies the `JSON` formatted data, which can be of type `bytes` or `string`. + +- Format: + +```go +func Valid(data interface{}) bool +``` + +- Example: + +```go +func ExampleValid() { + data1 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) + data2 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`) + fmt.Println(gjson.Valid(data1)) + fmt.Println(gjson.Valid(data2)) + + // Output: + // true + // false +} +``` + +### Marshal + +- Description: `Marshal` is an alias for `Encode`. + +- Format: + +```go +func Marshal(v interface{}) (marshaledBytes []byte, err error) +``` + +- Example: + +```go +func ExampleMarshal() { + data := map[string]interface{}{ + "name": "john", + "score": 100, + } + + jsonData, _ := gjson.Marshal(data) + fmt.Println(string(jsonData)) + + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "Guo Qiang", + Age: 18, + } + + infoData, _ := gjson.Marshal(info) + fmt.Println(string(infoData)) + + // Output: + // {"name":"john","score":100} + // {"Name":"Guo Qiang","Age":18} +} +``` + +### `MarshalIndent` + +- Description: `MarshalIndent` is an alias for `json.MarshalIndent`. + +- Format: + +```go +func MarshalIndent(v interface{}, prefix, indent string) (marshaledBytes []byte, err error) +``` + +- Example: + +```go +func ExampleMarshalIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.MarshalIndent(info, "", "\t") + fmt.Println(string(infoData)) + + // Output: + // { + // "Name": "John", + // "Age": 18 + // } +} +``` + +### Unmarshal + +- Description: `Unmarshal` is an alias for `DecodeTo`. + +- Format: + +```go +func Unmarshal(data []byte, v interface{}) (err error) +``` + +- Example: + +```go +func ExampleUnmarshal() { + type BaseInfo struct { + Name string + Score int + } + + var info BaseInfo + + jsonContent := "{\"name\":\"john\",\"score\":100}" + gjson.Unmarshal([]byte(jsonContent), &info) + fmt.Printf("%+v", info) + + // Output: + // {Name:john Score:100} +} +``` + +### Encode + +- Description: `Encode` serializes any type `value` into a `byte` array with content in `JSON` format. + +- Format: + +```go +func Encode(value interface{}) ([]byte, error) +``` + +- Example: + +```go +func ExampleEncode() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.Encode(info) + fmt.Println(string(infoData)) + + // Output: + // {"Name":"John","Age":18} +} +``` + +### `MustEncode` + +- Description: `MustEncode` performs the `Encode` operation but will `panic` if any error occurs. + +- Format: + +```go +func MustEncode(value interface{}) []byte +``` + +- Example: + +```go +func ExampleMustEncode() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData := gjson.MustEncode(info) + fmt.Println(string(infoData)) + + // Output: + // {"Name":"John","Age":18} +} +``` + +### `EncodeString` + +- Description: `EncodeString` serializes any type `value` into a `string` with content in `JSON` format. + +- Format: + +```go +func EncodeString(value interface{}) (string, error) +``` + +- Example: + +```go +func ExampleEncodeString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.EncodeString(info) + fmt.Println(infoData) + + // Output: + // {"Name":"John","Age":18} +} +``` + +### `MustEncodeString` + +- Description: `MustEncodeString` serializes any type `value` into a `string` with content in `JSON` format but will `panic` if any error occurs. + +- Format: + +```go +func MustEncodeString(value interface{}) string +``` + +- Example: + +```go +func ExampleMustEncodeString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData := gjson.MustEncodeString(info) + fmt.Println(infoData) + + // Output: + // {"Name":"John","Age":18} +} +``` + +### `Decode` + +- Description: `Decode` decodes `data` in `JSON` format to `interface{}`. The `data` parameter can be `[]byte` or `string`. + +- Format: + +```go +func Decode(data interface{}, options ...Options) (interface{}, error) +``` + +- Example: + +```go +func ExampleDecode() { + jsonContent := `{"name":"john","score":100}` + info, _ := gjson.Decode([]byte(jsonContent)) + fmt.Println(info) + + // Output: + // map[name:john score:100] +} +``` + +### DecodeTo + +- Description: `DecodeTo` decodes `data` in `JSON` format to the specified `interface` type variable `v`. The `data` parameter can be `[]byte` or `string`. The `v` parameter should be of pointer type. + +- Format: + +```go +func DecodeTo(data interface{}, v interface{}, options ...Options) (err error) +``` + +- Example: + +```go +func ExampleDecodeTo() { + type BaseInfo struct { + Name string + Score int + } + + var info BaseInfo + + jsonContent := "{\"name\":\"john\",\"score\":100}" + gjson.DecodeTo([]byte(jsonContent), &info) + fmt.Printf("%+v", info) + + // Output: + // {Name:john Score:100} +} +``` + +### `DecodeToJson` + +- Description: `DecodeToJson` encodes `data` in `JSON` format into a `json` object. The `data` parameter can be `[]byte` or `string`. + +- Format: + +```go +func DecodeToJson(data interface{}, options ...Options) (*Json, error) +``` + +- Example: + +```go +func ExampleDecodeToJson() { + jsonContent := `{"name":"john","score":100}"` + j, _ := gjson.DecodeToJson([]byte(jsonContent)) + fmt.Println(j.Map()) + + // May Output: + // map[name:john score:100] +} +``` + +### `SetSplitChar` + +- Description: `SetSplitChar` sets the level delimiter for data access. + +- Format: + +```go +func (j *Json) SetSplitChar(char byte) +``` + +- Example: + +```go +func ExampleJson_SetSplitChar() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } + }` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.SetSplitChar('#') + fmt.Println("John Score:", j.Get("users#list#1#score").Float32()) + } + // Output: + // John Score: 99.5 +} +``` + +### `SetViolenceCheck` + +- Description: `SetViolenceCheck` enables/disables violent check for data level access. + +- Format: + +```go +func (j *Json) SetViolenceCheck(enabled bool) +``` + +- Example: + +```go +func ExampleJson_SetViolenceCheck() { + data := + `{ + "users" : { + "count" : 100 + }, + "users.count" : 101 + }` + if j, err := gjson.DecodeToJson(data); err != nil { + fmt.Println(err) + } else { + j.SetViolenceCheck(true) + fmt.Println("Users Count:", j.Get("users.count")) + } + // Output: + // Users Count: 101 +} +``` + +### `ToJson` + +- Description: `ToJson` returns the `JSON` content as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToJson() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToJson() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.ToJson() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + +### `ToJsonString` + +- Description: `ToJsonString` returns the `JSON` content as a `string` type. + +- Format: + +```go +func (j *Json) ToJsonString() (string, error) +``` + +- Example: + +```go +func ExampleJson_ToJsonString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr, _ := j.ToJsonString() + fmt.Println(jsonStr) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + +### ToJsonIndent + +- Description: `ToJsonIndent` returns the indented `JSON` content as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToJsonIndent() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToJsonIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.ToJsonIndent() + fmt.Println(string(jsonBytes)) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + +### `ToJsonIndentString` + +- Description: `ToJsonIndentString` returns the indented `JSON` content as a `string` type. + +- Format: + +```go +func (j *Json) ToJsonIndentString() (string, error) +``` + +- Example: + +```go +func ExampleJson_ToJsonIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr, _ := j.ToJsonIndentString() + fmt.Println(jsonStr) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + +### MustToJson + +- Description: `MustToJson` returns the `JSON` content as a `[]byte` type, and if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToJson() []byte +``` + +- Example: + +```go +func ExampleJson_MustToJson() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes := j.MustToJson() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + +### `MustToJsonString` + +- Description: `MustToJsonString` returns the `JSON` content as a `string` type, and if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToJsonString() string +``` + +- Example: + +```go +func ExampleJson_MustToJsonString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr := j.MustToJsonString() + fmt.Println(jsonStr) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + +### MustToJsonIndent + +- Description: `MustToJsonStringIndent` returns the indented `JSON` content as a `[]byte` type, and if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToJsonIndent() []byte +``` + +- Example: + +```go +func ExampleJson_MustToJsonIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes := j.MustToJsonIndent() + fmt.Println(string(jsonBytes)) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + +### `MustToJsonIndentString` + +- Description: `MustToJsonStringIndent` returns the indented `JSON` content as a `string` type, and if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToJsonIndentString() string +``` + +- Example: + +```go +func ExampleJson_MustToJsonIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr := j.MustToJsonIndentString() + fmt.Println(jsonStr) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + +### ToXml + +- Description: `ToXml` returns content in `XML` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToXml(rootTag ...string) ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToXml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes, _ := j.ToXml() + fmt.Println(string(xmlBytes)) + + // Output: + // 18John +} +``` + +### `ToXmlString` + +- Description: `ToXmlString` returns content in `XML` format as a `string` type. + +- Format: + +```go +func (j *Json) ToXmlString(rootTag ...string) (string, error) +``` + +- Example: + +```go +func ExampleJson_ToXmlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr, _ := j.ToXmlString() + fmt.Println(string(xmlStr)) + + // Output: + // 18John +} +``` + +### ToXmlIndent + +- Description: `ToXmlIndent` returns indented content in `XML` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToXmlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes, _ := j.ToXmlIndent() + fmt.Println(string(xmlBytes)) + + // Output: + // + // 18 + // John + // +} +``` + +### `ToXmlIndentString` + +- Description: `ToXmlIndentString` returns indented content in `XML` format as a `string` type. + +- Format: + +```go +func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) +``` + +- Example: + +```go +func ExampleJson_ToXmlIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr, _ := j.ToXmlIndentString() + fmt.Println(string(xmlStr)) + + // Output: + // + // 18 + // John + // +} +``` + +### MustToXml + +- Description: `MustToXml` returns content in `XML` format as a `[]byte` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToXml(rootTag ...string) []byte +``` + +- Example: + +```go +func ExampleJson_MustToXml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes := j.MustToXml() + fmt.Println(string(xmlBytes)) + + // Output: + // 18John +} +``` + +### `MustToXmlString` + +- Description: `MustToXmlString` returns content in `XML` format as a `string` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToXmlString(rootTag ...string) string +``` + +- Example: + +```go +func ExampleJson_MustToXmlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr := j.MustToXmlString() + fmt.Println(string(xmlStr)) + + // Output: + // 18John +} +``` + +### MustToXmlIndent + +- Description: `MustToXmlStringIndent` returns indented content in `XML` format as a `[]byte` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToXmlIndent(rootTag ...string) []byte +``` + +- Example: + +```go +func ExampleJson_MustToXmlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes := j.MustToXmlIndent() + fmt.Println(string(xmlBytes)) + + // Output: + // + // 18 + // John + // +} +``` + +### `MustToXmlIndentString` + +- Description: `MustToXmlStringIndentString` returns indented content in `XML` format as a `string` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToXmlIndentString(rootTag ...string) string +``` + +- Example: + +```go +func ExampleJson_MustToXmlIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr := j.MustToXmlIndentString() + fmt.Println(string(xmlStr)) + + // Output: + // + // 18 + // John + // +} +``` + +### ToYaml + +- Description: `ToYaml` returns content in `YAML` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToYaml() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToYaml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes, _ := j.ToYaml() + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + +### ToYamlIndent + +- Description: `ToYamlIndent` returns indented content in `YAML` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToYamlIndent(indent string) ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToYamlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes, _ := j.ToYamlIndent("") + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + +### `ToYamlString` + +- Description: `ToYamlString` returns content in `YAML` format as a `string` type. + +- Format: + +```go +func (j *Json) ToYamlString() (string, error) +``` + +- Example: + +```go +func ExampleJson_ToYamlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlStr, _ := j.ToYamlString() + fmt.Println(string(YamlStr)) + + // Output: + //Age: 18 + //Name: John +} +``` + +### `MustToYaml` + +- Description: `MustToYaml` returns content in `YAML` format as a `[]byte` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToYaml() []byte +``` + +- Example: + +```go +func ExampleJson_MustToYaml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes := j.MustToYaml() + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + +### MustToYamlString + +- Description: `MustToYamlString` returns content in `YAML` format as a `string` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToYamlString() string +``` + +- Example: + +```go +func ExampleJson_MustToYamlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlStr := j.MustToYamlString() + fmt.Println(string(YamlStr)) + + // Output: + //Age: 18 + //Name: John +} +``` + +### `ToToml` + +- Description: `ToToml` returns content in `TOML` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToToml() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToToml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlBytes, _ := j.ToToml() + fmt.Println(string(TomlBytes)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + +### ToTomlString + +- Description: `ToTomlString` returns content in `TOML` format as a `string` type. + +- Format: + +```go +func (j *Json) ToTomlString() (string, error) +``` + +- Example: + +```go +func ExampleJson_ToTomlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlStr, _ := j.ToTomlString() + fmt.Println(string(TomlStr)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + +### `MustToToml` + +- Description: `MustToToml` returns content in `TOML` format as a `[]byte` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToToml() []byte +``` + +- Example: + +```go +func ExampleJson_MustToToml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlBytes := j.MustToToml() + fmt.Println(string(TomlBytes)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + +### MustToTomlString + +- Description: `MustToTomlString` returns content in `TOML` format as a `string` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToTomlString() string +``` + +- Example: + +```go +func ExampleJson_MustToTomlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlStr := j.MustToTomlString() + fmt.Println(string(TomlStr)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + +### `ToIni` + +- Description: `ToIni` returns content in `INI` format as a `[]byte` type. + +- Format: + +```go +func (j *Json) ToIni() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_ToIni() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + IniBytes, _ := j.ToIni() + fmt.Println(string(IniBytes)) + + // May Output: + //Name=John + //Age=18 +} +``` + +### ToIniString + +- Description: `ToIniString` returns content in `INI` format as a `string` type. + +- Format: + +```go +func (j *Json) ToIniString() (string, error) +``` + +- Example: + +```go +func ExampleJson_ToIniString() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniStr, _ := j.ToIniString() + fmt.Println(string(IniStr)) + + // Output: + //Name=John +} +``` + +### `MustToIni` + +- Description: `MustToIni` returns content in `INI` format as a `[]byte` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToIni() []byte +``` + +- Example: + +```go +func ExampleJson_MustToIni() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniBytes := j.MustToIni() + fmt.Println(string(IniBytes)) + + // Output: + //Name=John +} +``` + +### MustToIniString + +- Description: `MustToIniString` returns content in `INI` format as a `string` type. If any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustToIniString() string +``` + +- Example: + +```go +func ExampleJson_MustToIniString() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniStr := j.MustToIniString() + fmt.Println(string(IniStr)) + + // Output: + //Name=John +} +``` + +### MarshalJSON + +- Description: `MarshalJSON` implements the `json.Marshal` interface `MarshalJSON`. + +- Format: + +```go +func (j Json) MarshalJSON() ([]byte, error) +``` + +- Example: + +```go +func ExampleJson_MarshalJSON() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.MarshalJSON() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + +### UnmarshalJSON + +- Description: `UnmarshalJSON` implements the `json.Unmarshal` interface `UnmarshalJSON`. + +- Format: + +```go +func (j *Json) UnmarshalJSON(b []byte) error +``` + +- Example: + +```go +func ExampleJson_UnmarshalJSON() { + jsonStr := `{"Age":18,"Name":"John"}` + + j := gjson.New("") + j.UnmarshalJSON([]byte(jsonStr)) + fmt.Println(j.Map()) + + // Output: + // map[Age:18 Name:John] +} +``` + +### `UnmarshalValue` + +- Description: `UnmarshalValue` is an interface implementation for setting any type of value to `Json`. + +- Format: + +```go +func (j *Json) UnmarshalValue(value interface{}) error +``` + +- Example: + +```go +func ExampleJson_UnmarshalValue_Yaml() { + yamlContent := + `base: + name: john + score: 100` + + j := gjson.New("") + j.UnmarshalValue([]byte(yamlContent)) + fmt.Println(j.Var().String()) + + // Output: + // {"base":{"name":"john","score":100}} +} +``` + +```go +func ExampleJson_UnmarshalValue_Xml() { + xmlStr := `john100` + + j := gjson.New("") + j.UnmarshalValue([]byte(xmlStr)) + fmt.Println(j.Var().String()) + + // Output: + // {"doc":{"name":"john","score":"100"}} +} +``` + +### `MapStrAny` + +- Description: `MapStrAny` implements the interface method `MapStrAny()`. + +- Format: + +```go +func (j *Json) MapStrAny() map[string]interface{} +``` + +- Example: + +```go +func ExampleJson_MapStrAny() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.MapStrAny()) + + // Output: + // map[Age:18 Name:John] +} +``` + +### Interfaces + +- Description: `Interfaces` implements the interface method `Interfaces()`. + +- Format: + +```go +func (j *Json) Interfaces() []interface{} +``` + +- Example: + +```go +func ExampleJson_Interfaces() { + type BaseInfo struct { + Name string + Age int + } + + infoList := []BaseInfo{ + BaseInfo{ + Name: "John", + Age: 18, + }, + BaseInfo{ + Name: "Tom", + Age: 20, + }, + } + + j := gjson.New(infoList) + fmt.Println(j.Interfaces()) + + // Output: + // [{John 18} {Tom 20}] +} +``` + +### `Interface` + +- Description: `Interface` returns the value of the `Json` object. + +- Format: + +```go +func (j *Json) Interface() interface{} +``` + +- Example: + +```go +func ExampleJson_Interface() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Interface()) + + var nilJ *gjson.Json = nil + fmt.Println(nilJ.Interface()) + + // Output: + // map[Age:18 Name:John] + // +} +``` + +### `Var` + +- Description: `Var` returns the value of the `Json` object as type `*gvar.Var`. + +- Format: + +```go +func (j *Json) Var() *gvar.Var +``` + +- Example: + +```go +func ExampleJson_Var() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Var().String()) + fmt.Println(j.Var().Map()) + + // Output: + // {"Age":18,"Name":"John"} + // map[Age:18 Name:John] +} +``` + +### IsNil + +- Description: `IsNil` checks whether the Json object's value is `nil`. + +- Format: + +```go +func (j *Json) IsNil() bool +``` + +- Example: + +```go +func ExampleJson_IsNil() { + data1 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) + data2 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`) + + j1, _ := gjson.LoadContent(data1) + fmt.Println(j1.IsNil()) + + j2, _ := gjson.LoadContent(data2) + fmt.Println(j2.IsNil()) + + // Output: + // false + // true +} +``` + +### `Get` + +- Description: `Get` retrieves and returns the value according to the specified `pattern`. If the `pattern` is `".", it will return all values of the current `Json` object. If no `pattern` is found, it returns `nil`. + +- Format: + +```go +func (j *Json) Get(pattern string, def ...interface{}) *gvar.Var +``` + +- Example: + +```go +func ExampleJson_Get() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + fmt.Println(j.Get(".")) + fmt.Println(j.Get("users")) + fmt.Println(j.Get("users.count")) + fmt.Println(j.Get("users.array")) + + var nilJ *gjson.Json = nil + fmt.Println(nilJ.Get(".")) + + // Output: + // {"users":{"array":["John","Ming"],"count":1}} + // {"array":["John","Ming"],"count":1} + // 1 + // ["John","Ming"] +} +``` + +### `GetJson` + +- Description: `GetJson` retrieves the value specified by `pattern` and converts it into a non-concurrent-safe `Json` object. + +- Format: + +```go +func (j *Json) GetJson(pattern string, def ...interface{}) *Json +``` + +- Example: + +```go +func ExampleJson_GetJson() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.GetJson("users.array").Array()) + + // Output: + // [John Ming] +} +``` + +### GetJsons + +- Description: `GetJsons` retrieves the value specified by `pattern` and converts it into a slice of non-concurrent-safe `Json` objects. + +- Format: + +```go +func (j *Json) GetJsons(pattern string, def ...interface{}) []*Json +``` + +- Example: + +```go +func ExampleJson_GetJsons() { + data := + `{ + "users" : { + "count" : 3, + "array" : [{"Age":18,"Name":"John"}, {"Age":20,"Name":"Tom"}] + } + }` + + j, _ := gjson.LoadContent(data) + + jsons := j.GetJsons("users.array") + for _, json := range jsons { + fmt.Println(json.Interface()) + } + + // Output: + // map[Age:18 Name:John] + // map[Age:20 Name:Tom] +} +``` + +### `GetJsonMap` + +- Description: `GetJsonMap` retrieves the value specified by `pattern` and converts it into a map of non-concurrent-safe `Json` objects. + +- Format: + +```go +func (j *Json) GetJsonMap(pattern string, def ...interface{}) map[string]*Json +``` + +- Example: + +```go +func ExampleJson_GetJsonMap() { + data := + `{ + "users" : { + "count" : 1, + "array" : { + "info" : {"Age":18,"Name":"John"}, + "addr" : {"City":"Chengdu","Company":"Tencent"} + } + } + }` + + j, _ := gjson.LoadContent(data) + + jsonMap := j.GetJsonMap("users.array") + + for _, json := range jsonMap { + fmt.Println(json.Interface()) + } + + // May Output: + // map[City:Chengdu Company:Tencent] + // map[Age:18 Name:John] +} +``` + +### `Set` + +- Description: `Set` sets the value of the specified `pattern`. It supports data level access by default using the `'.'` character. + +- Format: + +```go +func (j *Json) Set(pattern string, value interface{}) error +``` + +- Example: + +```go +func ExampleJson_Set() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.Set("Addr", "ChengDu") + j.Set("Friends.0", "Tom") + fmt.Println(j.Var().String()) + + // Output: + // {"Addr":"ChengDu","Age":18,"Friends":["Tom"],"Name":"John"} +} +``` + +### `MustSet` + +- Description: `MustSet` performs the `Set` operation, but if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustSet(pattern string, value interface{}) +``` + +- Example: + +```go +func ExampleJson_MustSet() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.MustSet("Addr", "ChengDu") + fmt.Println(j.Var().String()) + + // Output: + // {"Addr":"ChengDu","Age":18,"Name":"John"} +} +``` + +### `Remove` + +- Description: `Remove` deletes the value of the specified `pattern`. It supports data level access by default using the `'.'` character. + +- Format: + +```go +func (j *Json) Remove(pattern string) error +``` + +- Example: + +```go +func ExampleJson_Remove() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.Remove("Age") + fmt.Println(j.Var().String()) + + // Output: + // {"Name":"John"} +} +``` + +### MustRemove + +- Description: `MustRemove` performs `Remove`, but if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustRemove(pattern string) +``` + +- Example: + +```go +func ExampleJson_MustRemove() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.MustRemove("Age") + fmt.Println(j.Var().String()) + + // Output: + // {"Name":"John"} +} +``` + +### `Contains` + +- Description: `Contains` checks if the value of the specified `pattern` exists. + +- Format: + +```go +func (j *Json) Contains(pattern string) bool +``` + +- Example: + +```go +func ExampleJson_Contains() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Contains("Age")) + fmt.Println(j.Contains("Addr")) + + // Output: + // true + // false +} +``` + +### Len + +- Description: `Len` returns the length/size of a value according to the specified `pattern`. The type of the `pattern` value should be a `slice` or a `map`. If the target value is not found or the type is invalid, it returns `-1`. + +- Format: + +```go +func (j *Json) Len(pattern string) int +``` + +- Example: + +```go +func ExampleJson_Len() { + data := + `{ + "users" : { + "count" : 1, + "nameArray" : ["Join", "Tom"], + "infoMap" : { + "name" : "Join", + "age" : 18, + "addr" : "ChengDu" + } + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Len("users.nameArray")) + fmt.Println(j.Len("users.infoMap")) + + // Output: + // 2 + // 3 +} +``` + +### `Append` + +- Description: `Append` appends a value to the `Json` object using the specified `pattern`. The type of the `pattern` value should be a `slice`. + +- Format: + +```go +func (j *Json) Append(pattern string, value interface{}) error +``` + +- Example: + +```go +func ExampleJson_Append() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + j.Append("users.array", "Lily") + + fmt.Println(j.Get("users.array").Array()) + + // Output: + // [John Ming Lily] +} +``` + +### MustAppend + +- Description: `MustAppend` performs `Append`, but if any error occurs, it will `panic`. + +- Format: + +```go +func (j *Json) MustAppend(pattern string, value interface{}) +``` + +- Example: + +```go +func ExampleJson_MustAppend() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + j.MustAppend("users.array", "Lily") + + fmt.Println(j.Get("users.array").Array()) + + // Output: + // [John Ming Lily] +} +``` + +### Map + +- Description: `Map` converts the current `Json` object to a `map[string]interface{}`. It returns `nil` if conversion fails. + +- Format: + +```go +func (j *Json) Map() map[string]interface{} +``` + +- Example: + +```go +func ExampleJson_Map() { + data := + `{ + "users" : { + "count" : 1, + "info" : { + "name" : "John", + "age" : 18, + "addr" : "ChengDu" + } + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Get("users.info").Map()) + + // Output: + // map[addr:ChengDu age:18 name:John] +} +``` + +### Array + +- Description: `Array` converts the current `Json` object to a `[]interface{}`. It returns `nil` if conversion fails. + +- Format: + +```go +func (j *Json) Array() []interface{} +``` + +- Example: + +```go +func ExampleJson_Array() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Get("users.array")) + + // Output: + // ["John","Ming"] +} +``` + +### `Scan` + +- Description: `Scan` automatically calls the `Struct` or `Structs` function to perform conversion based on the type of the `pointer` parameter. + +- Format: + +```go +func (j *Json) Scan(pointer interface{}, mapping ...map[string]string) error +``` + +- Example: + +```go +func ExampleJson_Scan() { + data := `{"name":"john","age":"18"}` + + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{} + + j, _ := gjson.LoadContent(data) + j.Scan(&info) + + fmt.Println(info) + + // May Output: + // {john 18} +} +``` + +### Dump + +- Description: `Dump` prints the `Json` object in a more readable way. + +- Format: + +```go +func (j *Json) Dump() +``` + +- Example: + +```go +func ExampleJson_Dump() { + data := `{"name":"john","age":"18"}` + + j, _ := gjson.LoadContent(data) + j.Dump() + + // May Output: + //{ + // "name": "john", + // "age": "18", + //} +} +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" new file mode 100644 index 00000000000..2bbcf4408f8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/components/network-gtcp-tls' +title: 'TCP - TLS' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gtcp, TLS Encryption, Network Communication, Go Language, Server, Client, Secure Communication, Network Security] +description: "Using GoFrame framework's gtcp module to implement TLS encrypted communication in environments with high security requirements. Through provided sample code, we explain how to create TLS servers and clients, how to use certificates for encrypted data transmission, and how to handle potential certificate expiration issues. This is crucial for developers who need to securely transmit data." +--- + +The `gtcp` module supports `TLS` encrypted communication servers and clients, which are essential in scenarios with high security demands. A `TLS` server can be created using either the `NewServerTLS` or `NewServerKeyCrt` methods. A `TLS` client can be created using either the `NewConnKeyCrt` or `NewConnTLS` methods. + +Examples: + +[https://github.com/gogf/gf/tree/master/.example/net/gtcp/tls](https://github.com/gogf/gf/tree/master/.example/net/gtcp/tls) + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + g.Log().Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConnKeyCrt(address, crtFile, keyFile) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + g.Log().Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} +``` + +After execution, you may see an error reported when the client is running: + +``` +panic: x509: certificate has expired or is not yet valid +``` + +This is because our certificate was manually created and has expired. For demonstration purposes, we ignored the client's verification of the certificate in the client code. + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + g.Log().Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + tlsConfig, err := gtcp.LoadKeyCrt(crtFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.InsecureSkipVerify = true + + conn, err := gtcp.NewConnTLS(address, tlsConfig) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + g.Log().Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} +``` + +Upon execution, the terminal output is: + +```0 +1 +2 +3 +4 +5 +2019-06-05 00:13:12.488 [ERRO] EOF +Stack: +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/net/gtcp/tls/gtcp_server_client.go:25 +``` + +Here, the client closes the connection after `5` seconds, resulting in the server receiving an `EOF` error when receiving data. In practice, this kind of error can be ignored; the server can simply close the client's connection upon encountering this error. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..f230e9bbb15 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/components/network-gtcp-funcs' +title: 'TCP - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, TCP Component, gtcp Module, Go Language, Network Programming, TLS Encryption, Data Communication, Short Connections, Utility Methods] +description: "The gtcp module in the GoFrame framework and some common utility methods allow for the creation of TCP connections, TLS secure encrypted communication, and data sending and receiving functions, and provide a concrete example showing how to access a specified web site via TCP." +--- + +The `gtcp` module also provides some common utility methods. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) +func NewNetConn(addr string, timeout ...int) (net.Conn, error) +func NewNetConnKeyCrt(addr, crtFile, keyFile string) (net.Conn, error) +func NewNetConnTLS(addr string, tlsConfig *tls.Config) (net.Conn, error) +func Send(addr string, data []byte, retry ...Retry) error +func SendPkg(addr string, data []byte, option ...PkgOption) error +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, option ...PkgOption) error +func SendRecv(addr string, data []byte, receive int, retry ...Retry) ([]byte, error) +func SendRecvPkg(addr string, data []byte, option ...PkgOption) ([]byte, error) +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) +func SendRecvWithTimeout(addr string, data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) +func SendWithTimeout(addr string, data []byte, timeout time.Duration, retry ...Retry) error +``` + +1. `NewNetConn` is used to simplify the creation of the standard library connection object `net.Conn`; +2. `NewNetConnTLS` and `NewNetConnKeyCrt` are used to create TCP clients that support TLS secure encrypted communication; +3. The `Send*` series of methods send data directly through the given address and obtain the returned results of the request, which are used for short connection requests; + +Below is a simple example where we use utility methods to access the specified web site: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" +) + +func main() { + data, err := gtcp.SendRecv("www.baidu.com:80", []byte("HEAD / HTTP/1.1\n\n"), -1) + if err != nil { + panic(err) + } + fmt.Println(string(data)) +} +``` + +In this example, we access the Baidu homepage via TCP, simulate the HTTP request header information, and receive the return result. After execution, the output result is as follows: + +``` +HTTP/1.1 302 Found +Connection: Keep-Alive +Content-Length: 17931 +Content-Type: text/html +Date: Tue, 04 Jun 2019 15:53:09 GMT +Etag: "54d9749e-460b" +Server: bfe/1.0.8.18 +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..daeece41339 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" @@ -0,0 +1,268 @@ +--- +slug: '/docs/components/network-gtcp-conn' +title: 'TCP - Object' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, gtcp, TCP communication, connection object, timeout handling, data writing, data reading, network programming, error retry] +description: "This page introduces the usage of gtcp module and its TCP connection object provided by the GoFrame framework. It includes how to use gtcp.Conn for reliable TCP communication and handling data sending and receiving. The text also describes the timeout handling mechanism and demonstrates with simple examples how to use these features, showing how to flexibly perform network programming in the GoFrame environment." +--- + +The `gtcp` module provides an easy-to-use `gtcp.Conn` link operation object. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Conn + func NewConn(addr string, timeout ...int) (*Conn, error) + func NewConnByNetConn(conn net.Conn) *Conn + func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) + func NewConnTLS(addr string, tlsConfig *tls.Config) (*Conn, error) + func (c *Conn) Close() error + func (c *Conn) LocalAddr() net.Addr + func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) + func (c *Conn) RecvLine(retry ...Retry) ([]byte, error) + func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *Conn) RemoteAddr() net.Addr + func (c *Conn) Send(data []byte, retry ...Retry) error + func (c *Conn) SendRecv(data []byte, receive int, retry ...Retry) ([]byte, error) + func (c *Conn) SendRecvWithTimeout(data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retry) error + func (c *Conn) SetDeadline(t time.Time) error + func (c *Conn) SetRecvBufferWait(bufferWaitDuration time.Duration) + func (c *Conn) SetRecvDeadline(t time.Time) error + func (c *Conn) SetSendDeadline(t time.Time) error +``` + +## Writing Operation + +The TCP communication writing operation is implemented by the `Send` method, providing an error retry mechanism via the second optional parameter `retry`. Note that the `Send` method does not have any buffering mechanism, meaning that each call to the `Send` method will immediately invoke the underlying TCP Write method to write data (buffering relies on the system's underlying implementation). Therefore, if you want to control output buffering, please handle it at the business layer. + +When performing TCP writes, reliable communication scenarios often involve a write and a read, i.e., starting `Recv` to get the target's feedback result immediately after a successful `Send`. Therefore, `gtcp.Conn` also offers a convenient `SendRecv` method. + +## Reading Operation + +The TCP communication reading operation is implemented by the `Recv` method, which also provides an error retry mechanism via a second optional parameter `retry`. The `Recv` method offers built-in read buffer control, allowing you to specify the reading length (specified by the `length` parameter). It will immediately return when the specified length of data has been read. If `length < 0`, it will read all readable buffer data and return it. When `length = 0`, it means to get the buffered data once and return immediately. + +Using `Recv(-1)` can read all readable buffer data (with indefinite length, possible truncation if the sent data packet is too long), but be cautious of packet parsing issues, which can easily result in incomplete packets. In this case, the business layer needs to ensure data packet integrity based on the predetermined protocol structure. It is recommended to use the simple protocol introduced later via `SendPkg`/ `RecvPkg` to send/receive message packets, as described in subsequent sections. + +## Timeout Handling + +`gtcp.Conn` provides timeout handling for TCP communication data writing and reading, specified via the `timeout` parameter in the method, which is straightforward and won't be elaborated further. + +* * * + +Next, we'll look at how to use the `gtcp.Conn` object through some examples. + +## Usage Examples + +### Example 1, Simple Usage + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + for i := 0; i < 10000; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +1. On the `Server` side, received client data is immediately printed to the terminal. +2. On the `Client` side, the same connection object is used to send an incrementing number to the server every second in a loop. This function also demonstrates that underlying `Socket` communication does not use buffering; executing a `Send` immediately sends data to the server. Therefore, the client needs to manage the buffered data to be sent locally. +3. Execution result After execution, you can see the server outputting the following information on the terminal: + +```shell + 2018-07-11 22:11:08.650 0 + 2018-07-11 22:11:09.651 1 + 2018-07-11 22:11:10.651 2 + 2018-07-11 22:11:11.651 3 + 2018-07-11 22:11:12.651 4 + 2018-07-11 22:11:13.651 5 + 2018-07-11 22:11:14.652 6 + 2018-07-11 22:11:15.652 7 + 2018-07-11 22:11:16.652 8 + 2018-07-11 22:11:17.652 9 + 2018-07-11 22:11:18.652 10 + 2018-07-11 22:11:19.653 11 + ... +``` + +### Example 2, Echo Service + +Let's improve the previous echo service: + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +In this example program, the `Client` sends the current time information to the `Server` every second, and the `Server` returns the original data message upon reception. Upon receiving the server-side return message, the `Client` immediately prints it to the terminal. + +Execution results in output as follows: + +``` +> 2018-07-19 23:25:43 127.0.0.1:34306 127.0.0.1:8999 +> 2018-07-19 23:25:44 127.0.0.1:34308 127.0.0.1:8999 +> 2018-07-19 23:25:45 127.0.0.1:34312 127.0.0.1:8999 +> 2018-07-19 23:25:46 127.0.0.1:34314 127.0.0.1:8999 +``` + +### Example 3, HTTP Client + +In this example, we use the gtcp package to implement a simple HTTP client, reading and printing the `header` and `content` of the Baidu homepage. + +```go +package main + +import ( + "fmt" + "bytes" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + conn, err := gtcp.NewConn("www.baidu.com:80") + if err != nil { + panic(err) + } + defer conn.Close() + + if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil { + panic(err) + } + + header := make([]byte, 0) + content := make([]byte, 0) + contentLength := 0 + for { + data, err := conn.RecvLine() + // Header reading, parsing text length + if len(data) > 0 { + array := bytes.Split(data, []byte(": ")) + // Get page content length + if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) { + contentLength = gconv.Int(string(array[1][:len(array[1])-1])) } + header = append(header, data...) + header = append(header, '\n') + } + // Header reading complete, read text content + if contentLength > 0 && len(data) == 1 { + content, _ = conn.Recv(contentLength) + break + } + if err != nil { + fmt.Errorf("ERROR: %s\n", err.Error()) + break + } + } + + if len(header) > 0 { + fmt.Println(string(header)) + } + if len(content) > 0 { + fmt.Println(string(content)) + } +} +``` + +Execution results in output as follows: + +``` +HTTP/1.1 200 OK +Accept-Ranges: bytes +Cache-Control: no-cache +Connection: Keep-Alive +Content-Length: 14615 +Content-Type: text/html +Date: Sat, 21 Jul 2018 04:21:03 GMT +Etag: "5b3c3650-3917" +Last-Modified: Wed, 04 Jul 2018 02:52:00 GMT +P3p: CP=" OTI DSP COR IVA OUR IND COM " +Pragma: no-cache +Server: BWS/1.1 +... +(truncated) +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..add323d0cdb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" @@ -0,0 +1,237 @@ +--- +slug: '/docs/components/network-gtcp-conn-package' +title: 'TCP Object - Package' +sidebar_position: 0 +hide_title: true +keywords: [TCP, Sticky Packet, gtcp, GoFrame, Data Protocol, Pack and Unpack, Message Package, Network Communication, Simple Protocol, Message Interaction] +description: "Solve the TCP message protocol's sticky packet problem using the gtcp module and its packaging and unpacking features. By using GoFrame's simple lightweight data interaction protocol, developers can more easily engage in message package interaction without worrying about the complexities of packaging and unpacking. Additionally, the documentation provides several examples to help developers better understand and implement message transmission with custom data structures." +--- + +`gtcp` provides many convenient native methods for connecting and operating data. However, in most application scenarios, developers need to design their own data structures and perform packaging/unpacking. Since the `TCP` message protocol does not have message boundary protection, sticky packets can easily occur in complex network communication environments. Therefore, `gtcp` also provides a simple data protocol, making it easy for developers to interact with message packages without worrying about processing details, including packaging/unpacking, as all these complex logics have been handled by `gtcp`. + +## Simple Protocol + +The `gtcp` module offers a simple, lightweight data interaction protocol with high efficiency. The protocol format is as follows: + +``` +Data Length (16bit) | Data Field (variable length) +``` + +1. Data Length: Default is `16-bit` (`2 bytes`), used to identify the data length of the message body in bytes, not including its own 2 bytes; +2. Data Field: Variable length. With data length, the maximum data length cannot exceed `0xFFFF = 65535 bytes = 64 KB`; + +The simple protocol is implemented by `gtcp`. If both the client's and server's developers use the `gtcp` module for communication, then there is no need to worry about protocol implementation, and they can focus on implementing/enclosing the `Data` field. If interfacing with other development languages is involved, it only requires implementation according to this protocol, as the simple protocol is very lightweight, leading to low interfacing costs. +:::tip +The Data field can also be empty, meaning no length at all. +::: +## Operation Methods + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Conn + func (c *Conn) SendPkg(data []byte, option ...PkgOption) error + func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error + func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) + func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) + func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error) +``` + +As seen, the message package methods are named by adding the `Pkg` keyword to the original basic connection operation methods for easy distinction. + +Among them, the `PkgOption` data structure in the request parameter is as follows, used to define the message package receiving strategy: + +```go +// Data reading options +type PkgOption struct { + HeaderSize int // Custom header size (default is 2 bytes, maximum cannot exceed 4 bytes) + MaxDataSize int // (byte) Maximum package size for data reading, default maximum cannot exceed 2 bytes (65535 byte) + Retry Retry // Failure retry policy +} +``` + +## Usage Examples + +### Example 1, Basic Usage + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + fmt.Println(err) + break + } + fmt.Println("receive:", data) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} +``` + +This example is quite simple. After execution, the output result is: + +``` +receive: [48] +receive: [49] +receive: [50] +receive: [51] +... +``` + +### Example 2, Custom Data Structure + +In most scenarios, we need to customize the data structure for the messages sent. Developers can use the `Data` field to transmit any message content. + +Below is a simple example of a custom data structure for a client to report the current memory and CPU usage of the host node. In this example, the `Data` field uses `JSON` data format for customization, making data encoding/decoding easier. +:::tip +In practical scenarios, developers often need to customize package parsing protocols or adopt more general `protobuf` binary package encapsulation/parsing protocols. +::: +1. `types/types.go` + +Data structure definition. + +```go +package types + +import "github.com/gogf/gf/v2/frame/g" + +type NodeInfo struct { + Cpu float32 // CPU percentage (%) + Host string // Host name + Ip g.Map // IP address information (possibly multiple) + MemUsed int // Memory used (byte) + MemTotal int // Total memory (byte) + Time int // Reporting time (timestamp) +} +``` + +2. `gtcp_monitor_server.go` + +Server. + +```go +package main + +import ( + "encoding/json" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/monitor/types" +) + +func main() { + // Server, receive client data and format it into a specified data structure, print + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + info := &types.NodeInfo{} + if err := json.Unmarshal(data, info); err != nil { + glog.Errorf("invalid package structure: %s", err.Error()) + } else { + glog.Println(info) + conn.SendPkg([]byte("ok")) + } + } + }).Run() +} +``` + +3. `gtcp_monitor_client.go` + +Client. + +```go +package main + +import ( + "encoding/json" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/monitor/types" +) + +func main() { + // Data reporting client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // Format data fields using JSON + info, err := json.Marshal(types.NodeInfo{ + Cpu : float32(66.66), + Host : "localhost", + Ip : g.Map { + "etho" : "192.168.1.100", + "eth1" : "114.114.10.11", + }, + MemUsed : 15560320, + MemTotal : 16333788, + Time : int(gtime.Timestamp()), + }) + if err != nil { + panic(err) + } + // Use SendRecvPkg to send a message package and receive return + if result, err := conn.SendRecvPkg(info); err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + } else { + glog.Println(string(result)) + } +} +``` + +4. After execution + + - The client output result is: + + ```html + 2019-05-03 13:33:25.710 ok + ``` + + - The server output result is: + + ```html + 2019-05-03 13:33:25.710 &{66.66 localhost map[eth1:114.114.10.11 etho:192.168.1.100] 15560320 16333788 1556861605} + 2019-05-03 13:33:25.710 client closed + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" new file mode 100644 index 00000000000..de6bbf4ea16 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" @@ -0,0 +1,243 @@ +--- +slug: '/docs/components/network-gtcp-conn-senior' +title: 'TCP Object - Senior' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gtcp, Long Connection, Asynchronous Communication, Full Duplex Communication, SendPkg, RecvPkg, Network Programming, Concurrency Safety] +description: "Advanced development of asynchronous full-duplex communication using gtcp in the GoFrame framework. The complete example demonstrates how to handle long connections in a program, use SendPkg and RecvPkg methods for data transmission, and ensure concurrency safety." +--- + +## Advanced Development + +For short connections, each data send and receive operation closes the connection, resulting in relatively simple connection handling logic but lower communication efficiency. In most TCP communication scenarios, long connection operations are often used, with an asynchronous full-duplex TCP communication mode, meaning all communication is entirely asynchronous. In such scenarios, the `gtcp.Conn` link object may simultaneously be in multiple read and write operations (data read and write operations of the `gtcp.Conn` object are concurrency-safe), hence `SendRecv` operations will logically fail. Because after sending data in the same logical operation, immediately retrieving data might result in receiving results from other write operations. + +Both the server and client need to encapsulate the use of the `Recv*` method to obtain data in an independent asynchronous loop and handle data through `switch...case...` (fully responsible for reading data in one `goroutine`), forwarding business processing based on the request data. + +:::tip +That is, `Send*`/`Recv*` methods are concurrency-safe, but data should be sent in one go. Because asynchronous concurrent writing is supported, the `gtcp.Conn` object is implemented without any buffering. +::: + +## Example + +We'll illustrate how to implement asynchronous full-duplex communication in a program with a complete example, found at: [https://github.com/gogf/gf/tree/master/.example/net/gtcp/pkg\_operations/common](https://github.com/gogf/gf/tree/master/.example/net/gtcp/pkg_operations/common) + +1. `types/types.go` + +Define the data format for communication so we can use `SendPkg`/`RecvPkg` methods for communication. + +For simplification of test code complexity, we'll use the `JSON` data format to pass data here. In some scenarios where the message package size is strictly considered, the `Data` field can be encapsulated and parsed in binary as needed. Also, note that even using `JSON` data format, the `Act` field often defines constants for implementation, and `uint8` type suffices for most cases to reduce message package size. Here, we use strings for demonstration purposes. + +```go +package types + + +type Msg struct { + Act string // Operation + Data string // Data +} +``` + +2. `funcs/funcs.go` + +Custom definitions for sending/receiving data in defined formats, facilitating data structure encoding/parsing. + +```go +package funcs + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" +) + + +// Custom format message package sending +func SendPkg(conn *gtcp.Conn, act string, data...string) error { + s := "" + if len(data) > 0 { + s = data[0] + } + msg, err := json.Marshal(types.Msg{ + Act : act, + Data : s, + }) + if err != nil { + panic(err) + } + return conn.SendPkg(msg) +} + + +// Custom format message package receiving +func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) { + if data, err := conn.RecvPkg(); err != nil { + return nil, err + } else { + msg = &types.Msg{} + err = json.Unmarshal(data, msg) + if err != nil { + return nil, fmt.Errorf("invalid package structure: %s", err.Error()) + } + return msg, err + } +} +``` + +3. `gtcp_common_server.go` + +Communication server. In this example, the server does not actively disconnect, but sends a `doexit` message to the client after `10` seconds, notifying the client to disconnect to end the example. + +```go +package main + + +import ( + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" + "time" +) + + +func main() { + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + // Test message, let the client exit voluntarily after 10 seconds + gtimer.SetTimeout(10*time.Second, func() { + funcs.SendPkg(conn, "doexit") + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + switch msg.Act { + case "hello": onClientHello(conn, msg) + case "heartbeat": onClientHeartBeat(conn, msg) + default: + glog.Errorf("invalid message: %v", msg) + break + } + } + }).Run() +} + + +func onClientHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + funcs.SendPkg(conn, msg.Act, "Nice to meet you!") +} + + +func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) +} +``` + +4. `gtcp_common_client.go` + +Communication client. As you can see, the code structure is similar to the server, with data retrieval independently residing within a `for` loop. Each business logic sends message packages directly using the `SendPkg` method. + +Heartbeat messages are typically implemented with a `gtimer` timer. In this example, the client sends heartbeat messages to the server every `1` second and sends a `hello` test message to the server after `3` seconds. These are implemented using the `gtimer` timer, which is common in TCP communication. + +After `10` seconds of client connection, the server sends a `doexit` message to the client, prompting the client to disconnect, ending the long connection. + +```go +package main + + +import ( + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" + "time" +) + + +func main() { + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // Heartbeat message + gtimer.SetInterval(time.Second, func() { + if err := funcs.SendPkg(conn, "heartbeat"); err != nil { + panic(err) + } + }) + // Test message, send hello message to server after 3 seconds + gtimer.SetTimeout(3*time.Second, func() { + if err := funcs.SendPkg(conn, "hello", "My name's John!"); err != nil { + panic(err) + } + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + break + } + switch msg.Act { + case "hello": onServerHello(conn, msg) + case "doexit": onServerDoExit(conn, msg) + case "heartbeat": onServerHeartBeat(conn, msg) + default: + glog.Errorf("invalid message: %v", msg) + break + } + } +} + + +func onServerHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) +} + + +func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) +} + + +func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("exit command from [%s]", conn.RemoteAddr().String()) + conn.Close() +} +``` + +5. After execution + + - Server output result + + ```html + 2019-05-03 14:59:13.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:14.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:15.733 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:15.733 hello message from [127.0.0.1:51220]: My name's John! + 2019-05-03 14:59:16.731 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:17.733 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:18.731 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:19.730 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:20.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:21.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:22.698 client closed + ``` + + - Client output result + + ```html + 2019-05-03 14:59:15.733 hello response message from [127.0.0.1:8999]: Nice to meet you! + 2019-05-03 14:59:22.698 exit command from [127.0.0.1:8999] + ``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..be754496a26 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" @@ -0,0 +1,173 @@ +--- +slug: '/docs/components/network-gtcp-connection-pool' +title: 'TCP - Connection Pool' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame, GoFrame Framework, TCP Connection Pool, gtcp Module, Connection Pool Feature, Short Connection Operations, High Concurrency, Reconnect, Data Transmission, Example Program] +description: "The connection pool feature in the gtcp module of the GoFrame framework, implemented via gtcp.PoolConn, has a fixed lifespan of 600 seconds, with a reconnect mechanism upon data transmission. It is suitable for scenarios with frequent short connection operations and high connection concurrency. The article provides two examples demonstrating the basic use of the connection pool and the reconnect mechanism, helping users deeply understand the practical application of connection pool in network programming." +--- + +The `gtcp` module offers a connection pool feature, implemented by the `gtcp.PoolConn` object, where the connection pool's cached fixed lifespan is 600 seconds and internally implements a reconnect mechanism during data transmission. The connection pool is particularly suitable for scenarios involving frequent short connection operations and high connection concurrency. Next, we'll demonstrate the capabilities of the connection pool using two examples. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**API Documentation:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type PoolConn + func NewPoolConn(addr string, timeout ...int) (*PoolConn, error) + func (c *PoolConn) Close() error + func (c *PoolConn) Recv(length int, retry ...Retry) ([]byte, error) + func (c *PoolConn) RecvLine(retry ...Retry) ([]byte, error) + func (c *PoolConn) RecvPkg(option ...PkgOption) ([]byte, error) + func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) (data []byte, err error) + func (c *PoolConn) Send(data []byte, retry ...Retry) error + func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error) + func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error + func (c *PoolConn) SendRecv(data []byte, receive int, retry ...Retry) ([]byte, error) + func (c *PoolConn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) + func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *PoolConn) SendRecvWithTimeout(data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retry) error +``` + +Since `gtcp.PoolConn` inherits from `gtcp.Conn`, methods of `gtcp.Conn` can also be used. + +## Example 1, Basic Usage + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +In this example, the Server runs asynchronously using a new goroutine, while the Client runs on the main goroutine. The Server is an echo server, while the Client sends the current time to the Server every second, after which it's echoed back by the Server, and the connection port information of both sides is printed on the Client side. + +After execution, the output is as follows: + +``` +> 2018-07-11 23:29:54 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:55 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:56 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:57 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:58 127.0.0.1:55876 127.0.0.1:8999 +... +``` + +You can see that the Client's port remains unchanged, and each time you get the same `gtcp.Conn` object through `gtcp.NewConn("127.0.0.1:8999")`. Additionally, each time `conn.Close()` does not truly close the connection but rather returns the object to the pool for reuse. + +## Example 2, Connection Disconnection Scenario + +This example demonstrates how to deal with invalid connection objects when the server side closes the connection. + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + return + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +After execution, the output is as follows: + +``` +> 2018-07-20 12:56:15 127.0.0.1:59368 127.0.0.1:8999 +EOF +> 2018-07-20 12:56:17 127.0.0.1:59376 127.0.0.1:8999 +EOF +> 2018-07-20 12:56:19 127.0.0.1:59378 127.0.0.1:8999 +EOF +... +``` + +In this example, the Server closes the connection after processing each request. The Client, after sending the first request, due to the IO reuse feature of the connection pool, will obtain the same connection object for the next request. As the Server connection has been actively closed, the second request write succeeds (actually hasn't been successfully sent to the Server, needing another read operation to detect the link error), but the read fails (`EOF` indicates the target connection is closed). Therefore, at this point, when the Client executes `Close`, it will destroy the connection operation object instead of further reusing it. The next time it gets a connection object through `gtcp.NewPoolConn`, the Client will establish a new connection with the Server for data communication. So you see, the Client's port is constantly changing because the `gtcp.Conn` object is a new connection object, and the previous connection object has been destroyed. + +The IO reuse of connection objects involves very subtle changes in connection states. Since point-to-point network communication itself is a complex environment, the state of connection objects may change passively at any time. Therefore, when using the gtcp connection pool feature, it is crucial to pay attention to the reconstruction mechanism of the connection object when a communication error occurs. As soon as an error occurs, immediately discard (`Close`) the object (`gtcp.PoolConn`) and rebuild (`gtcp.NewPoolConn`). \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..1f1fb5dfd2d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" @@ -0,0 +1,86 @@ +--- +slug: '/docs/components/network-gtcp' +title: 'TCP' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gtcp,TCPServer,Server,Programming,Network Connection,Lightweight,Concurrency,Example] +description: "The gtcp module in the GoFrame framework implements an easy-to-use, lightweight TCPServer. By using gtcp, users can easily create and manage TCP services, supporting high concurrency connections. The documentation provides simple code examples to demonstrate how to create a basic echo server using the gtcp module." +--- + +## Introduction +The `gtcp` module implements an easy-to-use, lightweight `TCPServer`. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**API Documentation**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Server + func GetServer(name ...interface{}) *Server + func NewServer(address string, handler func(*Conn), name ...string) *Server + func NewServerKeyCrt(address, crtFile, keyFile string, handler func(*Conn), name ...string) *Server + func NewServerTLS(address string, tlsConfig *tls.Config, handler func(*Conn), name ...string) *Server + func (s *Server) Close() error + func (s *Server) Run() (err error) + func (s *Server) SetAddress(address string) + func (s *Server) SetHandler(handler func(*Conn)) + func (s *Server) SetTLSConfig(tlsConfig *tls.Config) + func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error +``` + +Among them, `GetServer` uses a singleton pattern to obtain/create a singleton `Server` with a given unique name, which can be dynamically modified later via `SetAddress` and `SetHandler` methods; `NewServer` directly creates a Server object based on given parameters, and a name can be specified. + +We demonstrate the use of `TCPServer` by implementing a simple `echo server`: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" +) + +func main() { + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() +} +``` + +In this example, we use `Send` and `Recv` to send and receive data. The `Recv` method receives data in a blocking way until the client "finishes sending a piece of data" (executes `Send` once, with no buffering implemented in the underlying socket communication), or closes the connection. For more on the connection object `gtcp.Conn`, please continue reading the following sections. + +After execution, we use the `telnet` tool to test: + +```bash +john@home:~$ telnet 127.0.0.1 8999 +Trying 127.0.0.1... +Connected to 127.0.0.1. +Escape character is '^]'. +hello +> hello +hi there +> hi there +``` + +For each TCP connection initiated by a client, TCPServer creates a `goroutine` to handle it until the TCP connection is disconnected. Due to the lightweight nature of goroutines, very high levels of concurrency can be supported. + +## Documentation + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..b14b4388233 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,24 @@ +--- +slug: '/docs/components/network-gudp-funcs' +title: 'UDP - Methods' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame Framework,UDP Component,gudp Module,Utility Methods,UDP Communication,Data Transmission,Network Programming,Go Language,Network Protocol] +description: "Common utility methods for UDP communication using the gudp module in the GoFrame framework, including how to create a UDP connection with NewNetConn, use Send and SendRecv methods for data transmission, and use *Pkg methods to simplify data packet protocol transmission." +--- + +The `gudp` module also provides some commonly used utility methods. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**API Documentation**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + + + +The tools in `gudp` are relatively simple, so a detailed introduction is not provided here. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..ed4074ba686 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" @@ -0,0 +1,92 @@ +--- +slug: '/docs/components/network-gudp-conn' +title: 'UDP - Object' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame Framework, gudp.Conn, UDP Component, UDP Connection, gudp Module, Network Programming, Go Language, Data Communication, Programming Example] +description: "Using the GoFrame framework for UDP component development, specifically the use of the gudp.Conn connection object. The article provides detailed function interface descriptions and a complete example code for client-server communication, helping developers quickly master the specific operations and application scenarios of the UDP connection object." +--- + +The `gudp` module provides a very simple and easy-to-use `gudp.Conn` link operation object. + +**Usage:** + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**API Documentation:** [https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + +## Introduction + +Most of the operations of `gudp.Conn` are similar to the operation mode of `gtcp` (most of the method names are the same), but since `UDP` is a connectionless protocol, `gudp.Conn` (underlying communication port) can only complete up to one data write and read at a time. The client will need to create a new `Conn` object for communication the next time it communicates with the target server. + +## Usage Example + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gudp" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + ) + // Server + go gudp.NewServer("127.0.0.1:8999", func(conn *gudp.ServerConn) { + defer conn.Close() + for { + data, addr, err := conn.Recv(-1) + if len(data) > 0 { + if err = conn.Send(append([]byte("> "), data...), addr); err != nil { + logger.Error(ctx, err) + } + } + if err != nil { + logger.Error(ctx, err) + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gudp.NewClientConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + logger.Error(ctx, err) + } + conn.Close() + } else { + logger.Error(ctx, err) + } + time.Sleep(time.Second) + } +} +``` + +This example is similar to the communication example in `gtcp.Conn`, with the difference that the client and server cannot maintain a connection, requiring a new connection object for each communication. + +After execution, the output is as follows: + +```text +> 2018-07-21 23:13:31 127.0.0.1:33271 127.0.0.1:8999 +> 2018-07-21 23:13:32 127.0.0.1:45826 127.0.0.1:8999 +> 2018-07-21 23:13:33 127.0.0.1:58027 127.0.0.1:8999 +> 2018-07-21 23:13:34 127.0.0.1:33056 127.0.0.1:8999 +> 2018-07-21 23:13:35 127.0.0.1:39260 127.0.0.1:8999 +> 2018-07-21 23:13:36 127.0.0.1:33967 127.0.0.1:8999 +> 2018-07-21 23:13:37 127.0.0.1:52359 127.0.0.1:8999 +... +``` \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..2f72aff7f75 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" @@ -0,0 +1,60 @@ +--- +slug: '/docs/components/network-gudp' +title: 'UDP' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, UDP Component, UDP Protocol, Connectionless Protocol, gudp.Server, gudp.Conn, API Documentation, gudp Usage, NewServer, SetAddress] +description: "UDP component in the GoFrame framework, implementing a simple unreliable information delivery service using gudp.Server and gudp.Conn for the UDP protocol. Provides example code on creating and running a UDP server, along with related API documentation links for reference." +--- + +## Introduction +`UDP (User Datagram Protocol)` is a connectionless transport layer protocol that provides a simple unreliable information delivery service oriented towards transactions. The `UDP` server is implemented through `gudp.Server`, while the client is implemented using the `gudp.ClientConn` object or utility methods. + +**Usage**: + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**API Documentation**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + + +## Usage Example + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/net/gudp" +) + +func main() { + handler := func(conn *gudp.ServerConn) { + defer conn.Close() + for { + if data, addr, _ := conn.Recv(-1); len(data) > 0 { + fmt.Println(string(data), addr.String()) + } + } + } + err := gudp.NewServer("127.0.0.1:8999", handler).Run() + if err != nil { + fmt.Println(err) + } +} +``` + +`UDPServer` runs in a blocking manner. Users can perform concurrent processing in the custom callback function based on the read content. + +On `Linux`, you can use the following command to send `UDP` data to the server for testing, and then check whether there is output on the server side: + +```bash +echo "hello" > /dev/udp/127.0.0.1/8999 +``` + +## Documentation +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..508f8be1854 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/network' +title: 'Network' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, GoFrame framework, network services, development, network protocols, service optimization, Web services, RESTful API, TCP/UDP, load balancing] +description: "Using the GoFrame framework for network service development, covering the implementation of network protocols, RESTful API design, TCP/UDP communication, and how to optimize network service performance through load balancing." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" new file mode 100644 index 00000000000..d005cfe420e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/error-gerror' +title: 'Error Handling' +sidebar_position: 0 +hide_title: true +description: "The gerror module in the GoFrame framework is used for error handling. As a core component of GoFrame, the gerror module provides a unified error handling mechanism that improves code readability and maintainability, suitable for software development in the Go language." +keywords: [GoFrame, GoFrame framework, gerror, error handling, Go language, programming, software development, code readability, code maintenance, core component] +--- + +The error handling function is implemented by the `gerror` module. Please refer to the [Error Handling](../../核心组件/错误处理/错误处理.md) section for details. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" new file mode 100644 index 00000000000..5a8680d152e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/error-gcode' +title: 'Error Code' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Error Code Interface,Error Handling,Core Components,Interface Reference,gcode,Component Development,Architectural Design,System Stability] +description: "Learn about the error code interface gcode in the GoFrame framework and how to effectively handle errors in your project. This document provides a detailed introduction to the functionality and usage of the error code interface, helping developers build stable and reliable applications in the GoFrame framework, ensuring system high availability and maintainability." +--- + +Please refer to the core components section: [Error Code - Interface](../../核心组件/错误处理/错误处理-错误码特性/错误处理-错误码接口.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..93edba961cd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/error' +title: 'Errors' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame, GoFrame framework, error management, error handling, exception capture, web development, application errors, programming errors, error logging, error debugging] +description: "In the GoFrame framework, error management and handling are conducted. By capturing and handling errors appropriately, the stability and user experience of web applications can be enhanced. A detailed explanation of error logging and exception capture methods is provided to help developers better deal with runtime errors." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..ae97401d86d --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/deploy/proxy' +title: 'Deployment - Proxy' +sidebar_position: 1 +hide_title: true +keywords: [Proxy Deployment, GoFrame, Reverse Proxy, Nginx, Static Files, Golang Application, WebServer, Dynamic Requests, Load Balancing, Domain Configuration] +description: "Deploy GoFrame applications using Nginx as a reverse proxy front-end access layer. By configuring static file extensions or directories, you can effectively separate static and dynamic requests to improve performance. Configuration examples show how to forward requests to a Golang application for a professional WebServer deployment solution." +--- + +Proxy deployment involves placing a layer of third-party `WebServer` server in front to handle all requests, selectively forwarding some requests (often dynamically processed requests) to the backend `Golang` applications for execution. The backend-deployed `Golang` applications can be configured with multiple instances. This approach is commonly used in complex `WebServer` configurations, such as scenarios requiring static file separation, multiple domain and certificate configurations, custom load balancing layers, and more. + +Although `Golang` implemented `WebServer` can handle static files, it is relatively simple and less performant compared to professional `WebServer` like `nginx`/ `apache`. Therefore, using `Golang WebServer` as the front-end service to directly handle static file requests is not recommended. + +## `Nginx` + +We recommend using `Nginx` as a reverse proxy front-end access layer, which has two configuration methods for separating static and dynamic requests. + +### Static File Suffix + +This approach differentiates by file name suffix, delegating specified static files to `nginx` for processing, while other requests are forwarded to the `golang` application. The configuration example is as follows: + +```conf +server { + listen 80; + server_name goframe.org; + + access_log /var/log/gf-app-access.log; + error_log /var/log/gf-app-error.log; + + location ~ .*\.(gif|jpg|jpeg|png|js|css|eot|ttf|woff|svg|otf)$ { + access_log off; + expires 1d; + root /var/www/gf-app/public; + try_files $uri @backend; + } + + location / { + try_files $uri @backend; + } + + location @backend { + proxy_pass http://127.0.0.1:8199; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +Here, `8199` is the local `golang` application `WebServer` listening port. + +For example, under this configuration, you can access the specified static file via `http://goframe.org/my.png`. + +### Static File Directory + +This method distinguishes by file directory, delegating access requests for specified directories to `nginx`, while other requests are forwarded to the `golang` application. The configuration example is as follows: + +```conf +server { + listen 80; + server_name goframe.org; + + access_log /var/log/gf-app-access.log; + error_log /var/log/gf-app-error.log; + + location ^~ /public { + access_log off; + expires 1d; + root /var/www/gf-app; + try_files $uri @backend; + } + + location / { + try_files $uri @backend; + } + + location @backend { + proxy_pass http://127.0.0.1:8199; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +Here, `8199` is the local `golang` application `WebServer` listening port. + +For example, under this configuration, you can access static files via `http://goframe.org/public/my.png`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..5bed87e13f0 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" @@ -0,0 +1,87 @@ +--- +slug: '/docs/deploy/container' +title: 'Deployment - Container' +sidebar_position: 2 +hide_title: true +keywords: [container deployment, docker, golang, cross-compilation, static compilation, alpine, Dockerfile, image distribution, kubernetes, docker swarm] +description: "Deploy Golang applications using Dockerization, exploring cross-compilation technology, and how to build and distribute Docker images on Alpine images. In enterprise environments, it's recommended to combine with Kubernetes or Docker Swarm for container orchestration to improve system scalability and reliability." +--- + +Container deployment refers to deploying Golang applications using Dockerization, the most popular and recommended deployment method in the cloud service era. + +> In the examples below, we use `main` as the project name consistently. + +## 1\. Compile Program + +Cross-platform cross-compilation is one of Golang's features, allowing us to easily compile the version needed for the target server platform, and it's statically compiled, making it very easy to solve runtime dependencies. + +Use the following command to statically compile an executable for the Linux platform amd64 architecture: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go +``` + +The generated `main` is our statically compiled executable file deployable on `Linux amd64`. + +## 2\. Compile Image + +We need to compile this executable file `main` into a Docker image for distribution and deployment. The runtime environment for Golang is recommended to use the Alpine base system image, resulting in a container image of about 20MB. + +A reference `Dockerfile` is as follows: + +```dockerfile +FROM loads/alpine:3.8 + +LABEL maintainer="john@goframe.org" + +############################################################################### +# INSTALLATION +############################################################################### + +# Set a fixed project path +ENV WORKDIR /app/main + +# Add application executable and set permissions +ADD ./main $WORKDIR/main +RUN chmod +x $WORKDIR/main + +# Add static resource files +ADD resource $WORKDIR/resource + +############################################################################### +# START +############################################################################### +WORKDIR $WORKDIR +CMD ./main +``` + +In this example, our base image is `loads/alpine:3.8`. For users in China, it is recommended to use this base image. The Dockerfile address for the base image is: [https://github.com/gqcn/dockerfiles](https://github.com/gqcn/dockerfiles), and the repository address is: [https://hub.docker.com/u/loads](https://hub.docker.com/u/loads). + +Then use the `docker build -t main .` command to compile and generate a Docker image named `main`. + +**Notes** + +It should be noted that in some project architectural designs, **static files** and **configuration files** may not be compiled and published with the image but managed and published separately. + +For example, in projects using the MVVM pattern (such as those using the Vue framework), the front end and back end are often very independent, so the `public` directory is often not included in the image. In projects that use a `configuration management center` (such as using `consul`/`etcd`/`zookeeper`), the `config` directory is often not needed. + +Therefore, the use of the `Dockerfile` provided above is only for reference, and necessary adjustments should be made based on actual conditions. + +## 3\. Run Image + +Use the following command to run the image just compiled: + +```bash +docker run main +``` + +## 4\. Image Distribution + +For container distribution, you can use the official Docker platform: [https://hub.docker.com/](https://hub.docker.com/), or consider using Alibaba Cloud in China: [https://www.aliyun.com/product/acr](https://www.aliyun.com/product/acr). + +## 5\. Container Orchestration + +In enterprise-level production environments, Docker containers often need to be used in conjunction with container orchestration tools like Kubernetes or Docker Swarm. Container orchestration involves a lot of content, and interested individuals can refer to the following materials: + +- [https://kubernetes.io/](https://kubernetes.io/) +- [https://docs.docker.com/swarm/](https://docs.docker.com/swarm/) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..d726c655103 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" @@ -0,0 +1,135 @@ +--- +slug: '/docs/deploy/standalone' +title: 'Deployment - Standalone' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, standalone deployment, server deployment, nix server, Ubuntu deployment, background daemon process, process management, Linux, Windows] +description: "Standalone deployment of applications developed using the GoFrame framework, suitable for *nix series servers like Linux and MacOS. The article provides detailed explanations on setting up and managing background daemon processes on Ubuntu systems using tools like nohup, tmux, supervisor, systemctl, and screen. Additionally, it includes guidance on using the NSSM tool on Windows systems." +--- + +Applications developed using `GoFrame` can be independently deployed on servers to run as background daemon processes. This mode is commonly used in simple API service projects. + +We recommend using the `*nix` server series (including: `Linux`, `MacOS`, `*BSD`). Below, we use the `Ubuntu` system as an example to introduce how to deploy projects developed using the `GoFrame` framework. + +## \*nix + +### 1\. `nohup` + +We can use the simple `nohup` command to run the application as a background daemon process so that even if a remote SSH connection is disconnected, the program's execution will not be affected. The `nohup` command tool is often pre-installed in popular Linux distributions. The command is as follows: + +```bash +nohup ./gf-app & +``` + +### 2\. `tmux` + +`tmux` is a terminal multiplexer tool under `Linux` that can open different terminal windows to run applications as background daemon processes. Even if the remote `SSH` connection is disconnected, the program's execution will not be affected. Install it directly on the `ubuntu` system using `sudo apt-get install tmux`. Use the following steps to run the application in the background. + +1. `tmux new -s gf-app`; +2. Execute `./gf-app` in the new terminal window; +3. Use the shortcut `ctrl` + `B & D` to exit the current terminal window; +4. Use `tmux attach -t gf-app` to enter the previous terminal window; + +### 3\. `supervisor` + +`supervisor` is a universal process control program developed in `Python`, capable of transforming a normal command-line process into a background `daemon` and monitoring the process status, automatically restarting it if it exits abnormally. Official website: [http://supervisord.org/](http://supervisord.org/) Common configuration is as follows: + +```ini +[program:gf-app] +user = root +directory = /var/www +command = /var/www/main +stdout_logfile = /var/log/gf-app-stdout.log +stderr_logfile = /var/log/gf-app-stderr.log +autostart = true +autorestart = true +``` + +The steps are as follows: + +1. Use `sudo service supervisor start` to start the `supervisor` service; +2. Create an application configuration file `/etc/supervisor/conf.d/gf-app.conf`, with the content as above; +3. Use `sudo supervisorctl` to enter the `supervisor` management terminal; +4. Use `reload` to reread the configuration file and restart all processes managed by `supervisor`; +5. You can also use `update` to reload the configuration (default without restarting) and then use `start gf-app` to start the specified application; +6. Then use the `status` command to view the status of the processes managed by `supervisor`; + +Sharing pitfalls: + +1. After changing the `conf` configuration file, execute `reload` in `supervisorctl` to update and use the latest configuration. +2. The `directory` configuration usually cannot be omitted, specifying the current working directory path, and must be configured before `command`. +3. The `command` needs to use an absolute path; otherwise, it will not find the executable file. + +### 4\. `systemctl` + +`Systemd` is a `Linux` system tool used to start daemon processes, and it has become the standard configuration for most distributions. + +`systemctl` is the main command of `Systemd` used to manage the system. You can refer to [Ruan Yifeng's interpretation of Systemd](http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html), particularly sections four and five. + +In fact, most of our services are managed with `systemctl`, such as `MySQL, Nginx`, etc. + +Common configuration is as follows: + +```ini +[Unit] +# Unit description +Description=GF APP +# Execute this program after what service starts +After=mysql.service + +[Service] +Type=simple +# Directory of program execution +WorkingDirectory=/data/server/gfapp/ +# Startup script command +ExecStart=/data/server/gfapp/gfapp +# Restart conditions +Restart=always +# Seconds to restart +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +Usage: + +1. Create an application configuration file `/etc/systemd/system/gfapp.service`, with the content as above; +2. Use `systemctl daemon-reload` to reload services; +3. Execute `systemctl start gfapp` to start the service; +4. Finally, execute `systemctl status gfapp` to view the service running status information; +5. Execute `systemctl enable gfapp` to add the service to the boot startup items; +6. Note: The executed `gfapp` uses the file name as the service name; +7. Common commands are: `start(start), stop(stop), restart(restart), status(view running status), enable(add to boot startup), disable(remove program from boot startup)` + +### 5\. `screen` + +`Screen` is a free software developed under the `GNU` project for command-line terminal switching. Users can connect to multiple local or remote command-line sessions simultaneously and switch between them freely. `GNU Screen` can be considered a command-line interface version of a window manager. It provides a unified interface and corresponding functionality for managing multiple sessions. + +Installation: + +`sudo apt install -y screen` ( `debian` series) + +`sudo yum install -y screen`  ( `centos`) + +**Common parameters:** + +1. `screen -S yourname` -> Create a session called yourname +2. `screen -ls` -> List all current sessions +3. `screen -r yourname` -> Return to yourname session +4. `screen -d yourname` -> Remotely detach a session +5. `screen -d -r yourname` -> End the current session and return to yourname session + +**Usage:** + +1. Use the command `screen -S gfapp` to create a session; +2. Execute `./gf-app` in the new terminal window; +3. Execute `ctrl-a, ctrl-d` to temporarily leave the current session; +4. Execute `screen -r gfapp` to return to the command window; if not successful, the window might be occupied (`Attached`), try `screen -Dr gfapp`; +5. Execute `screen -X -S gfapp quit` to end the program; + +## windows + +### 1. `NSSM` + +[Small and Practical NSSM Packaging Windows Service Tool Introduction - Zhihu (zhihu.com)](https://zhuanlan.zhihu.com/p/455904037) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..4ee04d4fb88 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/deploy' +title: 'Project Deployment' +sidebar_position: 10 +hide_title: true +keywords: [Project Deployment, GoFrame, GoFrame Framework, Documentation, Guide, How to Deploy, Deployment Steps, Development Framework, Server Configuration, Application Launch] +description: "A detailed guide to project deployment using the GoFrame framework, including complete steps from preparing the server environment to launching the application, helping developers smoothly complete the deployment process." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/balancer.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/balancer.md new file mode 100644 index 00000000000..92c5363424e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/balancer.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/balancer' +title: 'Load Balancer' +hide_title: true +sidebar_position: 4 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/http/http.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/http/http.md new file mode 100644 index 00000000000..7e19ae711c2 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/http/http.md @@ -0,0 +1,94 @@ +--- +title: HTTP Service +slug: /examples/balancer/http +keywords: [load balancer, http, service discovery, goframe] +description: HTTP service load balancing in GoFrame +hide_title: true +--- + +# Load Balancer - HTTP Service Example + +Github Source: https://github.com/gogf/examples/tree/main/balancer/http + + +## Description + +This example demonstrates how to implement HTTP service load balancing with `GoFrame`. It shows: +- Service registration using `etcd` +- Client-side load balancing +- Round-robin load balancing strategy +- HTTP service communication + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Structure + +```text +. +├── client/ # HTTP client implementation with load balancing +│ └── client.go # Client code with round-robin balancer +├── server/ # HTTP server implementation +│ └── server.go # Server code with service registration +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Prerequisites + +1. Running `etcd` server: + ```bash + # Using docker + docker run -d --name etcd-server \ + --publish 2379:2379 \ + --publish 2380:2380 \ + --env ALLOW_NONE_AUTHENTICATION=yes \ + bitnami/etcd:latest + ``` + +## Usage + +1. Start multiple server instances (use random different ports): + ```bash + # Terminal 1 + cd server + go run server.go + + # Terminal 2 + cd server + go run server.go + + # Terminal 3 + cd server + go run server.go + ``` + +2. Run the client to test load balancing: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +1. Server Implementation (`server/server.go`): + - HTTP server setup using `GoFrame` + - Service registration with `etcd` + - Simple HTTP endpoint that returns "Hello world" + - Automatic service discovery registration + +2. Client Implementation (`client/client.go`): + - Service discovery using `etcd` + - Round-robin load balancing strategy + - Multiple request demonstration + - Automatic service discovery and load balancing + +## Notes + +- The example uses `etcd` for service registration and discovery +- Round-robin load balancing is implemented for demonstration +- The client automatically handles service discovery and load balancing +- Multiple server instances can be started to demonstrate load distribution diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/polaris/polaris.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/polaris/polaris.md new file mode 100644 index 00000000000..88986561333 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/balancer/polaris/polaris.md @@ -0,0 +1,105 @@ +--- +title: Polaris Integration +slug: /examples/balancer/polaris +keywords: [load balancer, polaris, service discovery, goframe] +description: An example demonstrating HTTP service load balancing using `Polaris` in `GoFrame` +hide_title: true +--- + +# Load Balancer - `Polaris` Integration Example + +Github Source: https://github.com/gogf/examples/tree/main/balancer/polaris + + +## Description + +This example demonstrates how to implement HTTP service load balancing with `GoFrame` using `Polaris`. It shows: +- Service registration using `Polaris` +- Client-side load balancing +- Round-robin load balancing strategy +- HTTP service communication +- Local cache and logging configuration + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Polaris Registry](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) + +## Structure + +```text +. +├── client/ # HTTP client implementation with load balancing +│ └── client.go # Client code with round-robin balancer +├── server/ # HTTP server implementation +│ └── server.go # Server code with service registration +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Prerequisites + +1. Running `Polaris` server: + ```bash + # Using docker + docker run -d --name polaris \ + -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \ + polarismesh/polaris-standalone:v1.17.2 + ``` + +## Configuration + +The example uses the following `Polaris` configurations: +- Server address: `127.0.0.1:8091` +- Local cache directory: `/polaris/backup` +- Log directory: `/polaris/log` +- Service TTL: 10 seconds + +## Usage + +1. Start multiple server instances (use random different ports): + ```bash + # Terminal 1 + cd server + go run server.go + + # Terminal 2 + cd server + go run server.go + + # Terminal 3 + cd server + go run server.go + ``` + +2. Run the client to test load balancing: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +1. Server Implementation (`server/server.go`): + - HTTP server setup using `GoFrame` + - Service registration with `Polaris` + - Simple HTTP endpoint that returns "Hello world" + - Automatic service discovery registration + - Configurable TTL for service registration + +2. Client Implementation (`client/client.go`): + - Service discovery using `Polaris` + - Round-robin load balancing strategy + - Multiple request demonstration with timing information + - Automatic service discovery and load balancing + - Local cache and logging configuration + +## Notes + +- The example uses `Polaris` for service registration and discovery +- Round-robin load balancing is implemented for demonstration +- The client automatically handles service discovery and load balancing +- Local cache is used to improve performance +- Logging is configured for better debugging diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/apollo/apollo.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/apollo/apollo.md new file mode 100644 index 00000000000..9eb741e0da6 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/apollo/apollo.md @@ -0,0 +1,163 @@ +--- +title: Apollo +slug: /examples/config/apollo +keywords: [config, apollo, goframe] +description: Apollo configuration center integration with GoFrame +hide_title: true +--- + +# `Apollo` Configuration Center Example + +Github Source: https://github.com/gogf/examples/tree/main/config/apollo + + +## Description + +This directory contains an example demonstrating how to integrate `Apollo` configuration center with `GoFrame` applications. It shows: + +1. `Apollo` Client Configuration + - `Apollo` client setup and initialization + - Configuration adapter implementation + - Error handling and logging + +2. Configuration Management + - Configuration loading and parsing + - Dynamic configuration updates + - Configuration value retrieval + +## Directory Structure + +```text +. +├── boot/ # Bootstrap configuration +│ └── boot.go # Apollo client initialization +├── main.go # Main application entry +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Apollo Config](https://github.com/gogf/gf/tree/master/contrib/config/apollo) + +## Features + +The example showcases the following features: + +1. `Apollo` Integration + - Client configuration + - Connection management + - Namespace handling + - Error handling + +2. Configuration Management + - Configuration loading + - Value retrieval + - Type conversion + - Default values + +3. Dynamic Updates + - Configuration watching + - Change notification + - Hot reload support + +## Configuration + +### `Apollo` Server +1. Server Configuration: + - Default port: 8080 + - Default cluster: default + - Default namespace: application + +2. Authentication: + - AppID based authentication + - Optional access key support + - Cluster level isolation + +### Client Configuration +1. Basic Settings: + - AppID: Application identifier + - Cluster: Configuration cluster + - IP: `Apollo` server address + +2. Advanced Options: + - Namespace customization + - Cache management + - Retry settings + +## Usage + +1. Start `Apollo` Server: + ```bash + # Start Apollo server using Docker + docker run -p 8080:8080 apolloconfig/apollo-portal + ``` + +2. Configure `Apollo`: + - Create application namespace + - Add configuration items + - Publish configuration + +3. Run Example: + ```bash + go run main.go + ``` + +## Implementation Details + +1. Client Setup + - Apollo client initialization + - Configuration adapter setup + - Error handling configuration + +2. Configuration Access + - Value retrieval methods + - Type conversion handling + - Default value management + +3. Error Handling + - Connection errors + - Configuration errors + - Type conversion errors + +## Best Practices + +1. Client Configuration + - Use environment variables for sensitive data + - Configure appropriate timeouts + - Implement proper error handling + +2. Configuration Management + - Use structured configuration keys + - Implement configuration validation + - Handle configuration updates gracefully + +3. Error Handling + - Log configuration errors + - Provide fallback values + - Implement retry mechanisms + +## Troubleshooting + +1. Connection Issues: + - Verify Apollo server status + - Check network connectivity + - Validate authentication settings + +2. Configuration Issues: + - Check namespace configuration + - Verify configuration format + - Review access permissions + +3. Client Issues: + - Check client configuration + - Verify AppID settings + - Review cluster configuration + +4. General Issues: + - Check application logs + - Verify dependencies + - Review error messages diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/config.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/config.md new file mode 100644 index 00000000000..cd025d2b03e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/config.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/config' +title: 'Service Config' +hide_title: true +sidebar_position: 5 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/consul/consul.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/consul/consul.md new file mode 100644 index 00000000000..8bdadc81761 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/consul/consul.md @@ -0,0 +1,130 @@ +--- +title: Consul +slug: /examples/config/consul +keywords: [config, consul, goframe] +description: Consul configuration center integration with GoFrame +hide_title: true +--- + +## `Consul` Configuration Center Example + +## Description + +This directory contains an example demonstrating how to integrate `Consul` configuration center with `GoFrame` applications. It shows: + +1. `Consul` Client Configuration + - `Consul` client setup and initialization + - Configuration adapter implementation + - Error handling and logging + +2. Configuration Management + - Configuration loading and parsing + - Dynamic configuration updates + - Configuration value retrieval + +## Directory Structure + +```text +. +├── boot/ # Bootstrap configuration +│ └── boot.go # Consul client initialization +├── main.go # Main application entry +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Consul Config](https://github.com/gogf/gf/tree/master/contrib/config/consul) + +## Features + +The example showcases the following features: + +1. `Consul` Integration + - Client configuration + - Connection management + - Key-Value store access + - Error handling + +2. Configuration Management + - Configuration loading + - Value retrieval + - Type conversion + - Default values + +3. Dynamic Updates + - Configuration watching + - Change notification + - Hot reload support + +## Configuration + +### `Consul` Server +1. Server Configuration: + - Default port: 8500 + - Default datacenter: dc1 + - Default scheme: http + +2. Authentication: + - Token-based authentication + - ACL system support + - Datacenter level isolation + +### Client Configuration +1. Basic Settings: + - Address: `Consul` server address + - Scheme: HTTP/HTTPS + - Datacenter: Configuration datacenter + - Token: Access token + +2. Advanced Options: + - Path customization + - Watch configuration + - Transport settings + - Retry options + +## Usage + +1. Start `Consul` Server: + ```bash + # Start Consul server using Docker + docker run -d --name consul \ + -p 8500:8500 -p 8600:8600/udp \ + hashicorp/consul + ``` + +2. Configure `Consul`: + - Access `Consul` UI at http://localhost:8500 + - Create Key-Value pairs + - Set up ACL tokens if needed + +3. Run Example: + ```bash + go run main.go + ``` + +## Implementation Details + +1. Client Setup (`boot/boot.go`): + - Consul client configuration + - Transport layer setup + - Authentication configuration + - Error handling + +2. Configuration Access (`main.go`): + - Configuration availability check + - Bulk configuration retrieval + - Single value access + - Error handling + +## Notes + +- The example uses Consul's Key-Value store for configuration management +- Configuration changes in Consul are automatically reflected in the application +- The client supports secure connections via HTTPS +- ACL tokens can be used for secure access +- Configuration paths can be customized based on needs diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/kubecm/kubecm.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/kubecm/kubecm.md new file mode 100644 index 00000000000..fb3929f485d --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/kubecm/kubecm.md @@ -0,0 +1,145 @@ +--- +title: Kubernetes ConfigMap +slug: /examples/config/kubecm +keywords: [config, kubernetes, configmap, goframe] +description: Kubernetes ConfigMap configuration integration with GoFrame +hide_title: true +--- + +# `Kubernetes ConfigMap` Configuration Example + +Github Source: https://github.com/gogf/examples/tree/main/config/kubecm + + +## Description + +This directory contains an example demonstrating how to integrate `Kubernetes ConfigMap` with `GoFrame` applications for configuration management. It shows: + +1. `Kubernetes` Client Configuration + - In-Pod configuration setup + - Out-of-Pod configuration setup + - `ConfigMap` access and management + - Error handling and logging + +2. Configuration Management + - Configuration loading and parsing + - Dynamic configuration updates + - Configuration value retrieval + +## Directory Structure + +```text +. +├── boot_in_pod/ # Bootstrap configuration for in-pod deployment +│ └── boot.go # In-pod client initialization +├── boot_out_pod/ # Bootstrap configuration for out-of-pod deployment +│ └── boot.go # Out-of-pod client initialization +├── main.go # Main application entry +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Kubernetes ConfigMap Config](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) + +## Features + +The example showcases the following features: + +1. `Kubernetes` Integration + - In-Pod configuration + - Out-of-Pod configuration + - `ConfigMap` management + - Error handling + +2. Configuration Management + - Configuration loading + - Value retrieval + - Type conversion + - Default values + +3. Dynamic Updates + - Configuration watching + - Change notification + - Hot reload support + +## Configuration + +### `Kubernetes` Setup +1. Cluster Configuration: + - `Kubernetes` cluster access + - Namespace management + - RBAC permissions + +2. `ConfigMap` Setup: + - `ConfigMap` creation + - Data item management + - Access control + +### Client Configuration +1. In-Pod Settings: + - Automatic service account + - `ConfigMap` name + - Data item name + +2. Out-of-Pod Settings: + - `KubeConfig` path + - Namespace selection + - Client configuration + - Access permissions + +## Usage + +1. Create `ConfigMap`: + ```bash + # Create a ConfigMap with configuration data + kubectl create configmap test-configmap --from-file=config.yaml=./config.yaml + ``` + +2. Configure Access: + ```bash + # Ensure proper RBAC permissions + kubectl create role config-reader --verb=get,list,watch --resource=configmaps + kubectl create rolebinding config-reader-binding --role=config-reader --serviceaccount=default:default + ``` + +3. Run Example: + ```bash + # For in-pod deployment + go run main.go + + # For out-of-pod deployment (local development) + KUBE_CONFIG=~/.kube/config go run main.go + ``` + +## Implementation Details + +1. In-Pod Setup (`boot_in_pod/boot.go`): + - Simple configuration for in-pod deployment + - Automatic service account usage + - Minimal configuration required + +2. Out-of-Pod Setup (`boot_out_pod/boot.go`): + - External cluster access configuration + - KubeConfig file usage + - Namespace specification + - Custom client configuration + +3. Configuration Access (`main.go`): + - Configuration availability check + - Bulk configuration retrieval + - Single value access + - Error handling + +## Notes + +- The example supports both in-pod and out-of-pod deployments +- In-pod deployment uses the pod's service account +- Out-of-pod deployment requires KubeConfig file +- ConfigMap changes are automatically reflected +- Proper RBAC permissions are required +- Configuration paths can be customized based on needs diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/nacos/nacos.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/nacos/nacos.md new file mode 100644 index 00000000000..ee6bca8478e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/nacos/nacos.md @@ -0,0 +1,110 @@ +--- +title: Nacos +slug: /examples/config/nacos +keywords: [config, nacos, goframe] +description: Nacos configuration center integration with GoFrame +hide_title: true +--- + +## `Nacos` Configuration Center Example + +## Description + +This directory contains an example demonstrating how to integrate `Nacos` configuration center with `GoFrame` applications. It shows: + +1. `Nacos` Client Configuration + - `Nacos` client setup and initialization + - Configuration adapter implementation + - Error handling and logging + +2. Configuration Management + - Configuration loading and parsing + - Dynamic configuration updates + - Configuration value retrieval + +## Directory Structure + +```text +. +├── boot/ # Bootstrap configuration +│ └── boot.go # Nacos client initialization +├── main.go # Main application entry +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Nacos Config](https://github.com/gogf/gf/tree/master/contrib/config/nacos) +- [Nacos Server](https://nacos.io/) + +## Features + +The example showcases the following features: + +1. `Nacos` Integration + - Client configuration + - Server connection management + - Configuration namespace + - Error handling + +2. Configuration Management + - Configuration loading + - Value retrieval + - Type conversion + - Default values + +3. Dynamic Updates + - Configuration watching + - Change notification + - Hot reload support + +## Configuration + +### `Nacos` Server +1. Server Configuration: + - Default port: 8848 + - Default address: localhost + - Default protocol: HTTP + +2. Authentication: + - Username/Password authentication + - Namespace isolation + - Group level access control + +### Client Configuration +1. Basic Settings: + - Server address and port + - Cache directory + - Log directory + - Data ID and Group + +2. Advanced Options: + - Namespace customization + - Cache management + - Logging configuration + - Retry settings + +## Usage + +1. Start `Nacos` Server: + ```bash + # Start Nacos server using Docker + docker run -d --name nacos \ + -p 8848:8848 -p 9848:9848 \ + -e MODE=standalone \ + nacos/nacos-server:latest + ``` + +2. Configure `Nacos`: + - Access `Nacos` console at http://localhost:8848/nacos + - Default credentials: nacos/nacos + - Create configuration items + +3. Run Example: + ```bash + go run main.go + ``` diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/polaris/polaris.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/polaris/polaris.md new file mode 100644 index 00000000000..30fda377472 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/config/polaris/polaris.md @@ -0,0 +1,138 @@ +--- +title: Polaris +slug: /examples/config/polaris +keywords: [config, polaris, goframe] +description: Polaris configuration center integration with GoFrame +hide_title: true +--- + +# `Polaris` Configuration Center Example + +Github Source: https://github.com/gogf/examples/tree/main/config/polaris + + +## Description + +This directory contains an example demonstrating how to integrate `Polaris` configuration center with `GoFrame` applications. It shows: + +1. `Polaris` Client Configuration + - `Polaris` client setup and initialization + - Configuration adapter implementation + - Error handling and logging + +2. Configuration Management + - Configuration loading and parsing + - Dynamic configuration updates + - Configuration value retrieval + +## Directory Structure + +```text +. +├── boot/ # Bootstrap configuration +│ └── boot.go # Polaris client initialization +├── main.go # Main application entry +├── testdata/ # Test configuration files +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Polaris Config](https://github.com/gogf/gf/tree/master/contrib/config/polaris) +- [Polaris Server](https://github.com/polarismesh/polaris) + +## Features + +The example showcases the following features: + +1. `Polaris` Integration + - Client configuration + - Server connection management + - Configuration namespace + - Error handling + +2. Configuration Management + - Configuration loading + - Value retrieval + - Type conversion + - Default values + +3. Dynamic Updates + - Configuration watching + - Change notification + - Hot reload support + +## Configuration + +### `Polaris` Server +1. Server Configuration: + - Default port: 8090 + - Default address: localhost + - Default protocol: `gRPC` + +2. Authentication: + - Namespace isolation + - Group level access control + - Access token support + +### Client Configuration +1. Basic Settings: + - Namespace + - File group + - File name + - Configuration path + - Log directory + +2. Advanced Options: + - Watch mode + - Retry settings + - Cache management + - Logging configuration + +## Usage + +1. Start `Polaris` Server: + ```bash + # Start Polaris server using Docker + docker run -d --name polaris \ + -p 8090:8090 -p 8091:8091 \ + polarismesh/polaris-server + ``` + +2. Configure `Polaris`: + - Access `Polaris` console at http://localhost:8090 + - Create configuration items + - Set up access control + +3. Run Example: + ```bash + go run main.go + ``` + +## Implementation Details + +1. Client Setup (`boot/boot.go`): + - Namespace configuration + - File group and name setup + - Configuration path setup + - Log directory configuration + - Watch mode enablement + +2. Configuration Access (`main.go`): + - Configuration availability check + - Bulk configuration retrieval + - Single value access + - Error handling + +## Notes + +- The example uses Polaris's configuration center features +- Configuration changes in Polaris are automatically reflected when watch mode is enabled +- Configuration is organized by namespace and file group +- Log files are stored in the specified log directory +- Configuration path points to the Polaris server configuration file +- Watch mode enables dynamic configuration updates diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/balancer/balancer.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/balancer/balancer.md new file mode 100644 index 00000000000..8fc31963516 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/balancer/balancer.md @@ -0,0 +1,107 @@ +--- +title: Load Balancing +slug: /examples/grpc/balancer +keywords: [grpc, load balancing, service discovery, goframe] +description: gRPC load balancing in GoFrame +hide_title: true +--- + +# `gRPC` - Load Balancing + +Github Source: https://github.com/gogf/examples/tree/main/grpc/balancer + + +## Description + +This example demonstrates how to implement `gRPC` load balancing in `GoFrame` applications. It shows how to: +- Configure `gRPC` load balancers +- Implement different balancing strategies +- Build resilient distributed systems + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client implementation with load balancing +├── controller/ # Service controllers +│ └── hello.go # Hello service implementation +├── protobuf/ # Protocol buffer definitions +├── server/ # Server example +│ ├── config.yaml # Server configuration +│ └── server.go # Server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Load Balancing + - Round-robin balancing + - Weight-based balancing + - Least connection balancing + - Custom balancing strategies + +2. Service Management + - Service registration + - Health checking + - Failover handling + - Connection management + +3. Protocol Support + - `gRPC` services + - Protocol buffer messages + - Custom metadata handling + - Stream processing + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [gRPC](https://grpc.io/docs/languages/go/quickstart/) + +## Prerequisites + +1. Protocol buffer compiler installed: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +## Usage + +1. Generate protocol buffer code: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto + ``` + +2. Start multiple server instances: + ```bash + cd server + # Start first instance on a random free port + go run server.go + + # Start second instance on a random free port + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +# Implementation Details + +The example demonstrates: +1. Load balancer configuration with `gRPC` +2. Service registration process +3. Connection management +4. Load balancing strategies diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/basic/basic.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/basic/basic.md new file mode 100644 index 00000000000..168703c7575 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/basic/basic.md @@ -0,0 +1,103 @@ +--- +title: Basic Usage +slug: /examples/grpc/basic +keywords: [grpc, basic, goframe] +description: basic gRPC usage in GoFrame +hide_title: true +sidebar_position: 0 +--- + +# `gRPC` - Basic Usage + +Github Source: https://github.com/gogf/examples/tree/main/grpc/basic + + +## Description + +This example demonstrates the basic usage of `gRPC` in `GoFrame` applications. It shows how to: +- Create `gRPC` servers and clients +- Define and use protocol buffers +- Implement service handlers +- Make `RPC` calls + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client implementation +├── controller/ # Service controllers +│ └── hello.go # Hello service implementation +├── protobuf/ # Protocol buffer definitions +├── server/ # Server example +│ ├── config.yaml # Server configuration +│ └── server.go # Server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Implementation + - Protocol buffer definitions + - Service handlers + - Request/response handling + - Error management + +2. Client Usage + - Connection management + - `RPC` calls + - Error handling + - Context usage + +3. Protocol Support + - `gRPC` services + - Protocol buffer messages + - Custom metadata + - Stream processing + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [gRPC](https://grpc.io/docs/languages/go/quickstart/) + +## Prerequisites + +1. Protocol buffer compiler installed: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +## Usage + +1. Generate protocol buffer code: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto + ``` + +2. Start the server: + ```bash + cd server + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. Service definition using `gRPC` and protocol buffers +2. Client/server implementation with `GoFrame` integration diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/ctx/ctx.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/ctx/ctx.md new file mode 100644 index 00000000000..2155cc76789 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/ctx/ctx.md @@ -0,0 +1,103 @@ +--- +title: Context Usage +slug: /examples/grpc/ctx +keywords: [grpc, context, metadata, goframe] +description: gRPC context usage in GoFrame +hide_title: true +sidebar_position: 1 +--- + +# `gRPC` - Context Usage + +Github Source: https://github.com/gogf/examples/tree/main/grpc/ctx + + +## Description + +This example demonstrates how to use context and metadata in `gRPC` with `GoFrame`. It shows how to: +- Pass metadata through `gRPC` context +- Handle context values and deadlines +- Manage request context +- Process context metadata + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client implementation with context +├── controller/ # Service controllers +│ └── helloworld.go # Hello service with context handling +├── protobuf/ # Protocol buffer definitions +├── server/ # Server example +│ ├── config.yaml # Server configuration +│ └── server.go # Server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Context Management + - Metadata handling + - Value propagation + - Deadline management + - Cancellation handling + +2. Client Usage + - Context creation + - Metadata attachment + - Context values + - Timeout configuration + +3. Server Features + - Context extraction + - Metadata processing + - Value retrieval + - Timeout handling + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [gRPC](https://grpc.io/docs/languages/go/quickstart/) + +## Prerequisites + +1. Protocol buffer compiler installed: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +# Usage + +1. Generate protocol buffer code: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto + ``` + +2. Start the server: + ```bash + cd server + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +# Implementation Details + +The example demonstrates: +1. Context propagation in `gRPC` +2. Metadata handling with `GoFrame` diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/grpc.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/grpc.md new file mode 100644 index 00000000000..f76fa32fa42 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/grpc.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/grpc' +title: 'gRPC' +hide_title: true +sidebar_position: 0 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md new file mode 100644 index 00000000000..c477148481f --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md @@ -0,0 +1,101 @@ +--- +title: Raw gRPC Example +slug: /examples/grpc/rawgrpc +keywords: [grpc, raw, implementation, goframe] +description: raw gRPC implementation in GoFrame +hide_title: true +sidebar_position: 9 +--- + +# `gRPC` - Raw Implementation + +Github Source: https://github.com/gogf/examples/tree/main/grpc/rawgrpc + + +## Description + +This example demonstrates how to implement raw `gRPC` services in `GoFrame` without additional abstractions. It shows how to: +- Create raw `gRPC` servers and clients +- Use protocol buffers directly +- Implement service handlers +- Make direct `RPC` calls + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Raw gRPC client implementation +├── helloworld/ # Protocol buffer definitions +│ └── helloworld.proto # Service and message definitions +├── server/ # Server example +│ └── server.go # Raw gRPC server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Raw `gRPC` Implementation + - Direct protocol buffer usage + - Manual service implementation + - Basic `RPC` communication + - Error handling + +2. Client Usage + - Direct connection management + - Raw `RPC` calls + - Basic error handling + - Connection configuration + +3. Server Features + - Raw service implementation + - Request processing + - Response handling + - Basic configuration + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [gRPC](https://grpc.io/docs/languages/go/quickstart/) + +## Prerequisites + +1. Protocol buffer compiler installed: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +# Usage + +1. Generate protocol buffer code: + ```bash + cd helloworld + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto + ``` + +2. Start the server: + ```bash + cd server + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +# Implementation Details + +The example demonstrates: +1. Raw `gRPC` service implementation +2. Direct client/server usage diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/resolver/resolver.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/resolver/resolver.md new file mode 100644 index 00000000000..1052f6840f3 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/grpc/resolver/resolver.md @@ -0,0 +1,108 @@ +--- +title: Service Resolver +slug: /examples/grpc/resolver +keywords: [grpc, resolver, etcd, goframe] +description: gRPC service resolver with etcd in GoFrame +hide_title: true +--- + +# `gRPC` - Service Resolver + +Github Source: https://github.com/gogf/examples/tree/main/grpc/resolver + + +## Description + +This example demonstrates how to use service resolver with `etcd` in `gRPC` services using `GoFrame`. It shows how to: +- Configure service resolver with `etcd` +- Register services to `etcd` +- Discover services using resolver +- Handle service updates + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client with service resolver +├── controller/ # Service controllers +│ └── helloworld.go # Hello service implementation +├── protobuf/ # Protocol buffer definitions +│ └── helloworld.proto # Service and message definitions +├── server/ # Server example +│ └── server.go # Server with service registration +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Resolution + - `etcd` integration + - Service registration + - Service discovery + - Update handling + +2. Client Usage + - Resolver configuration + - Service discovery + - Connection management + - Error handling + +3. Server Features + - Service registration + - `etcd` integration + - Health checking + - Metadata management + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [gRPC](https://grpc.io/docs/languages/go/quickstart/) + +## Prerequisites + +1. Running `etcd` instance: + ```bash + # Using Docker + docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.4.24 + ``` + +2. Protocol buffer compiler: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +## Usage + +1. Generate protocol buffer code: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. *.proto + ``` + +2. Start the server: + ```bash + cd server + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. Service registration with `etcd` +2. Service discovery using resolver diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/httpserver.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/httpserver.md new file mode 100644 index 00000000000..22a162a597d --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/httpserver.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/httpserver' +title: 'HTTP Server' +hide_title: true +sidebar_position: 2 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/jwt/jwt.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/jwt/jwt.md new file mode 100644 index 00000000000..68a283bd584 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/jwt/jwt.md @@ -0,0 +1,89 @@ +--- +title: JWT Authentication +slug: /examples/httpserver/jwt +keywords: [http, server, jwt, authentication, goframe] +description: JWT authentication example using GoFrame framework +hide_title: true +sidebar_position: 0 +--- + +# JWT Authentication Example with GoFrame + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/jwt + + +This example demonstrates how to implement JWT (JSON Web Token) authentication in a GoFrame HTTP server using the `github.com/golang-jwt/jwt` package. + +## Features + +- User login endpoint that generates JWT tokens +- Protected routes using JWT middleware +- Token validation and parsing +- Example of accessing protected resources +- Standard GoFrame project structure + +## Project Structure + +``` +jwt/ +├── api/ +│ └── v1/ +│ └── auth.go # API interface definitions +├── internal/ +│ ├── controller/ +│ │ └── auth.go # Business logic implementation +│ └── middleware/ +│ └── jwt.go # JWT middleware +└── main.go # Entry point +``` + +## API Endpoints + +1. Login: `POST /login` + ```json + { + "username": "admin", + "password": "password" + } + ``` + +2. Protected Resource: `GET /api/protected` + - Requires Bearer token in Authorization header + - Example: `Authorization: Bearer your-token-here` + +## Running the Example + +1. Start the server: + ```bash + go run main.go + ``` + +2. The server will start on port 8199 + +## Testing the API + +1. Login to get a token: + ```bash + curl -X POST http://localhost:8199/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"password"}' + ``` + +2. Access protected endpoint: + ```bash + curl http://localhost:8199/api/protected \ + -H "Authorization: Bearer your-token-here" + ``` + +## Security Notes + +- In production, replace the hardcoded secret key with a secure value +- Store user credentials in a database +- Implement proper password hashing +- Consider implementing refresh tokens +- Add rate limiting for login attempts + +## References + +For more detailed information about JWT implementation, please refer to the third-party component documentation: +- [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/proxy/proxy.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/proxy/proxy.md new file mode 100644 index 00000000000..b8bd4667858 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/proxy/proxy.md @@ -0,0 +1,95 @@ +--- +title: Proxy +slug: /examples/httpserver/proxy +keywords: [http, server, proxy, reverse, goframe] +description: A reverse proxy server using GoFrame framework +hide_title: true +--- + +# HTTP Server Proxy + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/proxy + + + +## Description + +This example demonstrates how to create a reverse proxy server using `GoFrame`. The example consists of two servers: + +1. A backend server running on port 8198 that provides the actual service +2. A proxy server running on port 8199 that forwards requests to the backend server + +The proxy server implements the following features: +- Reverse proxy functionality using `httputil.NewSingleHostReverseProxy` +- Custom error handling for proxy failures +- URL path rewriting +- Request body handling +- Detailed logging of proxy operations + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Structure + +- `go.mod`: The Go module file. +- `main.go`: The main application entry point. + +## Features + +- Simple and efficient reverse proxy implementation +- Automatic URL path rewriting +- Error handling for backend server failures +- Logging of proxy operations +- Support for all HTTP methods + +## Setup + +1. Clone the repository: + ```bash + git clone https://github.com/gogf/examples.git + cd examples/httpserver/proxy + ``` + +2. Install the dependencies: + ```bash + go mod tidy + ``` + +3. Run the application: + ```bash + go run main.go + ``` + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. The example will start two servers: + - Backend server at: http://127.0.0.1:8198 + - Proxy server at: http://127.0.0.1:8199 + +3. Test the proxy: + - Access through proxy: http://127.0.0.1:8199/proxy/user/1 + - Direct backend access: http://127.0.0.1:8198/user/1 + +## Implementation Details + +The proxy server uses Go's built-in `httputil.NewSingleHostReverseProxy` for implementing the reverse proxy functionality. All requests to the proxy server with the path prefix `/proxy/*` are forwarded to the backend server after removing the `/proxy` prefix. + +The implementation includes: +- Request path rewriting +- Error handling for backend failures +- Request body handling for proper forwarding +- Logging of proxy operations for debugging + +## Notes + +- The proxy server modifies the request URL path before forwarding +- All HTTP methods are supported +- Backend server errors result in a 502 Bad Gateway response diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md new file mode 100644 index 00000000000..5edb8c4d9fe --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md @@ -0,0 +1,80 @@ +--- +title: Rate Limit +slug: /examples/httpserver/rate-limit +keywords: [http, server, rate limit, middleware, goframe] +description: Rate limiting in a HTTP server using GoFrame framework +hide_title: true +--- + +# HTTP Server Rate Limit + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/rate-limit + + + +## Description + +This example demonstrates how to implement rate limiting in a HTTP server using `GoFrame`. It showcases how to protect your API endpoints from being overwhelmed by too many requests using the token bucket algorithm implemented by `golang.org/x/time/rate` package. + +The example implements: +- A simple HTTP endpoint `/hello` that returns a greeting message +- A rate limiting middleware that restricts requests to 10 per second +- Proper error handling when rate limit is exceeded (HTTP 429 Too Many Requests) + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Structure + +- `go.mod`: The Go module file. +- `main.go`: The main application entry point. + +## Features + +- Token bucket based rate limiting +- Configurable request rate and burst size +- Global middleware implementation +- Clean API endpoint implementation using GoFrame's binding feature +- Request validation and documentation using metadata +- Proper error handling and status codes + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. The server will start at http://127.0.0.1:8080 + +3. Test the rate limiting: + ```bash + # Normal request + curl "http://127.0.0.1:8080/hello?name=world" + + # To test rate limiting, send multiple requests quickly: + for i in {1..20}; do curl "http://127.0.0.1:8080/hello?name=world"; done + ``` + +## Implementation Details + +The rate limiting is implemented using: +1. `golang.org/x/time/rate.Limiter` for token bucket algorithm +2. GoFrame's middleware system for request interception +3. Clean request/response structs with validation and documentation + +Key components: +- Rate limit is set to 10 requests per second +- Burst size is set to 1 (no bursting allowed) +- Requests exceeding the limit receive HTTP 429 status code +- Request validation ensures required parameters are provided + +## Notes + +- The rate limit is global (applies to all clients) +- No persistence of rate limit state (resets on server restart) +- Rate limit can be configured by modifying the limiter parameters +- Consider using distributed rate limiting for production environments diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md new file mode 100644 index 00000000000..343e11f378f --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md @@ -0,0 +1,83 @@ +--- +title: Response with JSON Array +slug: /examples/httpserver/response-json-array +keywords: [http, server, json, array, goframe] +description: Handle JSON array responses in a HTTP server using GoFrame framework +hide_title: true +--- + +# HTTP Server Response with JSON Array + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/response-json-array + + + +## Description + +This example demonstrates how to implement a HTTP server that returns JSON array responses using `GoFrame`. It showcases how to: +- Structure your API response as a JSON array +- Configure OpenAPI/Swagger documentation +- Use GoFrame's middleware for consistent response handling +- Define type-safe request and response structures + +The example implements a `/user` endpoint that returns a list of users in JSON array format. + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Structure + +- `go.mod`: The Go module file. +- `main.go`: The main application entry point. + +## Features + +- JSON array response handling +- OpenAPI/Swagger integration +- Middleware-based response processing +- Type-safe request/response structures +- Clean API documentation +- Automatic response wrapping + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. The server will start at http://127.0.0.1:8199 + +3. Access the endpoints: + ```bash + # Get user list + curl "http://127.0.0.1:8199/user" + + # View API documentation + # Open in browser: http://127.0.0.1:8199/swagger + ``` + +## Implementation Details + +The example demonstrates several key concepts: +1. Using slice types for JSON array responses +2. Configuring OpenAPI/Swagger documentation +3. Implementing middleware for consistent response handling +4. Structuring type-safe API endpoints + +Key components: +- Response wrapper middleware for consistent JSON structure +- OpenAPI configuration for proper documentation +- Type definitions for request and response objects +- Swagger UI integration for API testing + +## Notes + +- Response format includes a wrapper with standard fields +- OpenAPI documentation is available at `/api` +- Swagger UI is available at `/swagger` +- Response data is automatically wrapped by the middleware +- All responses are properly documented in OpenAPI format diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md new file mode 100644 index 00000000000..cf71c84682a --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md @@ -0,0 +1,102 @@ +--- +title: File Upload Example +slug: /examples/httpserver/upload-file +keywords: [http, server, file, upload, goframe] +description: Handle file uploads in a HTTP server using GoFrame framework +hide_title: true +--- + +# HTTP Server File Upload + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/upload-file + + + +## Description + +This example demonstrates how to implement file upload functionality in a HTTP server using `GoFrame`. It showcases: +- A modern and user-friendly file upload interface +- Server-side file upload handling +- Progress tracking for file uploads +- Proper error handling and validation +- Maximum file size configuration + +The example provides both a REST API endpoint and a web interface for file uploads. + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Structure + +- `go.mod`: The Go module file for dependency management. +- `go.sum`: The Go module checksum file. +- `main.go`: The main application entry point that implements the file upload server. +- `static/`: Directory containing static web files + - `index.html`: A modern web interface for file uploads with progress tracking. + +The project is organized as follows: +``` +upload-file/ +├── go.mod # Go module definition +├── go.sum # Go module checksums +├── main.go # Server implementation +└── static/ # Static web assets + └── index.html # Upload interface +``` + +## Features + +- Modern web interface for file uploads +- Progress bar for upload tracking +- Support for large file uploads (up to 600MB) +- File validation and error handling +- Optional message attachment with uploads +- Access log enabled for debugging +- Clean API documentation + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. The server will start at http://127.0.0.1:8199 + +3. Access the upload interface: + - Web Interface: http://127.0.0.1:8199/ + - API Endpoint: POST http://127.0.0.1:8199/upload + +4. Upload files using either: + - The web interface by selecting a file and clicking "Upload" + - Using curl: + ```bash + curl -X POST http://127.0.0.1:8199/upload \ + -F "file=@/path/to/your/file" \ + -F "msg=Optional message" + ``` + +## Implementation Details + +The example implements several key features: +1. A modern HTML/CSS/JS frontend for file uploads +2. Server-side file handling using GoFrame's features +3. Progress tracking for large file uploads +4. Proper error handling and validation + +Key components: +- Maximum file size limit of 600MB +- Access logging for debugging +- Clean separation of frontend and backend code +- Type-safe request/response structures + +## Notes + +- The maximum file size is set to 600MB +- Access logs are enabled for debugging +- The server supports multipart/form-data uploads +- Frontend provides visual feedback during uploads +- All uploads are validated server-side diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/websocket/websocket.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/websocket/websocket.md new file mode 100644 index 00000000000..293565fe52a --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/httpserver/websocket/websocket.md @@ -0,0 +1,147 @@ +--- +title: WebSocket +slug: /examples/httpserver/websocket +keywords: [websocket, server, client, goframe, https] +description: A WebSocket server and client implementation using GoFrame framework +hide_title: true +--- + +# `WebSocket` Server and Client + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/websocket + + +## Description + +This example demonstrates how to implement `WebSocket` communication using `GoFrame`. The example includes both `HTTP` and `HTTPS` implementations with the following components: + +1. `WebSocket` server that handles both secure and non-secure connections +2. `WebSocket` client implementations for both `HTTP` and `HTTPS` connections +3. Example of handling `WebSocket` message exchange + +The implementation demonstrates: +- Basic `WebSocket` server setup +- Secure `WebSocket` (`WSS`) implementation +- Client connection handling +- Message exchange between client and server +- Proper connection cleanup + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Gorilla WebSocket](https://github.com/gorilla/websocket) + +## Structure + +```text +websocket/ +├── http/ +│ ├── client.go # HTTP WebSocket client +│ ├── server.go # HTTP WebSocket server +│ └── static/ # Static files directory +├── https/ +│ ├── client.go # HTTPS WebSocket client +│ ├── server.go # HTTPS WebSocket server +│ ├── static/ # Static files directory +│ └── certs/ # SSL certificates +└── README.md +``` + +## Features + +- `WebSocket` server implementation +- Secure `WebSocket` (`WSS`) support +- Client connection handling +- Message echo functionality +- Connection lifecycle management +- Origin checking (configurable) +- Error handling + +## Setup + +1. Clone the repository: + ```bash + git clone https://github.com/gogf/examples.git + cd examples/httpserver/websocket + ``` + +2. Install the dependencies: + ```bash + go mod tidy + ``` + +3. For `HTTPS/WSS` support, generate self-signed certificates (optional): + ```bash + mkdir -p https/certs + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout https/certs/server.key -out https/certs/server.crt + ``` + +## Usage + +### HTTP WebSocket + +1. Start the server: + ```bash + cd http + go run server.go + ``` + +2. Run the client: + ```bash + cd http + go run client.go + ``` + +3. Access the demo page in browser: + + Open `http://127.0.0.1:8000` in your browser to see the `WebSocket` demo in action. + The page provides a simple chat interface where you can send and receive messages in real-time. + +### HTTPS WebSocket + +1. Start the secure server: + ```bash + cd https + go run server.go + ``` + +2. Run the secure client: + ```bash + cd https + go run client.go + ``` + +3. Access the demo page in browser: + Open `https://127.0.0.1:8000` in your browser to see the `WebSocket` demo in action. + Note: Since we're using a self-signed certificate, your browser may show a security warning, which is normal. + +## Implementation Details + +### Server Features +- `WebSocket` upgrade handling +- Message echo functionality +- Connection lifecycle management +- Configurable origin checking +- Error handling and logging + +### Client Features +- Connection establishment +- Message sending and receiving +- `TLS` configuration for secure connections +- Clean connection closure + +## Notes + +- The `WebSocket` server echoes back any message it receives +- For `HTTPS`/`WSS`, self-signed certificates are used in the example +- In production, implement proper origin checking +- Handle connection closure properly to avoid resource leaks + +## More Information + +For more details about `WebSocket` implementation, please refer to: +- [GoFrame WebSocket Guide](https://goframe.org/docs/web/senior-websocket) +- [Gorilla WebSocket Documentation](https://github.com/gorilla/websocket) + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/mongodb/mongodb.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/mongodb/mongodb.md new file mode 100644 index 00000000000..5d2164fbade --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/mongodb/mongodb.md @@ -0,0 +1,104 @@ +--- +title: MongoDB +slug: /examples/nosql/mongodb +keywords: [nosql, mongodb, database, goframe] +description: An example demonstrating MongoDB integration in GoFrame +hide_title: true +sidebar_position: 2 +--- + +# GoFrame MongoDB Example + +Github Source: https://github.com/gogf/examples/tree/main/nosql/mongodb + + +This example demonstrates how to use `MongoDB` with `GoFrame` framework. + +## Overview + +This example shows: +1. How to configure `MongoDB` connection using `YAML` configuration +2. How to create a `MongoDB` client +3. Basic `MongoDB` operations (`INSERT`, `FIND`, `UPDATE`) +4. How to use `BSON` for document operations + +## Requirements + +- `Go 1.15` or higher +- `MongoDB` server +- `GoFrame v2` + +## Configuration + +The `MongoDB` configuration is stored in `config.yaml`: + +```yaml +mongo: + database: "user" + address: "mongodb://127.0.0.1:27017/test?retryWrites=true" +``` + +You can modify these settings according to your `MongoDB` server configuration. + +## Running MongoDB with Docker + +If you don't have `MongoDB` installed locally, you can quickly start a `MongoDB` instance using `Docker`: + +```bash +# Run MongoDB container +docker run --name mongo-test -p 27017:27017 -d mongo:latest + +# Verify the container is running +docker ps + +# If you need to stop the container later +docker stop mongo-test + +# If you need to remove the container +docker rm mongo-test +``` + +For `MongoDB` with authentication: + +```bash +# Run MongoDB with authentication +docker run --name mongo-test -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=password -d mongo:latest + +# Remember to update config.yaml accordingly: +# mongo: +# database: "user" +# address: "mongodb://admin:password@127.0.0.1:27017/test?retryWrites=true" +``` + +## Running the Example + +1. Make sure your `MongoDB` server is running +2. Update the `config.yaml` if needed +3. Run the example: + +```bash +go run main.go +``` + +## Code Structure + +- `main.go`: Contains the main logic and `MongoDB` client initialization +- `config.yaml`: `MongoDB` configuration file + +## Features + +1. `MongoDB` client initialization with error handling +2. Configuration management using `GoFrame`'s configuration system +3. Basic `MongoDB` operations demonstration +4. Proper error handling and logging +5. Use of `BSON` tags for document mapping + +## Further Reading + +For more advanced `MongoDB` usage, please refer to the official `MongoDB` Go driver [github.com/mongodb/mongo-go-driver](https://github.com/mongodb/mongo-go-driver). + +## Notes + +- The example uses the official `MongoDB` Go driver +- All operations are performed with context for proper cancellation and timeout handling +- The code includes proper error handling and logging diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/nosql.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/nosql.md new file mode 100644 index 00000000000..5a5d644e22d --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/nosql.md @@ -0,0 +1,18 @@ +--- +title: NoSQL +slug: /examples/nosql +keywords: [nosql, mongodb, redis, database, goframe] +description: NoSQL database integration examples in GoFrame +hide_title: true +sidebar_position: 0 +--- + +# NoSQL Database Examples + +Github Source: https://github.com/gogf/examples/tree/main/nosql + + + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/redis/redis.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/redis/redis.md new file mode 100644 index 00000000000..40d9bea5610 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/nosql/redis/redis.md @@ -0,0 +1,103 @@ +--- +title: Redis +slug: /examples/nosql/redis +keywords: [nosql, redis, cache, database, goframe] +description: An example demonstrating Redis integration in GoFrame +hide_title: true +sidebar_position: 1 +--- + + +# GoFrame Redis Example + +Github Source: https://github.com/gogf/examples/tree/main/nosql/redis + + +This example demonstrates how to use `Redis` with `GoFrame` framework. + +## Overview + +This example shows: +1. How to configure `Redis` connection using `YAML` configuration +2. How to create a `Redis` client +3. Basic `Redis` operations (`SET`/`GET`) + +## Requirements + +- `Go 1.15` or higher +- `Redis` server +- `GoFrame v2` + +## Configuration + +The `Redis` configuration is stored in `config.yaml`: + +```yaml +redis: + address: "127.0.0.1:6379" + password: +``` + +You can modify these settings according to your `Redis` server configuration. + +## Running Redis with Docker + +If you don't have `Redis` installed locally, you can quickly start a `Redis` instance using `Docker`: + +```bash +# Run Redis container +docker run --name redis-test -p 6379:6379 -d redis:latest + +# Verify the container is running +docker ps + +# If you need to stop the container later +docker stop redis-test + +# If you need to remove the container +docker rm redis-test +``` + +For `Redis` with password authentication: + +```bash +# Run Redis with password +docker run --name redis-test -p 6379:6379 -d redis:latest redis-server --requirepass your_password + +# Remember to update config.yaml accordingly: +# redis: +# address: "127.0.0.1:6379" +# password: "your_password" +``` + +## Running the Example + +1. Make sure your `Redis` server is running +2. Update the `config.yaml` if needed +3. Run the example: + +```bash +go run main.go +``` + +## Code Structure + +- `main.go`: Contains the main logic and `Redis` client initialization +- `config.yaml`: `Redis` configuration file + +## Features + +1. `Redis` client initialization with error handling +2. Configuration management using `GoFrame`'s configuration system +3. Basic `Redis` operations demonstration +4. Proper error handling and logging + +## Further Reading + +For more advanced `Redis` usage, please refer to the third-party package [github.com/redis/go-redis](https://github.com/redis/go-redis). + +## Notes + +- The example uses `go-redis/v9` client +- All operations are performed with context for proper cancellation and timeout handling +- The code includes proper error handling and logging diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/basic/basic.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/basic/basic.md new file mode 100644 index 00000000000..f861d795322 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/basic/basic.md @@ -0,0 +1,104 @@ +--- +title: Basic +slug: /examples/observability/metric/basic +keywords: [metrics, basic, prometheus, opentelemetry, goframe] +description: A basic example demonstrating various metric types and their usage in GoFrame +hide_title: true +--- + +# Metric - Basic Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/basic + + +## Description + +This example demonstrates the basic usage of various metric types in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Create and use different types of metrics +- Configure metric attributes +- Export metrics in `Prometheus` format +- Set up a metrics endpoint + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating metric usage +``` + +## Features + +The example showcases the following metric types: +1. Counter + - Cumulative measurements + - Only increases + - Used for counting events + +2. UpDownCounter + - Bidirectional counter + - Can increase and decrease + - Used for measuring varying quantities + +3. Histogram + - Distribution of measurements + - Configurable buckets + - Used for latency/size measurements + +4. Observable Metrics + - Counter + - UpDownCounter + - Gauge + - Updated via callbacks + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_metric_demo_counter This is a simple demo for Counter usage + goframe_metric_demo_counter{const_attr_1="1"} 11 + + # HELP goframe_metric_demo_histogram This is a simple demo for histogram usage + goframe_metric_demo_histogram_bucket{const_attr_3="3",le="0"} 0 + goframe_metric_demo_histogram_bucket{const_attr_3="3",le="10"} 1 + ... + ``` + +## Implementation Details + +The example demonstrates: +1. Proper metric initialization and configuration +2. Different ways to update metrics +3. Attribute handling +4. `Prometheus` integration +5. Observable metric callbacks + +## Notes + +- Metrics are exported in `Prometheus` format +- Default port is 8000 +- Metrics endpoint is at /metrics +- All metrics include constant attributes +- Observable metrics are updated via callbacks +- Proper cleanup is handled via defer diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/callback/callback.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/callback/callback.md new file mode 100644 index 00000000000..cc7a5fdf59a --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/callback/callback.md @@ -0,0 +1,97 @@ +--- +title: Callback +slug: /examples/observability/metric/callback +keywords: [metrics, callback, prometheus, opentelemetry, goframe] +description: callback-based metric collection in GoFrame +hide_title: true +--- + +# Metric - Callback Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/callback + + +## Description + +This example demonstrates how to implement callback-based metric collection in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Create metrics with callback functions +- Automatically update metric values through callbacks +- Configure metric attributes +- Export metrics in `Prometheus` format + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating callback-based metrics +``` + +## Features + +The example showcases the following features: +1. Callback-based Metrics + - Automatic value updates + - Custom callback functions + - No manual update needed + +2. Regular Metrics + - Manual value updates + - Direct control over values + - Comparison with callback approach + +3. Metric Configuration + - Custom attributes + - Help text + - Units + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_metric_demo_counter This is a simple demo for Counter usage + goframe_metric_demo_counter{const_attr_1="1"} 11 + + # HELP goframe_metric_demo_observable_counter This is a simple demo for ObservableCounter usage + goframe_metric_demo_observable_counter{const_attr_3="3"} 10 + ``` + +## Implementation Details + +The example demonstrates: +1. Creating metrics with callback functions +2. Automatic value updates through callbacks +3. Comparison between callback and manual updates +4. Proper metric configuration +5. `Prometheus` integration + +## Notes + +- Callbacks are executed automatically by the metrics system +- No need for manual updates of callback-based metrics +- Callbacks should be lightweight and fast +- Consider thread safety in callbacks +- Proper cleanup is handled via defer +- Default port is 8000 +- Metrics endpoint is at /metrics diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md new file mode 100644 index 00000000000..680ff57964e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md @@ -0,0 +1,97 @@ +--- +title: Dynamic Attributes +slug: /examples/observability/metric/dynamic_attributes +keywords: [metrics, dynamic attributes, prometheus, opentelemetry, goframe] +description: dynamic metric attributes in GoFrame +hide_title: true +--- + +# Metric - Dynamic Attributes Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/dynamic_attributes + + +## Description + +This example demonstrates how to work with dynamic metric attributes in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Add dynamic attributes to metrics at runtime +- Combine constant and dynamic attributes +- Use attributes in both regular and observable metrics +- Export metrics with dynamic attributes + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating dynamic metric attributes +``` + +## Features + +The example showcases the following features: +1. Dynamic Attributes + - Runtime attribute assignment + - Attribute combination + - Value-based attributes + +2. Metric Types with Attributes + - Counter with dynamic attributes + - Observable Counter with dynamic attributes + - Constant attribute baseline + +3. Attribute Management + - Attribute creation + - Value typing + - Attribute scoping + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_metric_demo_counter This is a simple demo for Counter usage + goframe_metric_demo_counter{const_attr_1="1",dynamic_attr_2="2"} 11 + + # HELP goframe_metric_demo_observable_counter This is a simple demo for ObservableCounter usage + goframe_metric_demo_observable_counter{const_attr_4="4",dynamic_attr_1="1"} 10 + ``` + +## Implementation Details + +The example demonstrates: +1. Creating metrics with constant attributes +2. Adding dynamic attributes at runtime +3. Combining different attribute types +4. Attribute scoping and inheritance +5. Proper attribute value typing + +## Notes + +- Dynamic attributes are added per observation +- Constant attributes are always present +- Attributes can be of various types +- Consider cardinality when using dynamic attributes +- High cardinality can impact performance +- Default port is 8000 +- Metrics endpoint is at /metrics diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md new file mode 100644 index 00000000000..293412aefeb --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md @@ -0,0 +1,97 @@ +--- +title: Global Attributes +slug: /examples/observability/metric/global_attributes +keywords: [metrics, global attributes, prometheus, opentelemetry, goframe] +description: global metric attributes in GoFrame +hide_title: true +--- + +# Metric - Global Attributes Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/global_attributes + + +## Description + +This example demonstrates how to work with global metric attributes in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Set and manage global attributes +- Apply global attributes across multiple metrics +- Configure attribute scope and patterns +- Combine global attributes with local ones + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating global metric attributes +``` + +## Features + +The example showcases the following features: +1. Global Attributes + - Global attribute configuration + - Attribute inheritance + - Pattern-based application + +2. Metric Types with Global Attributes + - Counter with global attributes + - Observable Counter with global attributes + - Local attribute combination + +3. Attribute Management + - Global attribute scoping + - Version-based filtering + - Pattern-based filtering + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_metric_demo_counter This is a simple demo for Counter usage + goframe_metric_demo_counter{const_attr_1="1",global_attr_1="1"} 11 + + # HELP goframe_metric_demo_observable_counter This is a simple demo for ObservableCounter usage + goframe_metric_demo_observable_counter{const_attr_2="2",global_attr_1="1"} 10 + ``` + +## Implementation Details + +The example demonstrates: +1. Setting up global attributes +2. Configuring attribute patterns +3. Version-based attribute filtering +4. Attribute inheritance and precedence +5. Global attribute scoping + +## Notes + +- Global attributes apply to all matching metrics +- Local attributes take precedence over global ones +- Pattern matching controls attribute application +- Version filtering enables targeted attribute sets +- Consider attribute cardinality impact +- Default port is 8000 +- Metrics endpoint is at /metrics diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_client/http_client.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_client/http_client.md new file mode 100644 index 00000000000..94b677af088 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_client/http_client.md @@ -0,0 +1,103 @@ +--- +title: HTTP Client +slug: /examples/observability/metric/http_client +keywords: [metrics, http client, prometheus, opentelemetry, goframe] +description: HTTP client metrics collection in GoFrame +hide_title: true +--- + +# Metric - HTTP Client Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/http_client + + +## Description + +This example demonstrates how to collect and monitor HTTP client metrics in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Monitor HTTP client requests +- Track request durations +- Collect response status codes +- Export client-side metrics + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating HTTP client metrics +``` + +## Features + +The example showcases the following metrics: +1. Request Metrics + - Total requests count + - Active requests + - Request duration + - Request size + +2. Response Metrics + - Response status codes + - Response size + - Error count + - Response duration + +3. Connection Metrics + - Connection pool stats + - DNS lookup duration + - TLS handshake duration + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_http_client_request_duration_seconds Duration of HTTP requests + goframe_http_client_request_duration_seconds_bucket{method="GET",status="200",url="https://goframe.org",le="0.1"} 1 + + # HELP goframe_http_client_requests_total Total number of HTTP requests made + goframe_http_client_requests_total{method="GET",status="200",url="https://goframe.org"} 1 + + # HELP goframe_http_client_response_size_bytes Size of HTTP response payloads + goframe_http_client_response_size_bytes{method="GET",status="200",url="https://goframe.org"} 12345 + ``` + +## Implementation Details + +The example demonstrates: +1. HTTP client configuration +2. Automatic metric collection +3. Built-in metrics integration +4. Prometheus export setup +5. Metric attribute handling + +## Notes + +- Metrics are collected automatically +- No manual instrumentation needed +- Built-in metrics are enabled by default +- Supports all HTTP methods +- Tracks all response codes +- Default port is 8000 +- Metrics endpoint is at /metrics +- Consider security implications when exposing metrics diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_server/http_server.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_server/http_server.md new file mode 100644 index 00000000000..ad6c1353b7e --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/http_server/http_server.md @@ -0,0 +1,123 @@ +--- +title: HTTP Server +slug: /examples/observability/metric/http_server +keywords: [metrics, http server, prometheus, opentelemetry, goframe] +description: HTTP server metrics collection in GoFrame +hide_title: true +--- + +# Metric - HTTP Server Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/http_server + + +## Description + +This example demonstrates how to collect and monitor HTTP server metrics in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Monitor HTTP server requests +- Track request latencies +- Collect error rates +- Export server-side metrics + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating HTTP server metrics +``` + +## Features + +The example showcases the following endpoints and metrics: +1. Endpoints + - `/`: Basic endpoint returning "ok" + - `/error`: Endpoint that triggers an error + - `/sleep`: Endpoint with 5-second delay + - `/metrics`: Prometheus metrics endpoint + +2. Request Metrics + - Total requests count + - Active requests + - Request duration + - Request size + +3. Response Metrics + - Response status codes + - Response size + - Error count + - Response latency + +4. Server Metrics + - Goroutine count + - Memory usage + - GC statistics + - Connection stats + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Test different endpoints: + ```bash + # Basic request + curl http://localhost:8000/ + + # Error request + curl http://localhost:8000/error + + # Slow request + curl http://localhost:8000/sleep + ``` + +3. View metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +4. Example metrics output: + ```text + # HELP goframe_http_server_requests_total Total number of HTTP requests made + goframe_http_server_requests_total{method="GET",path="/",status="200"} 1 + + # HELP goframe_http_server_request_duration_seconds Duration of HTTP requests + goframe_http_server_request_duration_seconds_bucket{method="GET",path="/sleep",status="200",le="5.0"} 1 + + # HELP goframe_http_server_panics_total Total number of HTTP requests that caused a panic + goframe_http_server_panics_total{method="GET",path="/error"} 1 + ``` + +## Implementation Details + +The example demonstrates: +1. HTTP server setup with metrics +2. Automatic metric collection +3. Built-in metrics integration +4. Error handling and tracking +5. Latency monitoring + +## Notes + +- Metrics are collected automatically +- No manual instrumentation needed +- Built-in metrics are enabled by default +- Supports all HTTP methods +- Tracks all response codes +- Default port is 8000 +- Metrics endpoint is at /metrics +- Consider security when exposing metrics +- High cardinality paths are normalized diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md new file mode 100644 index 00000000000..d4a88fb9301 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md @@ -0,0 +1,98 @@ +--- +title: Meter Attributes +slug: /examples/observability/metric/meter_attributes +keywords: [metrics, meter attributes, prometheus, opentelemetry, goframe] +description: meter-level attributes in GoFrame metrics +hide_title: true +--- + +# Metric - Meter Attributes Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/meter_attributes + + +## Description + +This example demonstrates how to work with meter-level attributes in `GoFrame` using `OpenTelemetry` and `Prometheus` integration. It shows how to: +- Configure meter-level attributes +- Apply attributes across all metrics +- Combine meter and metric attributes +- Manage attribute inheritance + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `main.go`: The main application demonstrating meter attributes +``` + +## Features + +The example showcases the following features: +1. Meter Attributes + - Global meter configuration + - Meter-level attributes + - Attribute inheritance + +2. Metric Types with Inherited Attributes + - Counter with combined attributes + - Observable Counter with combined attributes + - Attribute precedence rules + +3. Attribute Management + - Meter attribute configuration + - Version-based attributes + - Attribute combination + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run main.go + ``` + +2. Access the metrics: + ```bash + # Using curl + curl http://localhost:8000/metrics + + # Or open in browser + http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP goframe_metric_demo_counter This is a simple demo for Counter usage + goframe_metric_demo_counter{const_attr_1="1",meter_label_1="1",meter_label_2="2"} 11 + + # HELP goframe_metric_demo_observable_counter This is a simple demo for ObservableCounter usage + goframe_metric_demo_observable_counter{const_attr_2="2",meter_label_1="1",meter_label_2="2"} 10 + ``` + +## Implementation Details + +The example demonstrates: +1. Setting up meter attributes +2. Attribute inheritance rules +3. Version-based configuration +4. Attribute combination logic +5. Proper attribute scoping + +## Notes + +- Meter attributes apply to all metrics +- Metric attributes take precedence +- Consider attribute cardinality +- Version filtering available +- Attributes are immutable +- Default port is 8000 +- Metrics endpoint is at /metrics +- Consider performance impact of many attributes diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/metric.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/metric.md new file mode 100644 index 00000000000..988fec57843 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/metric.md @@ -0,0 +1,9 @@ +--- +slug: '/examples/observability/metric' +title: 'Metric' +hide_title: true +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md new file mode 100644 index 00000000000..0969c2c4ce1 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md @@ -0,0 +1,102 @@ +--- +title: Prometheus Integration +slug: /examples/observability/metric/prometheus +keywords: [metrics, prometheus, direct integration, goframe] +description: direct Prometheus integration in GoFrame without OpenTelemetry +hide_title: true +--- + +# Metric - Direct Prometheus Integration Example + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/prometheus + + +## Description + +This example demonstrates how to directly integrate `Prometheus` metrics in `GoFrame` without using `OpenTelemetry`. It shows how to: +- Create Prometheus metrics directly +- Register metrics with Prometheus registry +- Expose metrics via HTTP endpoint +- Update metric values dynamically + +## Structure + +```text +- `go.mod`: The Go module file for dependency management +- `go.sum`: The Go module checksums file +- `prometheus.go`: The main application demonstrating direct Prometheus integration +``` + +## Features + +The example showcases the following features: +1. Metric Types + - Counter: Monotonically increasing value + - Gauge: Value that can go up and down + +2. Metric Operations + - Metric registration + - Value updates + - HTTP exposure + - Random value generation + +3. HTTP Endpoints + - `/`: Triggers metric updates + - `/metrics`: Exposes Prometheus metrics + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Metric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +## Usage + +1. Run the example: + ```bash + go run prometheus.go + ``` + +2. Test the application: + ```bash + # Generate some metric values + curl http://localhost:8000/ + + # View metrics + curl http://localhost:8000/metrics + ``` + +3. Example metrics output: + ```text + # HELP demo_counter A demo counter. + # TYPE demo_counter counter + demo_counter 1 + + # HELP demo_gauge A demo gauge. + # TYPE demo_gauge gauge + demo_gauge 42 + ``` + +## Implementation Details + +The example demonstrates: +1. Direct Prometheus metric creation +2. Manual metric registration +3. HTTP server configuration +4. Dynamic value updates +5. Metric exposure + +## Notes + +- Uses native Prometheus client +- No OpenTelemetry dependency +- Simpler but less feature-rich +- Manual metric management +- Good for basic use cases +- Default port is 8000 +- Metrics endpoint at /metrics +- Consider security when exposing metrics +- Random values for demonstration +- Counter only increases +- Gauge can increase or decrease diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/observability.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/observability.md new file mode 100644 index 00000000000..95004142648 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/observability.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/observability' +title: 'Observability' +hide_title: true +sidebar_position: 3 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md new file mode 100644 index 00000000000..adf2a64a523 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md @@ -0,0 +1,195 @@ +--- +title: gRPC with Database +slug: /examples/observability/trace/grpc-with-db +keywords: [trace, grpc, database, opentelemetry, goframe] +description: distributed tracing in gRPC services with database operations using GoFrame +hide_title: true +--- + +# Tracing - gRPC with Database + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/grpc-with-db + + +## Description + +This example demonstrates how to implement distributed tracing in a gRPC service that interacts with a database using `GoFrame`. It shows how to: +- Configure tracing in gRPC services +- Trace database operations +- Propagate trace context +- Visualize distributed traces + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame gRPCx](https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx) +- [GoFrame MySQL Driver](https://github.com/gogf/gf/tree/master/contrib/drivers/mysql) +- [GoFrame Etcd Registry](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) +- [GoFrame OpenTelemetry Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc) + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client with tracing +├── controller/ # Service controllers +├── protobuf/ # Protocol buffer definitions +│ └── user/ # User service proto files +├── server/ # Server example +│ ├── server.go # Server with tracing +│ └── config.yaml # Server configuration +├── sql.sql # Database schema +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Distributed Tracing + - Trace propagation + - Span management + - Trace visualization + +2. Database Operations + - Query tracing + - Transaction tracing + - Error tracking + +3. gRPC Integration + - Service tracing + - Context propagation + - Error handling + +## Prerequisites + +1. Running MySQL instance: + ```bash + docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + mysql:5.7 + ``` + +2. Initialize database schema: + ```bash + # Connect to MySQL container + docker exec -i mysql mysql -uroot -p12345678 test < sql.sql + ``` + +3. Running Redis instance: + ```bash + docker run -d --name redis \ + -p 6379:6379 \ + redis:6.0 + ``` + +4. Running Jaeger instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +5. Protocol buffer compiler: + ```bash + # For macOS + brew install protobuf + + # Install protoc-gen-go and protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +## Configuration + +The server configuration is defined in `server/config.yaml`: + +```yaml +grpc: + name: "demo" # Service name + logStdout: true # Enable stdout logging + errorLogEnabled: true # Enable error logging + accessLogEnabled: true # Enable access logging + errorStack: true # Enable error stack trace + +database: + logger: + level: "all" # Log all SQL operations + stdout: true # Print to stdout + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true # Enable debug mode + +redis: + default: + address: 127.0.0.1:6379 # Default Redis instance + db: 0 + cache: + address: 127.0.0.1:6379 # Cache Redis instance + db: 1 +``` + +## Usage + +1. Generate protocol buffer code: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. ./user/*.proto + ``` + +2. Start the server: + ```bash + cd server + go run server.go + ``` + +3. Run the client: + ```bash + cd client + go run client.go + ``` + +4. View traces: + Open http://localhost:16686 in your browser to view traces in `Jaeger` UI. + +## Implementation Details + +The example demonstrates: +1. Trace context propagation +2. Database operation tracing + +## Troubleshooting + +1. Database Connection Issues: + - Ensure `MySQL` is running: `docker ps | grep mysql` + - Check `MySQL` connection: `docker exec -it mysql mysql -uroot -p12345678 -e "SELECT 1"` + - Verify database schema: `docker exec -it mysql mysql -uroot -p12345678 test -e "DESC user"` + +2. Redis Connection Issues: + - Ensure `Redis` is running: `docker ps | grep redis` + - Test `Redis` connection: `docker exec -it redis redis-cli ping` + +3. Tracing Issues: + - Verify `Jaeger` is running: `docker ps | grep jaeger` + - Check `Jaeger` UI accessibility: http://localhost:16686 + - Ensure trace endpoint is correct in configuration + +4. gRPC Issues: + - Check if protobuf files are generated correctly + - Verify service registration in `etcd` + - Ensure server is listening on correct port diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md new file mode 100644 index 00000000000..c7e0c08d14c --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md @@ -0,0 +1,195 @@ +--- +title: HTTP with Database +slug: /examples/observability/trace/http-with-db +keywords: [trace, http, database, goframe] +description: distributed tracing in HTTP services with database operations using GoFrame +hide_title: true +--- + +# Tracing - HTTP with Database + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/http-with-db + + +## Description + +This example demonstrates how to implement distributed tracing in an HTTP service that interacts with a database using `GoFrame`. It shows how to: +- Configure tracing in HTTP services +- Trace database operations +- Propagate trace context +- Visualize distributed traces + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame MySQL Driver](https://github.com/gogf/gf/tree/master/contrib/drivers/mysql) +- [GoFrame Redis Driver](https://github.com/gogf/gf/tree/master/contrib/nosql/redis) +- [GoFrame OpenTelemetry Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client with tracing +├── server/ # Server example +│ ├── server.go # Server with tracing +│ └── config.yaml # Server configuration +├── sql.sql # Database schema +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Distributed Tracing + - Trace propagation + - Span management + - Trace visualization + +2. Database Operations + - Query tracing + - Transaction tracing + - Error tracking + +3. HTTP Integration + - Request tracing + - Context propagation + - Error handling + +## Prerequisites + +1. Running `MySQL` instance: + ```bash + docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + mysql:5.7 + ``` + +2. Initialize database schema: + ```bash + # Connect to MySQL container + docker exec -i mysql mysql -uroot -p12345678 test < sql.sql + ``` + +3. Running `Redis` instance: + ```bash + docker run -d --name redis \ + -p 6379:6379 \ + redis:6.0 + ``` + +4. Running `Jaeger` instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## Configuration + +The server configuration is defined in `server/config.yaml`: + +```yaml +database: + logger: + level: "all" # Log all SQL operations + stdout: true # Print to stdout + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true # Enable debug mode + +redis: + default: + address: 127.0.0.1:6379 # Default Redis instance + db: 0 + cache: + address: 127.0.0.1:6379 # Cache Redis instance + db: 1 +``` + +## Usage + +1. Start the server: + ```bash + cd server + go run server.go + ``` + +2. Run the client: + ```bash + cd client + go run client.go + ``` + +3. View traces: + Open http://localhost:16686 in your browser to view traces in `Jaeger` UI. + +## API Endpoints + +The server provides the following HTTP endpoints: + +1. Insert User + ```text + POST /insert + Request: {"Name": "string"} + Response: {"ID": number} + ``` + +2. Query User + ```text + GET /query + Request: {"ID": number} + Response: {"User": object} + ``` + +3. Delete User + ```text + DELETE /delete + Request: {"Id": number} + Response: {} + ``` + +## Implementation Details + +The example demonstrates: +1. HTTP request tracing +2. Database operation tracing +3. Redis cache integration +4. Error handling and propagation + +## Troubleshooting + +1. Database Connection Issues: + - Ensure `MySQL` is running: `docker ps | grep mysql` + - Check `MySQL` connection: `docker exec -it mysql mysql -uroot -p12345678 -e "SELECT 1"` + - Verify database schema: `docker exec -it mysql mysql -uroot -p12345678 test -e "DESC user"` + +2. Redis Connection Issues: + - Ensure `Redis` is running: `docker ps | grep redis` + - Test `Redis` connection: `docker exec -it redis redis-cli ping` + +3. Tracing Issues: + - Verify `Jaeger` is running: `docker ps | grep jaeger` + - Check `Jaeger` UI accessibility: http://localhost:16686 + - Ensure trace endpoint is correct in configuration + +4. HTTP Issues: + - Check if server is running: `curl http://localhost:8199/ping` + - Verify request format matches API specification + - Check server logs for detailed error messages diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http/http.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http/http.md new file mode 100644 index 00000000000..d5b603b2a23 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/http/http.md @@ -0,0 +1,116 @@ +--- +title: HTTP Service +slug: /examples/observability/trace/http +keywords: [trace, http, goframe] +description: distributed tracing in HTTP services using GoFrame +hide_title: true +--- + +# Tracing - HTTP Service + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/http + + +## Description + +This example demonstrates how to implement distributed tracing in HTTP services using `GoFrame`. It shows how to: +- Configure tracing in HTTP services +- Trace HTTP requests and responses +- Propagate trace context +- Visualize distributed traces + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client with tracing +├── server/ # Server example +│ └── server.go # Server with tracing +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Distributed Tracing + - Trace propagation + - Span management + - Trace visualization + +2. HTTP Integration + - Request tracing + - Context propagation + - Error handling + +## Prerequisites + +1. Running `Jaeger` instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## Usage + +1. Start the server: + ```bash + cd server + go run server.go + ``` + +2. Run the client: + ```bash + cd client + go run client.go + ``` + +3. View traces: + Open http://localhost:16686 in your browser to view traces in `Jaeger` UI. + +## API Endpoints + +The server provides the following HTTP endpoint: + +1. Hello World + ```text + GET /hello + Response: "Hello World" + ``` + +## Implementation Details + +The example demonstrates: +1. HTTP request tracing +2. Trace context propagation +3. Error handling and logging + +## Troubleshooting + +1. Server Issues: + - Ensure server is running: `curl http://localhost:8199/hello` + - Check server logs for detailed error messages + +2. Tracing Issues: + - Verify `Jaeger` is running: `docker ps | grep jaeger` + - Check `Jaeger` UI accessibility: http://localhost:16686 + - Ensure trace endpoint is correct in configuration diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md new file mode 100644 index 00000000000..9049f6a2492 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md @@ -0,0 +1,150 @@ +--- +title: In-Process Service (gRPC Exporter) +slug: /examples/observability/trace/inprocess-grpc +keywords: [trace, inprocess, grpc, goframe, otlp-grpc] +description: distributed tracing in in-process services using GoFrame with gRPC-based OpenTelemetry exporter +hide_title: true +--- + +# Tracing - In-Process Service (gRPC Exporter) + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/inprocess-grpc + + +## Description + +This example demonstrates how to implement distributed tracing in in-process services using GoFrame with gRPC-based OpenTelemetry exporter. It shows how to: +- Configure tracing in a single process using gRPC exporter +- Trace function calls and operations +- Propagate trace context through gRPC +- Visualize distributed traces + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry gRPC Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc) + +## Structure + +``` +. +├── main.go # Main application with gRPC-based tracing +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Distributed Tracing with gRPC Exporter + - gRPC-based trace data transmission + - Span management with gRPC context + - Trace visualization + +2. Function Call Tracing + - Function entry/exit tracing + - gRPC context propagation + - Error handling + +3. Data Operations + - User data retrieval + - Data aggregation + - Error handling + +## Prerequisites + +1. Running Jaeger instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## Usage + +1. Run the application: + ```bash + go run main.go + ``` + +2. View traces: + Open http://localhost:16686 in your browser to view traces in Jaeger UI. + +## Implementation Details + +The example demonstrates: + +1. User Data Operations + ```go + GetUser(ctx, id) // Retrieves complete user data + ├── GetInfo(ctx, id) // Gets basic user information + ├── GetDetail(ctx, id) // Gets detailed user information + └── GetScores(ctx, id) // Gets user scores + ``` + +2. Trace Context Flow + - Main function creates root span with gRPC context + - Each function creates child span with gRPC metadata + - Context propagates through gRPC calls + - All spans are properly ended with gRPC cleanup + +3. Error Handling + - Non-existent user IDs return nil + - Each function handles errors independently + - Errors are traced and logged with gRPC status codes + +4. gRPC-based Trace Export + - Uses gRPC protocol for trace data transmission + - Configures gRPC trace collector endpoint + - Handles gRPC connection management + - Supports gRPC authentication and TLS + +## Troubleshooting + +1. Application Issues: + - Check if application is running correctly + - Verify function call outputs + - Review error logs + +2. Tracing Issues: + - Verify Jaeger is running: `docker ps | grep jaeger` + - Check Jaeger UI accessibility: http://localhost:16686 + - Ensure gRPC endpoint is correct in configuration + +3. gRPC Issues: + - Check gRPC connection status + - Verify gRPC endpoint accessibility + - Review gRPC error messages and status codes + - Verify gRPC authentication token + +## Example Output + +For user ID 100: +```go +{ + "id": 100, + "name": "john", + "gender": 1, + "site": "https://goframe.org", + "email": "john@goframe.org", + "math": 100, + "english": 60, + "chinese": 50 +} +``` + +For non-existent user IDs: +```go +{} diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md new file mode 100644 index 00000000000..767d35cb9a0 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md @@ -0,0 +1,148 @@ +--- +title: In-Process Service (HTTP Exporter) +slug: /examples/observability/trace/inprocess +keywords: [trace, inprocess, goframe, otlp-http] +description: distributed tracing in in-process services using GoFrame with HTTP-based OpenTelemetry exporter +hide_title: true +--- + +# Tracing - In-Process Service (HTTP Exporter) + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/inprocess + + +## Description + +This example demonstrates how to implement distributed tracing in in-process services using GoFrame with HTTP-based OpenTelemetry exporter. It shows how to: +- Configure tracing in a single process using HTTP exporter +- Trace function calls and operations +- Propagate trace context +- Visualize distributed traces + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry HTTP Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## Structure + +``` +. +├── main.go # Main application with HTTP-based tracing +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Distributed Tracing with HTTP Exporter + - HTTP-based trace data transmission + - Span management + - Trace visualization + +2. Function Call Tracing + - Function entry/exit tracing + - Context propagation + - Error handling + +3. Data Operations + - User data retrieval + - Data aggregation + - Error handling + +## Prerequisites + +1. Running Jaeger instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## Usage + +1. Run the application: + ```bash + go run main.go + ``` + +2. View traces: + Open http://localhost:16686 in your browser to view traces in Jaeger UI. + +## Implementation Details + +The example demonstrates: + +1. User Data Operations + ```go + GetUser(ctx, id) // Retrieves complete user data + ├── GetInfo(ctx, id) // Gets basic user information + ├── GetDetail(ctx, id) // Gets detailed user information + └── GetScores(ctx, id) // Gets user scores + ``` + +2. Trace Context Flow + - Main function creates root span + - Each function creates child span + - Context propagates through function calls + - All spans are properly ended + +3. Error Handling + - Non-existent user IDs return nil + - Each function handles errors independently + - Errors are traced and logged + +4. HTTP-based Trace Export + - Uses HTTP protocol for trace data transmission + - Configures HTTP trace collector endpoint + - Handles HTTP connection management + +## Troubleshooting + +1. Application Issues: + - Check if application is running correctly + - Verify function call outputs + - Review error logs + +2. Tracing Issues: + - Verify Jaeger is running: `docker ps | grep jaeger` + - Check Jaeger UI accessibility: http://localhost:16686 + - Ensure HTTP endpoint is correct in configuration + +3. HTTP Export Issues: + - Check HTTP connection status + - Verify HTTP endpoint accessibility + - Review HTTP error messages + +## Example Output + +For user ID 100: +```go +{ + "id": 100, + "name": "john", + "gender": 1, + "site": "https://goframe.org", + "email": "john@goframe.org", + "math": 100, + "english": 60, + "chinese": 50 +} +``` + +For non-existent user IDs: +```go +{} diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/otlp/otlp.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/otlp/otlp.md new file mode 100644 index 00000000000..5f03412f7d4 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/otlp/otlp.md @@ -0,0 +1,163 @@ +--- +title: OpenTelemetry Example +slug: /examples/observability/trace/otlp +keywords: [trace, otlp, grpc, http, goframe] +description: OpenTelemetry trace data export methods in GoFrame +hide_title: true +--- + +# OpenTelemetry Tracing Examples + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/otlp + + +## Description + +This directory contains examples demonstrating different methods of exporting OpenTelemetry trace data in GoFrame applications. It includes: + +1. gRPC-based Export (`grpc/`) + - Uses gRPC protocol for trace data transmission + - Suitable for high-performance, streaming trace data export + - Supports bidirectional streaming and connection multiplexing + +2. HTTP-based Export (`http/`) + - Uses HTTP protocol for trace data transmission + - Suitable for environments with HTTP proxy or firewall restrictions + - Simpler to configure and debug + +## Directory Structure + +``` +. +├── grpc/ # gRPC-based tracing example +│ └── main.go # gRPC trace exporter implementation +├── http/ # HTTP-based tracing example +│ └── main.go # HTTP trace exporter implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame OpenTelemetry gRPC Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc) +- [GoFrame OpenTelemetry HTTP Tracing](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## Prerequisites + +1. Running Jaeger instance: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## Comparison of Export Methods + +### gRPC Export (grpc/) +1. Advantages: + - Higher performance + - Bidirectional streaming + - Connection multiplexing + - Better for high-volume tracing + +2. Configuration: + - Requires gRPC endpoint + - Supports authentication token + - Configurable connection settings + +3. Use Cases: + - High-volume trace data + - Microservices architecture + - Performance-critical systems + +### HTTP Export (http/) +1. Advantages: + - Simpler setup + - Works through HTTP proxies + - Easier to debug + - Better firewall compatibility + +2. Configuration: + - Requires HTTP endpoint + - Supports path configuration + - Standard HTTP settings + +3. Use Cases: + - Environments with HTTP proxy + - Simpler deployment requirements + - Development and testing + +## Usage + +### gRPC Export Example +1. Navigate to gRPC example: + ```bash + cd grpc + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +### HTTP Export Example +1. Navigate to HTTP example: + ```bash + cd http + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +3. View traces: + Open http://localhost:16686 in your browser to view traces in Jaeger UI. + +## Implementation Details + +Both examples demonstrate: +1. Trace Context Management + - Span creation and management + - Context propagation + - Baggage handling + +2. Error Handling + - Connection error handling + - Export error handling + - Graceful shutdown + +3. Configuration + - Service name configuration + - Endpoint configuration + - Authentication setup + +## Troubleshooting + +1. gRPC Export Issues: + - Check gRPC endpoint accessibility + - Verify authentication token + - Review gRPC connection logs + +2. HTTP Export Issues: + - Check HTTP endpoint accessibility + - Verify path configuration + - Review HTTP response codes + +3. General Issues: + - Verify Jaeger is running + - Check network connectivity + - Review application logs diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/processes/processes.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/processes/processes.md new file mode 100644 index 00000000000..1986ffed2a5 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/processes/processes.md @@ -0,0 +1,154 @@ +--- +title: Multi-Process Example +slug: /examples/observability/trace/processes +keywords: [trace, processes, gcmd, gproc, goframe] +description: Examples demonstrating distributed tracing across multiple processes using different GoFrame process management approaches +hide_title: true +--- + +# Multi-Process Tracing Examples + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/processes + + +## Description + +This directory contains examples demonstrating distributed tracing across multiple processes using different GoFrame process management approaches. It includes: + +1. Command-Line Process Management (`gcmd/`) + - Uses `gcmd` package for process management + - Demonstrates command-line based process creation + - Shows trace context propagation between parent and child processes + +2. Process Management (`gproc/`) + - Uses `gproc` package for process management + - Demonstrates programmatic process creation + - Shows trace context propagation in process hierarchy + +## Directory Structure + +``` +. +├── gcmd/ # Command-line process management example +│ ├── main.go # Main process implementation +│ └── sub/ # Sub-process implementation +│ └── sub.go # Sub-process code +├── gproc/ # Process management example +│ ├── main.go # Main process implementation +│ └── sub/ # Sub-process implementation +│ └── sub.go # Sub-process code +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## Features + +The examples showcase the following features: + +1. Process Management + - Process creation and execution + - Command-line argument handling + - Process communication + - Error handling + +2. Trace Context Propagation + - Parent-child process trace linking + - Context management across processes + - Trace baggage handling + +3. Logging and Debugging + - Process identification + - Debug logging + - Error reporting + +## Comparison of Approaches + +### Command-Line Management (gcmd/) +1. Features: + - Command-line interface + - Structured command handling + - Built-in help and documentation + - Command hierarchy support + +2. Use Cases: + - CLI applications + - Command-driven tools + - Interactive applications + +### Process Management (gproc/) +1. Features: + - Programmatic process control + - Direct process manipulation + - Shell command execution + - Process synchronization + +2. Use Cases: + - Background processes + - Service management + - Process automation + +## Usage + +### Command-Line Example +1. Navigate to gcmd example: + ```bash + cd gcmd + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +### Process Management Example +1. Navigate to gproc example: + ```bash + cd gproc + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +## Implementation Details + +Both examples demonstrate: + +1. Process Creation + - Main process initialization + - Sub-process spawning + - Process environment setup + +2. Context Management + - Context creation and initialization + - Context propagation between processes + - Context cleanup + +3. Error Handling + - Process execution errors + - Command execution errors + - Graceful error reporting + +## Troubleshooting + +1. Process Issues: + - Check process execution permissions + - Verify process environment variables + - Review process exit codes + +2. Context Issues: + - Verify context propagation + - Check context cancellation + - Review context deadlines + +3. General Issues: + - Check Go environment setup + - Verify GoFrame installation + - Review application logs diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/provider/provider.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/provider/provider.md new file mode 100644 index 00000000000..7b076007739 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/provider/provider.md @@ -0,0 +1,192 @@ +--- +title: OpenTelemetry Provider Examples +slug: /examples/observability/trace/provider +keywords: [trace, provider, grpc, http, goframe] +description: Examples demonstrating different OpenTelemetry trace provider configurations in GoFrame +hide_title: true +--- + +# OpenTelemetry Provider Examples + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/provider + + +## Description + +This directory contains examples demonstrating different OpenTelemetry trace provider configurations in GoFrame applications. It includes: + +1. gRPC Provider (`grpc/`) + - Uses gRPC protocol for trace data transmission + - Configures trace provider with gRPC exporter + - Demonstrates resource and sampler configuration + +2. HTTP Provider (`http/`) + - Uses HTTP protocol for trace data transmission + - Configures trace provider with HTTP exporter + - Shows resource and sampler configuration + +3. Internal Components (`internal/`) + - Common provider initialization code + - Shared constants and configurations + - Utility functions for tracing setup + +## Directory Structure + +``` +. +├── grpc/ # gRPC provider example +│ └── main.go # gRPC provider implementation +├── http/ # HTTP provider example +│ └── main.go # HTTP provider implementation +├── internal/ # Shared components +│ ├── consts.go # Constants definition +│ ├── provider.go # Provider initialization +│ └── request.go # Request utilities +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [OpenTelemetry](https://opentelemetry.io) + +## Features + +The examples showcase the following features: + +1. Provider Configuration + - Resource configuration + - Sampler configuration + - Exporter setup + - Context propagation + +2. Resource Attributes + - Service name + - Host information + - Process details + - Custom attributes + +3. Sampling Strategies + - Always sample + - Never sample + - Parent-based sampling + - Trace ID ratio based sampling + +4. Span Processing + - Simple span processor + - Batch span processor + - Span limits configuration + +## Comparison of Providers + +### gRPC Provider (grpc/) +1. Features: + - High-performance streaming + - Bidirectional communication + - Connection multiplexing + - Compression support + +2. Configuration: + - gRPC endpoint + - Authentication headers + - Compression options + - Connection security + +3. Use Cases: + - High-volume tracing + - Performance-critical systems + - Streaming trace data + +### HTTP Provider (http/) +1. Features: + - Standard HTTP protocol + - Simple configuration + - Firewall friendly + - Compression support + +2. Configuration: + - HTTP endpoint + - URL path + - Headers configuration + - Compression level + +3. Use Cases: + - Basic tracing needs + - HTTP proxy environments + - Simple deployment + +## Usage + +### gRPC Provider Example +1. Navigate to gRPC example: + ```bash + cd grpc + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +### HTTP Provider Example +1. Navigate to HTTP example: + ```bash + cd http + ``` + +2. Run the example: + ```bash + go run main.go + ``` + +## Implementation Details + +Both examples demonstrate: + +1. Provider Setup + - Resource configuration + - Sampler configuration + - Exporter initialization + - Context management + +2. Resource Configuration + - Service identification + - Host information + - Process attributes + - Custom tags + +3. Sampling Configuration + - Sampling strategy selection + - Sampling rate configuration + - Parent context handling + +4. Error Handling + - Connection errors + - Export errors + - Shutdown handling + +## Troubleshooting + +1. Provider Issues: + - Check endpoint configuration + - Verify authentication settings + - Review connection errors + - Check compression settings + +2. Resource Issues: + - Verify service name + - Check host information + - Review attribute values + +3. Sampling Issues: + - Check sampling strategy + - Verify sampling rate + - Review parent context + +4. General Issues: + - Check network connectivity + - Verify dependencies + - Review error logs diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/trace.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/trace.md new file mode 100644 index 00000000000..bf410f89b81 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/observability/trace/trace.md @@ -0,0 +1,9 @@ +--- +slug: '/examples/observability/trace' +title: 'Tracing' +hide_title: true +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/injection/injection.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/injection/injection.md new file mode 100644 index 00000000000..1a28b955bd3 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/injection/injection.md @@ -0,0 +1,151 @@ +--- +title: Dependency Injection +slug: /examples/practices/injection +keywords: [dependency injection, testing, goframe, di] +description: A dependency injection example using GoFrame framework and do package +hide_title: true +--- + +# Dependency Injection Example + +Github Source: https://github.com/gogf/examples/tree/main/practices/injection + + +## Introduction + +This example demonstrates how to implement dependency injection in a `GoFrame` application. + +Key demonstrations: +1. Using monorepo pattern for code repository management +2. Basic dependency injection setup +3. Service layer implementation with `DI` +4. Controller layer integration with `gRPC` +5. Unit testing with mock dependencies + +The implementation focuses on making core business logic easily testable: +- Separation of concerns using dependency injection +- Clear interfaces for service dependencies +- Independent unit testing for each layer to ensure code quality + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [MongoDB](https://www.mongodb.com) +- [Redis](https://redis.io) +- [github.com/samber/do](https://github.com/samber/do) + +## Directory Structure + +```text +injection/ +├── app/ +│ ├── gateway/ # API Gateway service, calls user gRPC service for external interfaces +│ │ ├── api/ # API definitions +│ │ ├── internal/ # Internal implementations +│ │ │ ├── cmd/ # Command line tools +│ │ │ ├── controller/ # Controllers +│ │ │ ├── model/ # Data models +│ │ │ └── service/ # Business logic +│ │ └── manifest/ # Configuration files +│ └── user/ # User service +│ ├── api/ # API definitions +│ │ ├── entity/ # Entity definitions +│ │ └── user/ # User API proto +│ ├── internal/ # Internal implementations +│ │ ├── cmd/ # Command line tools +│ │ ├── controller/ # Controllers with DI +│ │ ├── dao/ # Data Access Objects +│ │ ├── model/ # Data models +│ │ └── service/ # Business logic with DI +│ └── manifest/ # Configuration files +├── hack/ # Development tools +└── utility/ # Common utilities + ├── injection/ # DI utilities + └── mongohelper/ # MongoDB helper tools +``` + +## Features + +- Dependency injection usage +- `MongoDB` and `Redis` integration +- `gRPC` service implementation +- Complete unit testing +- Resource cleanup handling +- Named dependency support + +## Installation + +1. Clone the repository: + ```bash + git clone https://github.com/gogf/examples.git + cd examples/practices/injection + ``` + +2. Install dependencies: + ```bash + go mod tidy + ``` + +3. Start required services using `Docker`: + ```bash + # Start MongoDB + docker run -d --name mongo -p 27017:27017 mongo:latest + + # Start Redis + docker run -d --name redis -p 6379:6379 redis:latest + ``` + +## Usage + +1. Run the `gRPC Server` service: + ```bash + cd examples/practices/injection/app/user + go run main.go server + ``` + +2. Run the `HTTP Server` service: + ```bash + cd examples/practices/injection/app/gateway + go run main.go server + ``` + +3. (Optional) Run async worker, only demonstrates multi-command functionality, no actual logic: + ```bash + cd examples/practices/injection/app/gateway + go run main.go worker + ``` + +4. (Optional) Run tests: + ```bash + go test ./... + ``` + +## Implementation Details + +### Dependency Injection Setup +- Using `github.com/samber/do` package for dependency management +- Support for named and unnamed dependency management +- Helper functions for common operations + +### Service Layer +- Clear separation of concerns +- Interface-based design +- Easy to test with mock implementations + +### Controller Layer +- `gRPC` integration with `DI` support +- Clear error handling +- Proper resource management + +### Testability +- Complete unit tests for data layer (`dao`), service layer (`service`), and interface layer (`controller`) +- Mock dependencies, database configuration for unit tests managed through `manifest/config` +- Service addresses or domains for microservice connections managed through configuration files for easy service mocking + +## Important Notes + +- Properly clean up resources in the `Shutdown` method when registering dependency injection +- Use named dependencies when multiple instances of the same type are needed in dependency injection + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/practices.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/practices.md new file mode 100644 index 00000000000..a9de3736707 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/practices/practices.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/practices' +title: 'Practices' +hide_title: true +sidebar_position: 99 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/consul/consul.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/consul/consul.md new file mode 100644 index 00000000000..42cb4f8362d --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/consul/consul.md @@ -0,0 +1,113 @@ +--- +title: Consul +slug: /examples/registry/consul +keywords: [registry, consul, service discovery, goframe] +description: Consul service registry integration in GoFrame +hide_title: true +--- + +# Registry - `Consul` Integration + +Github Source: https://github.com/gogf/examples/tree/main/registry/consul + + +## Description + +This example demonstrates how to integrate `Consul` service registry with `GoFrame` applications. It shows how to: +- Register services with `Consul` +- Discover services using `Consul` +- Implement service health checks +- Build distributed systems + +## Structure + +```text +. +├── docker-compose/ # Docker compose files for running Consul +├── grpc/ # gRPC service examples +│ ├── client/ # gRPC client implementation +│ ├── controller/ # gRPC service controllers +│ ├── protobuf/ # Protocol buffer definitions +│ └── server/ # gRPC server implementation +│ ├── main.go # Server startup code +│ └── config.yaml # Server configuration +├── http/ # HTTP service examples +│ ├── client/ # HTTP client implementation +│ └── server/ # HTTP server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Registration + - Automatic service registration + - Health check configuration + - Metadata management + +2. Service Discovery + - Dynamic service discovery + - Load balancing + - Failover support + +3. Protocol Support + - `HTTP` services + - `gRPC` services + - Custom protocols + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Consul Registry](https://github.com/gogf/gf/tree/master/contrib/registry/consul) +- [Consul](https://developer.hashicorp.com/consul/downloads) + +## Prerequisites + +1. Running `Consul` server: + ```bash + # Using docker-compose + cd docker-compose + docker-compose up -d + ``` + +## Usage + +### `HTTP` Example + +1. Start the `HTTP` server: + ```bash + cd http/server + go run server.go + ``` + +2. Run the `HTTP` client: + ```bash + cd http/client + go run client.go + ``` + +### `gRPC` Example + +1. Start the `gRPC` server: + ```bash + cd grpc/server + go run server.go + ``` + +2. Run the `gRPC` client: + ```bash + cd grpc/client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. `Consul` client configuration +2. Service registration process +3. Service discovery mechanism +4. Health check implementation +5. Load balancing strategies diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/etcd/etcd.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/etcd/etcd.md new file mode 100644 index 00000000000..a253619e04f --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/etcd/etcd.md @@ -0,0 +1,130 @@ +--- +title: Etcd +slug: /examples/registry/etcd +keywords: [registry, etcd, service discovery, goframe] +description: Etcd service registry integration in GoFrame +hide_title: true +--- + +# Registry - `Etcd` Integration + +Github Source: https://github.com/gogf/examples/tree/main/registry/etcd + + +## Description + +This example demonstrates how to integrate `Etcd` service registry with `GoFrame` applications. It shows how to: +- Register services with `Etcd` +- Discover services using `Etcd` +- Implement service health checks +- Build distributed systems + +## Structure + +```text +. +├── grpc/ # gRPC service examples +│ ├── client/ # gRPC client implementation +│ ├── controller/ # gRPC service controllers +│ ├── protobuf/ # Protocol buffer definitions +│ └── server/ # gRPC server implementation +│ ├── main.go # Server startup code +│ └── config.yaml # Server configuration +├── http/ # HTTP service examples +│ ├── client/ # HTTP client implementation +│ └── server/ # HTTP server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Registration + - Automatic service registration + - TTL-based health check + - Metadata management + - Lease management + +2. Service Discovery + - Dynamic service discovery + - Load balancing + - Failover support + - Watch mechanism + +3. Protocol Support + - `HTTP` services + - `gRPC` services + - Custom protocols + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Etcd Registry](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) +- [Etcd](https://etcd.io/docs/v3.5/install/) + +## Prerequisites + +1. Running `etcd` server: + ```bash + # Using docker + docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.4.24 + ``` + +## Usage + +### `HTTP` Example + +1. Start the `HTTP` server: + ```bash + cd http/server + go run server.go + ``` + +2. Run the `HTTP` client: + ```bash + cd http/client + go run client.go + ``` + +### `gRPC` Example + +1. Start the `gRPC` server: + ```bash + cd grpc/server + go run server.go + ``` + +2. Run the `gRPC` client: + ```bash + cd grpc/client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. etcd client configuration +2. Service registration process +3. Service discovery mechanism +4. Health check implementation +5. Load balancing strategies +6. Watch mechanism usage +7. Lease management + +## Notes + +- etcd server must be running +- Default etcd address: 127.0.0.1:2379 +- Services are registered automatically +- TTL-based health checks +- Automatic lease renewal +- Load balancing is handled automatically +- Consider security in production +- Handle failover scenarios +- Monitor service health +- Implement proper error handling +- Watch for service changes +- Manage leases properly diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/file/file.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/file/file.md new file mode 100644 index 00000000000..712933b6f24 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/file/file.md @@ -0,0 +1,100 @@ +--- +title: File +slug: /examples/registry/file +keywords: [registry, file, service discovery, goframe] +description: File-based service registry integration in GoFrame +hide_title: true +--- + +# Registry - `File` Integration + +Github Source: https://github.com/gogf/examples/tree/main/registry/file + + +## Description + +This example demonstrates how to use File-based service registry with `GoFrame` applications. It shows how to: +- Register services using file system +- Discover services from files +- Implement simple service discovery +- Build basic distributed systems + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client implementation with file-based discovery +├── server/ # Server example +│ └── server.go # Server implementation with file registration +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Registration + - File-based service registration + - Simple metadata storage + - Local file system integration + - Automatic file management + +2. Service Discovery + - File-based service discovery + - Local service lookup + - Basic load balancing + - Simple failover support + +3. Protocol Support + - `HTTP` services + - Extensible for other protocols + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame File Registry](https://github.com/gogf/gf/tree/master/contrib/registry/file) + +## Prerequisites + +No additional infrastructure required. The example uses the local file system for service registration and discovery. + +## Usage + +1. Start the server: + ```bash + cd server + go run server.go + ``` + +2. Run the client: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. File-based registry configuration +2. Service registration process +3. Service discovery mechanism +4. Basic health check implementation +5. Simple load balancing strategy + +## Notes + +- Uses local file system +- Suitable for development and testing +- Simple to set up and use +- No external dependencies +- File-based persistence +- Basic service discovery +- Limited scalability +- Consider security in production +- Handle file permissions properly +- Implement proper error handling +- Monitor file system usage +- Clean up old registrations diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/nacos/nacos.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/nacos/nacos.md new file mode 100644 index 00000000000..fc6feb25253 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/nacos/nacos.md @@ -0,0 +1,121 @@ +--- +title: Nacos +slug: /examples/registry/nacos +keywords: [registry, nacos, service discovery, goframe] +description: Nacos service registry integration in GoFrame +hide_title: true +--- + +# Registry - `Nacos` Integration + +Github Source: https://github.com/gogf/examples/tree/main/registry/nacos + + +## Description + +This example demonstrates how to integrate `Nacos` service registry with `GoFrame` applications. It shows how to: +- Register services with `Nacos` +- Discover services using `Nacos` +- Implement service health checks +- Build distributed systems + +## Structure + +```text +. +├── docker-compose/ # Docker compose files for running Nacos +│ ├── standalone/ # Standalone mode configuration +│ └── cluster/ # Cluster mode configuration +├── grpc/ # gRPC service examples +│ ├── client/ # gRPC client implementation +│ ├── controller/ # gRPC service controllers +│ ├── protobuf/ # Protocol buffer definitions +│ └── server/ # gRPC server implementation +│ ├── main.go # Server startup code +│ └── config.yaml # Server configuration +├── http/ # HTTP service examples +│ ├── client/ # HTTP client implementation +│ └── server/ # HTTP server implementation +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Registration + - Automatic service registration + - Health check configuration + - Metadata management + - Namespace support + - Group management + +2. Service Discovery + - Dynamic service discovery + - Load balancing + - Failover support + - Service grouping + - Namespace isolation + +3. Protocol Support + - `HTTP` services + - `gRPC` services + - Custom protocols + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Nacos Registry](https://github.com/gogf/gf/tree/master/contrib/registry/nacos) +- [Nacos](https://nacos.io/en-us/docs/v2/quickstart/quick-start.html) + +## Prerequisites + +1. Running `Nacos` server: + ```bash + # Using docker-compose + cd docker-compose + docker-compose up -d + ``` + +## Usage + +### `HTTP` Example + +1. Start the `HTTP` server: + ```bash + cd http/server + go run server.go + ``` + +2. Run the `HTTP` client: + ```bash + cd http/client + go run client.go + ``` + +### `gRPC` Example + +1. Start the `gRPC` server: + ```bash + cd grpc/server + go run server.go + ``` + +2. Run the `gRPC` client: + ```bash + cd grpc/client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. `Nacos` client configuration +2. Service registration process +3. Service discovery mechanism +4. Health check implementation +5. Load balancing strategies +6. Namespace management +7. Group configuration diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/polaris/polaris.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/polaris/polaris.md new file mode 100644 index 00000000000..d58eef82737 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/polaris/polaris.md @@ -0,0 +1,101 @@ +--- +title: Polaris +slug: /examples/registry/polaris +keywords: [registry, polaris, service discovery, goframe] +description: Polaris service registry integration in GoFrame +hide_title: true +--- + +# Registry - `Polaris` Integration + +Github Source: https://github.com/gogf/examples/tree/main/registry/polaris + + +## Description + +This example demonstrates how to integrate `Polaris` service registry with `GoFrame` applications. It shows how to: +- Register services with `Polaris` +- Discover services using `Polaris` +- Implement service health checks +- Build distributed systems + +## Structure + +```text +. +├── client/ # Client example +│ └── client.go # Client implementation with Polaris discovery +├── server/ # Server example +│ └── server.go # Server implementation with Polaris registration +├── go.mod # Go module file +└── go.sum # Go module checksums +``` + +## Features + +The example showcases the following features: +1. Service Registration + - Automatic service registration + - Health check configuration + - Metadata management + - Service routing + - Rate limiting + +2. Service Discovery + - Dynamic service discovery + - Load balancing + - Failover support + - Circuit breaking + - Fault tolerance + +3. Protocol Support + - `HTTP` services + - Custom protocols + - Protocol extension + +## Requirements + +- [Go](https://golang.org/dl/) `1.22` or higher +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Polaris Registry](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) +- [Polaris](https://polarismesh.cn/docs/) + +## Prerequisites + +1. Running `Polaris` server: + ```bash + # Using docker + docker run -d \ + --name polaris \ + -p 8090:8090 \ + -p 8091:8091 \ + -p 8093:8093 \ + -p 8761:8761 \ + polarismesh/polaris-server + ``` + +## Usage + +1. Start the server: + ```bash + cd server + go run server.go + ``` + +2. Run the client: + ```bash + cd client + go run client.go + ``` + +## Implementation Details + +The example demonstrates: +1. `Polaris` client configuration +2. Service registration process +3. Service discovery mechanism +4. Health check implementation +5. Load balancing strategies +6. Circuit breaker configuration +7. Rate limiting setup diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/registry.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/registry.md new file mode 100644 index 00000000000..aadaf0aee30 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/examples/registry/registry.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/registry' +title: 'Service Registry' +hide_title: true +sidebar_position: 6 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" new file mode 100644 index 00000000000..99d259b29e9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" @@ -0,0 +1,73 @@ +--- +slug: '/quick/hello-world' +title: 'Hello World' +hide_title: true +sidebar_position: 2 +keywords: [GoFrame framework, Web Server, Go language, modular framework, route binding, automatic API documentation, g.Server, network request, Server object, GoFrame tutorial] +description: "Building a simple 'Hello World' Web Server using the GoFrame framework. GoFrame is a modular Go language framework that offers a convenient way to construct a web server. This article provides a detailed analysis of the code example, including the creation of a Server object, route binding, port setting, and interpretation of the running results, offering a quick start path for beginners." +--- + +The `Web Server` module is a core component of `GoFrame`, a comprehensive modular framework. We'll start our journey with web service development, as it's an excellent way to introduce the framework's capabilities. + +## Hello World + +Let's create a simple web server: + +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello World!") + }) + s.SetPort(8000) + s.Run() +} +``` + +Let's break down this code: +- The `g.Server()` method returns a default `Server` object implemented as a **singleton**. This means multiple calls return the same instance. The `g` package is a convenience wrapper that initializes and provides access to commonly used components. +- Routes and their handlers are registered using the `Server` object's `BindHandler` method. In this example, we've mapped the root path `/` to a handler that returns "Hello World". +- The handler function receives a `*ghttp.Request` parameter (`r`) containing the current request's context. Here, we use `r.Response.Write` to send our response. +- The `SetPort` method configures the server's listening port (8000 in this case). If not specified, a random port is assigned. +- Finally, the `Run()` method starts the server and blocks until the program is terminated. + +## Running the Server + +When you run the program, you'll see output similar to this in your terminal: +```html +$ go run main.go +2024-10-27 21:30:39.412 [INFO] pid[58889]: http server started listening on [:8000] +2024-10-27 21:30:39.412 [INFO] {08a0b0086e5202184111100658330800} openapi specification is disabled + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------|------------- + :8000 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------|------------- +``` + +The output provides several key pieces of information: +- Process ID (`58889`) and listening address (`:8000`, which means the server is listening on all local IP addresses on port `8000`) +- A notice about OpenAPI specification being disabled (the framework includes automatic API documentation generation, which we'll cover in detail later) +- A routing table showing: + - `ADDRESS`: The listening address for each route. Note that a single process can host multiple `Server` instances, each with its own address + - `METHOD`: The HTTP methods the route handles (`ALL` means it accepts any method: GET, POST, PUT, DELETE, etc.) + - `ROUTE`: The URL path pattern + - `HANDLER`: The route's callback function name (shown as `main.main.func1` since we used an anonymous function) + - `MIDDLEWARE`: Any middleware attached to the route (we'll cover middleware, a powerful request interceptor feature, in later chapters) + +Visit http://127.0.0.1:8000/ in your browser to see the result: + +![img_1.png](img_1.png) + +## What's Next + +Great job! You've just created your first web server with `GoFrame`! + +In the next section, we'll explore how to handle client-submitted data in your web server. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" new file mode 100755 index 00000000000..058c27047ef Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" new file mode 100755 index 00000000000..91e150427bb Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" new file mode 100644 index 00000000000..139ad94bf9f --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" @@ -0,0 +1,21 @@ +--- +slug: /quick-next +title: "What's Next" +hide_title: true +sidebar_position: 10 +keywords: [GoFrame,GoFrame Framework,Web Server,Documents,Modular Design,Low Coupling,Video Tutorial,Project Scaffold,Business Project,Development Process] +description: "Quickly understand the development process and features of the Web Server API by viewing the Documents sections to resolve any doubts. GoFrame is a low-coupling, modular design framework with independent module designs, and independently written documentation. The community provides introductory video tutorials, and later a complete business project will be developed using the GoFrame framework project scaffold." +--- + +## What You've Learned +By now, you should have a solid understanding of building Web APIs with `GoFrame` and have hands-on experience with its key features. While we've covered the basics in these quick-start guides, there's much more to explore. For in-depth explanations and advanced features, check out the relevant sections in our main documentation. If you ever find yourself stuck with a particular module, the documentation provides detailed insights into each component's functionality, usage patterns, and common pitfalls. + +:::info +As you dive into the documentation, keep in mind that `GoFrame` follows a modular, loosely coupled design philosophy. Each module is designed to work independently, and our documentation reflects this approach - each chapter focuses specifically on its respective component. +::: + +So, what comes next? + +## Building Real Applications + +In the upcoming sections, we'll walk through building a complete business application using the `GoFrame` project scaffold. This hands-on tutorial will show you how to put everything together in a real-world context. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" new file mode 100755 index 00000000000..9648141f9fe Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" new file mode 100755 index 00000000000..9d5dc1f50c0 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" new file mode 100755 index 00000000000..8255a733774 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" new file mode 100755 index 00000000000..4364353e498 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" new file mode 100755 index 00000000000..583a983cd4d Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" new file mode 100755 index 00000000000..8ee3e595775 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..3a7f057aac2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" @@ -0,0 +1,68 @@ +--- +slug: '/quick/install' +title: 'Installation' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame, GoFrame Framework, Install GoFrame, Modular Framework, Low Coupling Design, Web Server API Development, Go Language Environment, Project Framework, Basic Components, HTTP Server] +description: "Quick start guide for the GoFrame framework. GoFrame is a modular, low coupling design development framework that includes common basic components and development tools. It's suitable for complete business project frameworks and independent component libraries. The content covers downloading and installing GoFrame, running basic operations, and introducing how to develop simple Web Server API applications." +--- + +Welcome to the `GoFrame` Quick Start Guide! `GoFrame` is a modern Go framework designed with modularity and loose coupling in mind. You can use it either as a full-featured application framework or cherry-pick individual components for your specific needs. + +This guide will walk you through installing and setting up `GoFrame`, culminating in building your first web service. + +:::info +New to Go? We recommend checking out our [Environment Setup Guide](../../docs/其他资料/准备开发环境/准备开发环境.md) first to get your development environment ready. + +Stuck or have questions? Feel free to leave a comment 💬. Our community is here to help, and we'll get back to you as quickly as possible 🌟🌟. +::: + +## Go Version Requirements + +To ensure stability and security, `GoFrame` maintains compatibility with recent Go versions. We typically support the latest release and three previous versions. + +Minimum requirement: +```bash +golang version >= 1.20 +``` + +## Getting Started + +Install the latest version: +```bash +go get -u -v github.com/gogf/gf/v2 +``` + +Or add this to your `go.mod`: +```bash +require github.com/gogf/gf/v2 latest +``` + +## Quick Test + +Let's verify your installation with a simple program: +```go title="main.go" +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2" +) + +func main() { + fmt.Println("Hello GF:", gf.VERSION) +} +``` + +Run it: +```bash +go run main.go +``` + +You should see something like: +```bash +Hello GF: v2.7.4 +``` + +Perfect! Now that `GoFrame` is set up, let's build your first HTTP server. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" new file mode 100644 index 00000000000..27c71109e1c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" @@ -0,0 +1,118 @@ +--- +slug: '/quick/middleware' +title: 'Using Middleware' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame, Middleware, Web Server, ErrorHandler, Request Interception, Pre-middleware, Post-middleware, Custom Error Handling, GoFrame Framework, Request Flow Control] +description: "Use middleware in the GoFrame framework to intercept requests and return results, and implement custom processing logic through pre and post middleware. The example code demonstrates how to define an error handling middleware and bind it to a route. Middleware makes request error handling and output format unification flexible and powerful." +--- + +Remember the question from our previous chapter about handling error objects? Let's solve it by exploring the `Web Server`'s middleware feature. + +## Understanding Middleware + +Middleware acts as an interceptor in your web server, allowing you to process requests both before and after they reach your route handlers. You can use middleware to modify requests, validate data, handle errors, or transform responses. + +Middleware functions look similar to regular route handlers but include a special `Middleware` object for controlling request flow. There are two types: +- **Pre-middleware**: Executes before the route handler +- **Post-middleware**: Executes after the route handler + +Here's the basic structure: +```go +func Middleware(r *ghttp.Request) { + // Pre-middleware logic + r.Middleware.Next() + // Post-middleware logic +} +``` + +The `r.Middleware.Next()` call passes control to the next handler in the chain. If you skip this call, the chain stops and returns immediately (useful for authentication middleware that needs to block unauthorized requests). + +## Implementing Error Handling + +Let's enhance our previous example with middleware: + +```go title="main.go" +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"User's name"` + Age int `v:"required" dc:"User's age"` +} +type HelloRes struct{} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} + +func ErrorHandler(r *ghttp.Request) { + r.Middleware.Next() + + if err := r.GetError(); err != nil { + r.Response.Write("Error: ", err.Error()) + return + } +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ErrorHandler) + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` + +Key changes: +- We've added an `ErrorHandler` middleware that checks for errors after the route handler executes +- The middleware is applied to all routes in the group via `group.Middleware(ErrorHandler)` + +## Testing the Changes + +When you run the server, you'll see: + +```text +2024-11-06 22:30:06.927 [INFO] pid[35434]: http server started listening on [:8000] +2024-11-06 22:30:06.927 [INFO] {905637567a67051830833b2189796dda} openapi specification is disabled + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-------------------|-------------------- + :8000 | GET | / | main.(*Hello).Say | main.ErrorHandler +----------|--------|-------|-------------------|-------------------- +``` + +Notice that our `ErrorHandler` is now listed in the middleware column. + +Let's test with valid parameters at http://127.0.0.1:8000/?name=john&age=18: + +![img.png](img.png) + +And with missing parameters at http://127.0.0.1:8000/: + +![img_4.png](img_4.png) + +## Looking Ahead + +We've seen how middleware can elegantly handle error cases and customize error messages. But this is just scratching the surface of what middleware can do. + +Consider a common scenario: most APIs in a project typically return responses in a consistent format (like JSON). Could we use middleware to standardize this across all our endpoints? Absolutely! That's exactly what we'll explore in the next chapter. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" new file mode 100644 index 00000000000..faaee9b5dbb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" @@ -0,0 +1,123 @@ +--- +slug: '/quick/strict-router' +title: 'Standard Routing' +hide_title: true +sidebar_position: 5 +keywords: [GoFrame, GoFrame framework, standard routing, route registration, data structure, route object management, Go language, web server, HTTP methods, route callbacks] +description: "Use standard routing in the GoFrame framework to simplify route registration, focusing on business logic. Standardize route registration by defining request and response data structures, and manage routes using an object-oriented approach to enhance code maintainability. Provides complete sample code and execution results to guide readers in applying it to real projects." +--- + +The `GoFrame` framework provides a standardized routing approach that simplifies route registration and lets developers focus on business logic. We call this **Standard Routing** - a more intuitive and maintainable way to handle HTTP endpoints. + +## Defining Request/Response Types + +Standard routing uses structured types for both requests and responses. This approach enables parameter validation, documentation generation, and future extensibility: + +```go +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"User's name"` + Age int `v:"required" dc:"User's age"` +} +type HelloRes struct {} +``` + +Key components: +- The request struct embeds `g.Meta` with routing metadata tags: + - `path`: The URL path to register + - `method`: The HTTP method to handle (GET, POST, etc.) +- Field tags provide additional functionality: + - `v`: Validation rules (short for "valid"). Here, `v:"required"` marks fields as mandatory + - `dc`: Documentation comments (short for "description") for API docs + +:::info +For a complete reference of available tags and validation rules, check out the relevant chapters in our development manual. For now, just focus on understanding their basic purpose. +::: + +## Object-Oriented Route Handlers + +For better maintainability, especially in larger APIs, we use an object-oriented approach instead of registering routes one by one. Here's how it works: + +```go +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} +``` + +Benefits of this approach: +- Routes are organized into logical groups via handler objects +- Method signatures are more idiomatic and business-focused compared to raw `func(*ghttp.Request)` handlers +- The original request object is still accessible via `g.RequestFromCtx` when needed + +## Complete Example + +Here's how we'd rewrite our web server using standard routing: + +```go title="main.go" +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"User's name"` + Age int `v:"required" dc:"User's age"` +} +type HelloRes struct{} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` + +Key points: +- `s.Group` creates a route group with a common prefix ("`/`" in this case) +- `group.Bind` registers all public methods of our handler object automatically, using struct metadata to configure routes + +## Testing the API + +Let's test with valid parameters at http://127.0.0.1:8000/?name=john&age=18: + +![img.png](img.png) + +When we try an invalid request at http://127.0.0.1:8000/, we get no output. This is because the validation failed before reaching our handler - the server rejected the request due to missing required parameters. + +## What's Next? + +While we've improved our routing structure, we still need better control over error handling. For example, how can we: +- Capture and customize validation error messages? +- Format error responses consistently? + +We'll tackle these questions in the next chapter. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" new file mode 100644 index 00000000000..2273a771bdd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" @@ -0,0 +1,12 @@ +--- +slug: '/quick' +title: 'Quick Start' +sidebar_position: 0 +hide_title: true +keywords: [quick start, GoFrame framework, GoFrame tutorial, web development, framework usage, API reference, development guide, project setup, programming introduction, technical documentation] +description: "Through this quick start guide, master the basic steps and techniques for web development using the GoFrame framework. This article provides a detailed project setup process and important API references to help developers quickly get started with the GoFrame framework and enhance programming efficiency." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" new file mode 100644 index 00000000000..ed9750244bd --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" @@ -0,0 +1,160 @@ +--- +slug: '/quick/api-doc' +title: 'API Documentation' +hide_title: true +sidebar_position: 8 +keywords: [GoFrame, GoFrame Framework, API Documentation Generation, OpenAPIv3, Swagger UI, Automated API Documentation, API Data Structure, GoFrame Tutorial, Web Development, API Documentation] +description: "Using the GoFrame framework, standard-compliant API documentation can be easily generated. This article introduces how to enhance API definitions and combine them with the OpenAPIv3 protocol and Swagger UI to automatically generate and display API documentation. The code examples provide a detailed explanation of API data structure definitions, routing middleware settings, and how to optimize the API documentation generation process using GoFrame framework features." +--- + +Generating API documentation with `GoFrame` is straightforward. Let's enhance our API definitions from the previous sections to create more comprehensive documentation. + +## Enhanced API Definitions + +```go +type HelloReq struct { + g.Meta `path:"/" method:"get" tags:"Test" summary:"Hello world test case"` + Name string `v:"required" json:"name" dc:"User's name"` + Age int `v:"required" json:"age" dc:"User's age"` +} +type HelloRes struct { + Content string `json:"content" dc:"Response content"` +} +``` + +We've added two important tags to the `g.Meta` field: +- `tags`: Categorizes the API into logical groups +- `summary`: Provides a brief description of the API endpoint + +:::info +These tags are standard fields in the `OpenAPIv3` specification. For a complete guide on API documentation generation and available tags, refer to the relevant section in our development manual. +::: + +## Complete Implementation + +```go title="main.go" +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type Response struct { + Message string `json:"message" dc:"Response message"` + Data interface{} `json:"data" dc:"Response payload"` +} + +type HelloReq struct { + g.Meta `path:"/" method:"get" tags:"Test" summary:"Hello world test case"` + Name string `v:"required" json:"name" dc:"User's name"` + Age int `v:"required" json:"age" dc:"User's age"` +} +type HelloRes struct { + Content string `json:"content" dc:"Response content"` +} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} + +func Middleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(Middleware) + group.Bind( + new(Hello), + ) + }) + s.SetOpenApiPath("/api.json") + s.SetSwaggerPath("/swagger") + s.SetPort(8000) + s.Run() +} +``` + +Key additions in this example: +- `s.SetOpenApiPath("/api.json")` enables OpenAPIv3 documentation generation at `/api.json` +- `s.SetSwaggerPath("/swagger")` enables the built-in Swagger UI at `/swagger` (the UI can be customized - see our development manual for details) + +### Understanding OpenAPIv3 + +OpenAPIv3 is the industry standard for API documentation. It defines APIs in a machine-readable format (typically JSON) that can be consumed by various documentation tools like Swagger UI, Postman, and ApiFox. + +### Understanding Swagger + +Swagger is a popular tool for visualizing and interacting with APIs. It supports multiple documentation formats, with OpenAPIv3 being the most common. + +To use the built-in Swagger UI in this example, we need both OpenAPIv3 and Swagger UI enabled via the `SetOpenApiPath` and `SetSwaggerPath` methods respectively. + +## Running the Application + +### Console Output + +When you run the application, you'll see: +```text +2024-10-28 21:56:42.747 [INFO] pid[87280]: http server started listening on [:8000] +2024-10-28 21:56:42.747 [INFO] {d84db2976ea202187ba40f5d0657e4d7} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-10-28 21:56:42.747 [INFO] {d84db2976ea202187ba40f5d0657e4d7} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | GET | / | main.(*Hello).Say | main.Middleware +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|-------------------- +``` + +Notable changes in the output: +- A link to the Swagger UI at `http://127.0.0.1:8000/swagger/` +- A link to the OpenAPI specification at `http://127.0.0.1:8000/api.json` +- Two additional routes for Swagger and OpenAPI endpoints. The Swagger route uses the Server's `HOOK` feature, which provides more flexible request interception than middleware. The `HOOK_BEFORE_SERVE` point used here is one of several predefined hook points (see the Web Server chapter in our manual for more details). + +### Exploring the Swagger UI + +Visit http://127.0.0.1:8000/swagger/ to see your API documentation: + +![alt text](QQ_1730124029804.png) + +The UI shows: +- Our single API endpoint under the "Test" category with its description "Hello world test case" +- Detailed input/output structures including parameter types, validation rules (like required fields), and descriptions + +## Key Takeaways + +We've learned two important concepts: +1. Structured API definitions make APIs more maintainable, which is crucial for complex projects with many endpoints +2. GoFrame's automatic documentation generation creates standard OpenAPIv3 docs and Swagger UI with minimal effort, significantly reducing maintenance overhead and improving team collaboration \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..5caf8573b45 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" @@ -0,0 +1,167 @@ +--- +slug: '/quick/common-response' +title: 'Response Structure' +hide_title: true +sidebar_position: 7 +keywords: [GoFrame Framework, Unified Response Structure, JSON Format, API Data Structure, Route Callback Function, Middleware Definition, Execution Result, Error Handling, Example Code, API Documentation Generation] +description: "Use the GoFrame framework to unify API response structures to return data in JSON format, define API data structures and route callback functions, handle execution results using middleware, and provide complete example code. By applying these methods, you can achieve a unified data format encapsulation in business projects, simplifying the API documentation generation and maintenance process." +--- + +In this chapter, we'll implement a standardized response format for our API endpoints. All responses, whether successful or not, will be returned in a consistent JSON format. + +## Response Structure + +Let's define our standard response structure: +```go +type Response struct { + Message string `json:"message" dc:"Response message"` + Data interface{} `json:"data" dc:"Response payload"` +} +``` +We've added `json` tags to specify how each field should be serialized in the JSON response. + +## API Contracts +```go +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" json:"name" dc:"User's name"` + Age int `v:"required" json:"age" dc:"User's age"` +} +type HelloRes struct { + Content string `json:"content" dc:"Response content"` +} +``` +- The `HelloRes` struct defines the shape of our successful response data +- All fields include `json` tags for proper serialization + +## Handler Implementation +```go +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} +``` +Instead of directly writing to the response as in previous examples, we now return a structured response using `HelloRes`. + +## Response Middleware + +```go +func Middleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} +``` +This middleware: +- Gets the handler's response using `r.GetHandlerResponse()` (this is the `*HelloRes` returned by our handler) +- Checks for errors using `r.GetError()` (the `error` value returned by our handler) +- Wraps everything in our standard response structure and sends it as JSON + +## Complete Example + +```go title="main.go" +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type Response struct { + Message string `json:"message" dc:"Response message"` + Data interface{} `json:"data" dc:"Response payload"` +} + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" json:"name" dc:"User's name"` + Age int `v:"required" json:"age" dc:"User's age"` +} +type HelloRes struct { + Content string `json:"content" dc:"Response content"` +} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} + +func Middleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(Middleware) + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` + +## Testing the API + +Let's test with valid parameters at http://127.0.0.1:8000/?name=john&age=18: + +![img_3.png](img_3.png) + +And with missing parameters at http://127.0.0.1:8000/: + +![img_5.png](img_5.png) + +## Looking Ahead + +We've successfully implemented a standardized JSON response format, which is crucial for maintaining consistency across large APIs. + +Notice how we've structured everything - from input parameters to response formats - with clear types, descriptions, and validation rules. This structured approach opens up interesting possibilities: could we automatically generate API documentation from these definitions? Indeed we can, and that's exactly what we'll explore in the next chapter. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..09eac17dab2 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" @@ -0,0 +1,74 @@ +--- +slug: '/quick/request-input' +title: 'Parameters Retrieving' +hide_title: true +sidebar_position: 3 +keywords: [GoFrame, GoFrame Framework, Request Parameters, HTTP Parameter Retrieval, Web Server Development, Query String, HTTP Methods, Parameter Handling, ghttp, Go Programming] +description: "Retrieve request parameters submitted by the client in a Web Server using the GoFrame framework, focusing on handling parameters submitted via HTTP methods like Query String, Form, and Body using the r.Get method. Learn how to handle default parameter values and automatic parameter type recognition. Detailed example code demonstrates receiving and processing parameters in GoFrame, and analyzes common issues, laying the foundation for structured processing of parameter objects in later sections." +--- +Now that we've created a basic web server, let's learn how to handle client-submitted parameters. For simplicity, we'll focus on parameters passed via query strings. + +## Handling Parameters + +Let's modify our "Hello World" example to handle user input: + +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writef( + "Hello %s! Your Age is %d", + r.Get("name", "unknown").String(), + r.Get("age").Int(), + ) + }) + s.SetPort(8000) + s.Run() +} +``` + +The `GoFrame` framework makes parameter handling straightforward. In this example, we use the `r.Get` method, which can retrieve parameters from any HTTP method (Query String, Form, Body, etc.). It automatically detects and parses the submission format, including `json` and `xml`. + +Here's the method signature: +```go +func (r *Request) Get(key string, def ...interface{}) *gvar.Var +``` + +The `Get` method takes two parameters: +1. The parameter name to look up +2. An optional default value to use if the parameter is missing + +It returns a `*gvar.Var` object - a versatile type provided by `GoFrame` that can be converted to various data types as needed. + +## Testing the API + +Let's test with parameters at http://127.0.0.1:8000/?name=john&age=18: + +![img.png](img.png) + +And without parameters at http://127.0.0.1:8000/: + +![alt text](QQ_1730178667265.png) + +Notice how the default values kick in: +- When `name` is missing, it defaults to "unknown" +- When `age` is missing, it defaults to 0 (the zero value for integers) + +## Room for Improvement + +While we've successfully handled parameters, there are some issues with this approach: +1. Hardcoding parameter names is error-prone - a simple typo could cause bugs that are hard to track down +2. There's no clear way to document: + - The business purpose of each parameter + - Expected data types + - Validation rules + - Parameter descriptions + +In the next section, we'll solve these problems by introducing structured parameter objects. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..34132349f7a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,87 @@ +--- +slug: '/quick/request-struct' +title: 'Parameters Structure' +hide_title: true +sidebar_position: 4 +keywords: [GoFrame, GoFrame Framework, Request Data Structure, Structured Request, Parameter Mapping, Data Validation, Web Server, Request Object, Hardcoding Parameter Names, API Optimization] +description: "Resolving the hardcoding problem of parameter names through data structuring, this introduces how to define request objects to receive client parameters, achieving parameter mapping and validation through the GoFrame framework to improve code maintainability. Additionally, the example program demonstrates methods to avoid redundant validation logic and explores more concise solutions." +--- + +In this chapter, we'll improve upon our previous example by introducing structured data handling to eliminate hardcoded parameter names. + +## Request Structure + +Let's define a structure to handle client parameters: +```go +type HelloReq struct { + Name string // User's name + Age int // User's age +} +``` +This approach allows us to document our parameters and specify their types explicitly, eliminating the need for hardcoded parameter names. + +## Implementation + +Here's our updated web server implementation: +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + Name string // User's name + Age int // User's age +} + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + var req HelloReq + if err := r.Parse(&req); err != nil { + r.Response.Write(err.Error()) + return + } + if req.Name == "" { + r.Response.Write("name should not be empty") + return + } + if req.Age <= 0 { + r.Response.Write("invalid age value") + return + } + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + }) + s.SetPort(8000) + s.Run() +} +``` + +Key improvements in this version: +- We use `r.Parse` to automatically map request parameters to our struct. This method handles the parameter parsing and assignment based on predefined mapping rules (which we'll cover in detail in the type conversion documentation). +- We've added input validation to ensure both `Name` and `Age` parameters contain valid values. + +## Testing the API + +When we visit http://127.0.0.1:8000/?name=john&age=18, we get the expected response: + +![img.png](img.png) + +Let's test the validation by making a request without parameters to http://127.0.0.1:8000/: + +![img_2.png](img_2.png) + +## Room for Improvement + +While we've made progress by using structured data, there are still several areas we can enhance: +- The `r.Parse` operation is boilerplate code that should be separated from our business logic +- Having to call `r.Parse` in every API handler becomes repetitive +- The manual validation using multiple `if` statements can become unwieldy as the number of parameters grows + +Can we make this code more elegant and maintainable? Absolutely! In the next chapter, we'll explore a more streamlined approach to handle these concerns. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" new file mode 100755 index 00000000000..6ac6eb56956 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" new file mode 100755 index 00000000000..513c6fd5b35 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" new file mode 100755 index 00000000000..b0fd96b1cfd Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" new file mode 100755 index 00000000000..f3e887216c3 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" new file mode 100755 index 00000000000..ec7dd64fccf Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" new file mode 100755 index 00000000000..184c48833eb Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" new file mode 100755 index 00000000000..57f8d3438eb Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" new file mode 100755 index 00000000000..5826c132a2d Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" new file mode 100755 index 00000000000..377ec8f63e4 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" new file mode 100644 index 00000000000..c5322aba6d9 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" @@ -0,0 +1,69 @@ +--- +slug: /quick/scaffold-next +title: "What's Next" +hide_title: true +sidebar_position: 3 +keywords: [GoFrame, web development, microservices, core components, API development, service architecture, configuration management, database integration, learning path, best practices] +description: "Ready to take your GoFrame skills to the next level? This guide outlines your learning path forward, from mastering core components to building production-ready web services and microservices. Learn about essential framework features, best practices, and real-world application development through practical examples." +translator: claude3.5 +--- + +## Quick Review + +Congratulations! By now, you should be familiar with: + +1. Setting up a GoFrame project +2. Understanding application startup flow +3. Grasping the framework's architectural principles +4. Building a basic API service + +## Learning Path + +Let's explore your options for diving deeper into GoFrame. + +### Community Resources + +Our community members have created excellent learning materials to help you succeed. Check out our [Community Tutorials](../../course/社区教程.md) for practical insights and tips. + +### Mastering Service Development + +Most developers use GoFrame for one of two primary purposes: building web applications or microservices. + +#### Web Application Development + +If you're focused on HTTP-based web applications, head over to our [Web Service Development Guide](../../docs/WEB服务开发/WEB服务开发.md). + +This comprehensive guide walks you through web service development and related components in a progressive, easy-to-follow manner. + +#### Microservice Development + +For those interested in building microservices, our [Microservice Development Guide](../../docs/微服务开发/微服务开发.md) is your next stop. + +Learn how to leverage GoFrame's features to build robust, scalable microservices architectures. + +### Framework Components Deep Dive + +Whether you're troubleshooting an unfamiliar component or using GoFrame as a component library, we've got you covered with detailed documentation. + +#### Core Components + +Our core components are essential building blocks used in most projects. They're crucial for mastering the framework. Start here: [Core Components ](../../docs/核心组件/核心组件.md) + +#### Component Directory + +For a complete overview of all available components, check out our [Components Category](../../docs/组件列表/组件列表.md). + +## Learning Through Examples + +The best way to learn is through practical examples. We maintain several reference projects to help you get started: + +- **API Development**: [User Service Demo](https://github.com/gogf/gf-demo-user) + A complete REST API implementation showcasing best practices + +- **Web MVC**: [Chat Application](https://github.com/gogf/gf-demo-chat) + A real-time chat application demonstrating web MVC patterns + +- **Microservices**: [gRPC Demo](https://github.com/gogf/gf-demo-grpc) + Example microservice implementation using gRPC + +- **More Examples**: Browse our [Awesome GoFrame](https://github.com/gogf/awesome-gf) collection for additional community projects and resources \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..aba0631ce81 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" @@ -0,0 +1,167 @@ +--- +slug: '/quick/scaffold-index' +title: 'Introduction' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame, project scaffolding, CLI tools, project setup, web development, API documentation, Swagger integration, development workflow, framework installation, version management] +description: "Master GoFrame's project scaffolding to quickly build robust business applications. Learn essential steps from installing the framework tools to creating and running your first project, complete with HTTP server setup and API documentation. This guide covers everything you need to kickstart your GoFrame development journey." +translator: claude3.5 +--- + +In this chapter, we'll walk you through building a complete API project using GoFrame's scaffolding tools. + +:::warning +**Important Note:** GoFrame is a professional-grade, engineered development framework for Go that's thoughtfully designed, easy to use, and backed by comprehensive documentation and an active community. **Once you experience the productivity benefits of GoFrame, you'll never want to go back to manual development**. + +If you encounter any challenges while following this quick start guide, feel free to drop a comment in the discussion section 💬. We're here to help and will provide solutions as quickly as possible 🌟🌟. +::: + + +## Installing the Framework Tools + +GoFrame provides a suite of development tools that streamline your workflow with convenient commands for project scaffolding, code generation, and framework updates. You can download these tools from: [https://github.com/gogf/gf/releases](https://github.com/gogf/gf/releases) + +### Pre-compiled Installation + +We offer pre-compiled binaries hosted on GitHub for immediate use across different platforms. + +#### macOS Installation + +```bash +wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf +``` + +Common installation issues and solutions: + +- If `wget` is missing, install it using `brew install wget` +- 🔥 For `zsh` users: There might be a conflict with the `gf` alias (git fetch). After installation, **restart your terminal** to resolve this +- Missing Go environment variables: Ensure your Go development environment is properly set up by following our [Environment Setup Guide](../../docs/其他资料/准备开发环境/准备开发环境.md) +- For other issues, try the Source Code Installation method below + +#### Linux Installation + +```bash +wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf +``` + +If `wget` is not installed: +- `Ubuntu/Debian:` `apt-get install wget -y` +- `CentOS/RedHat:` `yum install wget -y` + + +#### Windows Installation + +Download the appropriate binary file and run it. If the installation fails, use the Source Code Installation method instead. + +### Source Code Installation + +For a universal installation method, compile from source using `go install`: + +```bash +go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +### Verifying Your Installation + +Run `gf -v` to verify the installation. A successful installation will show output similar to this: + +```html +$ gf -v +v2.7.4 +Welcome to GoFrame! +Env Detail: + Go Version: go1.23.1 darwin/arm64 + GF Version(go.mod): cannot find go.mod +CLI Detail: + Installed At: /Users/john/go/bin/gf + Built Go Version: go1.23.1 + Built GF Version: v2.7.4 +Others Detail: + Docs: https://goframe.org + Now : 2024-10-29T13:30:30+08:00 +``` + +Note: The `Go/GF Version` shows the versions used to build the binary, while `GoFrame Version` indicates the framework version used in your current project (detected from `go.mod`). + +:::info +Important Note: 🔥 If you're using `zsh`, remember to restart your terminal after installation to resolve any potential `gf` alias conflicts with `git fetch`. +::: + +## Creating a Project + +```bash +gf init demo -u +``` + +This command creates a project scaffold named `demo`. The `-u` flag updates the project's GoFrame dependencies to their latest versions. GoFrame uses a unique project structure optimized for various development scenarios. For detailed information about the project structure, see: [Project Structure Guide🔥](../../docs/框架设计/工程开发设计/工程目录设计.md). + +The scaffold provides a versatile structure suitable for web applications, CLI tools, and microservices. By default, it generates an HTTP Web Server template. After understanding the directory structure, feel free to remove any unnecessary directories. + +![](/markdown/4590d75ced1c7976fb64103d7b543758.png) + +## Running Your Project + +Start your project with this command: + +```bash +cd demo && gf run main.go +``` + +The `gf run` command enables hot-reloading during development. You can also use standard `go run` if preferred. + +You'll see output similar to: + +```text +$ cd demo && gf run main.go +build: main.go +go build -o ./main main.go +./main +build running pid: 76159 +2022-08-22 12:20:59.058 [INFO] swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2022-08-22 12:20:59.058 [INFO] openapi specification is serving at address: http://127.0.0.1:8000/api.json +2022-08-22 12:20:59.059 [INFO] pid[76159]: http server started listening on [:8000] + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | demo/internal/controller.(*cHello).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- +``` + +By default, your application will: +- Run on port `8000` +- Enable `OpenAPI` documentation +- Provide a `Swagger UI` interface +- Display all route information in the terminal +- Include a sample `/hello` endpoint + +Try accessing: [http://127.0.0.1:8000/hello](http://127.0.0.1:8000/hello) + +![](/markdown/b5926140d8b840d44e15996bd019677a.png) + +Explore the `API documentation` through `Swagger UI`: + +![](/markdown/e59aa12576f6d575b2abf0fb8ebbf19d.png) + +## Upgrading the Framework + +To update to the latest framework version, run this command in your project's root directory (where `go.mod` is located): + +```bash +gf up -a +``` + + +## Chapter Summary + +In this chapter, you've learned how to: +- Install the GoFrame CLI tools +- Create a new project using the scaffold +- Run and test your application + +Next, we'll explore the project startup process in detail. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" new file mode 100755 index 00000000000..65776d79096 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" new file mode 100755 index 00000000000..089282cef42 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" new file mode 100755 index 00000000000..5b93b250648 Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" new file mode 100755 index 00000000000..70854700f7c Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" new file mode 100755 index 00000000000..c194d7b1c3a Binary files /dev/null and "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" differ diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" new file mode 100644 index 00000000000..c9a6dc3f268 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" @@ -0,0 +1,74 @@ +--- +slug: '/quick/scaffold-api-sql' +title: 'Step1 - Design Data Structure' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame, Data Table Design, MySQL Database, Docker, SQL Statements, InnoDB, Auto Increment, varchar, Database Connection, User Status] +description: "Design and apply MySQL data tables. We define the structure of data tables for user information and practice operations by running MySQL with Docker, including creating table structures and applying SQL statements, helping you quickly master database operation skills." +--- + + +## Design Data Structure SQL + +First, we define a data table. The following is the `SQL` file of the data table to be used in this chapter's example: + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id', + `name` varchar(45) DEFAULT NULL COMMENT 'user name', + `status` tinyint DEFAULT NULL COMMENT 'user status', + `age` tinyint unsigned DEFAULT NULL COMMENT 'user age', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +## Apply Data Structure SQL + +We need to apply this data table to the `mysql` database for subsequent use. If you don't have a `mysql` database service locally, you can use `docker` to run one: + +```bash +docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + loads/mysql:5.7 +``` + +After starting, connect to the database and apply the table creation `sql` statements: +```text +$ mysql -h127.0.0.1 -p3306 -uroot -p +mysql: [Warning] Using a password on the command line API can be insecure. +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 57 +Server version: 9.0.1 Homebrew + +Copyright (c) 2000, 2024, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> use test; +Database changed +mysql> CREATE TABLE `user` ( + -> `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id', + -> `name` varchar(45) DEFAULT NULL COMMENT 'user name', + -> `status` tinyint DEFAULT NULL COMMENT 'user status', + -> `age` tinyint unsigned DEFAULT NULL COMMENT 'user age', + -> PRIMARY KEY (`id`) + -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +Query OK, 0 rows affected, 2 warnings (0.02 sec) + +mysql> +``` + + + +## Learning Summary + +It is a good development practice to design database tables before API development. Here we are using the `mysql` database, which requires setting up/running the database service first. + +After designing the database table, we can use a scaffold tool to automatically generate corresponding database operation-related files in the next step. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" new file mode 100644 index 00000000000..89620fe3827 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" @@ -0,0 +1,156 @@ +--- +slug: '/quick/scaffold-api-gen-dao' +title: 'Step2 - Generate dao/do/entity' +hide_title: true +sidebar_position: 2 +keywords: [GoFrame,CLI Tool,Data Access Object,Automated Generation,Data Model,Database Configuration,Make Command,Data Transformation,Code Generation,ORM Component] +description: "Use the scaffolding tool in the GoFrame framework for automated data access object generation, ensure the CLI tool is configured correctly, then execute the code generation through commands, and generate corresponding dao, do, and entity files after creating the database tables to simplify CRUD operations. It demonstrates the specific file structure and usage, as well as the principles of generating different types of files." +--- + +## CLI Tool Configuration + +Before using the scaffolding tool, please check whether the local `cli` tool configuration is correct. The default configuration is as follows: + +```yaml title="hack/config.yaml" + +# CLI tool, only in development environment. +# https://goframe.org/docs/cli +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + descriptionTag: true + + docker: + build: "-a amd64 -s linux -p temp -ew" + tagPrefixes: + - my.image.pub/my-app +``` + +- The `dao` section configuration is the configuration for the command to be executed this time, where `link` is the database configuration required for connection. `descriptionTag` indicates adding field descriptions to the `description` tag in the generated `entity` code file. If the `entity` object of the data table is used in the API `api` definition, this tag can serve as a parameter description. We need to change this `link` configuration to our database connection address. For a detailed introduction to the `link` configuration item, please refer to the section [ORM Configuration - File](../../../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置-配置文件.md). +- The `docker` configuration item is a configuration template provided by default, used for image compilation. It is not explained in detail here, but those interested can refer to the development manual's relevant sections on development tools. + +## Execute Code Generation + +After creating the data tables, we execute `make dao` in the project's root directory to automatically generate the corresponding `dao/do/entity` files. + +```text +$ make dao +generated: /Users/john/Temp/demo/internal/dao/user.go +generated: /Users/john/Temp/demo/internal/dao/internal/user.go +generated: /Users/john/Temp/demo/internal/model/do/user.go +generated: /Users/john/Temp/demo/internal/model/entity/user.go +done! +``` + +:::tip +The `make` command is a directive provided by default under `*nix` systems, including `Linux/MacOS`. If on other systems such as `Windows`, which do not support the `make` command by default, you can use the `gf gen dao` command as a replacement. +::: + +![goframe dao, do, entity](QQ_1731806701346.png) + +Each table will generate three types of `Go` files: +- `dao`: Access the underlying data source through object methods, implemented based on the `ORM` component. +- `do`: Data transformation model for converting business models to data models, maintained by the tool and cannot be modified by users. The tool will overwrite this directory each time code files are generated. +- `entity`: Data model, maintained by the tool and cannot be modified by users. The tool will overwrite this directory each time code files are generated. + +For a more detailed introduction, please refer to the section [Dao/Do/Entity Generating](../../../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + +:::info +The code generated by the scaffolding tool includes a top-level file comment. If the file comment contains the description `Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.`, it means the file is maintained by the scaffolding tool and will be overwritten with each code generation. +::: + +### dao + +Two `dao` files are generated: +- `internal/dao/internal/user.go` is used to wrap access to the data table `user`. This file automatically generates some data structures and methods to simplify `CRUD` operations on the data table. This file is overwritten each time it is generated and is automatically maintained by the development tools, so developers do not need to worry about it. +- `internal/dao/user.go` is actually a further encapsulation of `internal/dao/internal/user.go`, used for direct access by other modules. Developers can modify this file as desired or extend the capabilities of `dao`. + +Since the generated `internal/dao/internal/user.go` file is completely maintained by the development tools, we only need to care about this generated source file `internal/dao/user.go`, and make any functional extensions if needed. + +Sample source code: https://github.com/gogf/quick-demo/blob/main/internal/dao/user.go + +```go title="internal/dao/user.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package dao + +import ( + "demo/internal/dao/internal" +) + +// internalUserDao is internal type for wrapping internal DAO implements. +type internalUserDao = *internal.UserDao + +// userDao is the data access object for table user. +// You can define custom methods on it to extend its functionality as you wish. +type userDao struct { + internalUserDao +} + +var ( + // User is globally public accessible object for table user operations. + User = userDao{ + internal.NewUserDao(), + } +) + +// Fill with you ideas below. +``` + +### do + +Sample source code: https://github.com/gogf/quick-demo/blob/main/internal/model/do/user.go + +```go title="internal/model/do/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// User is the golang structure of table user for DAO operations like Where/Data. +type User struct { + g.Meta `orm:"table:user, do:true"` + Id interface{} // user id + Name interface{} // user name + Status interface{} // user status + Age interface{} // user age +} +``` + +The usage of the `do` data transformation model will be demonstrated in subsequent code logic. + +### entity + +Sample source code: https://github.com/gogf/quick-demo/blob/main/internal/model/entity/user.go + +```go title="internal/model/entity/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +// User is the golang structure for table user. +type User struct { + Id uint `json:"id" orm:"id" description:"user id"` // user id + Name string `json:"name" orm:"name" description:"user name"` // user name + Status int `json:"status" orm:"status" description:"user status"` // user status + Age uint `json:"age" orm:"age" description:"user age"` // user age +} +``` + +We can see that this `entity` data structure definition corresponds directly to the data table fields. + +## Learning Summary + +It can be felt that using the convenient scaffolding tools of the `GoFrame` framework, we are liberated from some repetitive coding labor, greatly improving production efficiency. Operations on the database will become very simple. + +In the next step, we will design the `CRUD` API, to see how to quickly define an API in the `GoFrame` framework. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" new file mode 100644 index 00000000000..d7b15f4dc7a --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" @@ -0,0 +1,115 @@ +--- +slug: '/quick/scaffold-api-definition' +title: 'Step3 - Write API Definition' +hide_title: true +sidebar_position: 3 +keywords: [GoFrame, API Definition, RESTful, HTTP Method, Versioning, Parameter Validation, User Management, Metadata Management, Data Return, Golang] +description: "Define CRUD APIs in the project's api directory, adopting a RESTful style of API design, using HTTP Method to standardize API requests. API definitions use g.Meta to manage metadata information, including route address, request method, and API description. The request parameters and return data structures define detailed parameter validation rules. Starting with version v1 for API version control to maintain future compatibility. APIs flexibly receive parameters to meet the diverse needs of API requests." +--- + +In the `api` directory of the project, we begin defining our `CRUD` APIs. +- We use the `RESTful` style for API design, fully utilizing the `GET/POST/PUT/DELETE` HTTP Methods. This standardized design results in very elegant APIs. +- Similarly, we start with version `v1`. Using version numbers is a good development habit, which helps in maintaining compatibility in future APIs. + +![user api definition](QQ_1732094808338.png) + +## `Create` +```go title="api/user/v1/user.go" +type CreateReq struct { + g.Meta `path:"/user" method:"post" tags:"User" summary:"Create user"` + Name string `v:"required|length:3,10" dc:"user name"` + Age uint `v:"required|between:18,200" dc:"user age"` +} +type CreateRes struct { + Id int64 `json:"id" dc:"user id"` +} +``` +Brief Introduction: +- In API definitions, `g.Meta` is used to manage API metadata information, which are defined as tags on the `g.Meta` property. These metadata include `path` (route address), `method` (request method), `tags` (API group for generating API documentation), and `summary` (API description). These metadata are part of `OpenAPIv3`, which we won't go into detail here. For those interested, refer to the chapter: [API Document - OpenAPIv3](../../../docs/WEB服务开发/接口文档/接口文档-OpenAPIv3.md). +- The `Name` and `Age` attributes here are the parameter definitions for our API. The `dc` tag is a shorthand for `description`, indicating the meaning of the parameter; the `v` tag is a shorthand for `valid`, indicating the validation rules for the parameter. We use three built-in validation rules here: + - `required`: The parameter is mandatory. + - `length`: Validates the parameter's length. + - `between`: Validates the parameter's range. + Learn about these here, and refer to the section [Data Validation - Rules](../../../docs/核心组件/数据校验/数据校验-校验规则.md) for more validation rules. +- The request parameter structure `CreateReq` does not specify parameter reception methods because the `GoFrame` framework supports very flexible parameter reception methods, automatically recognizing `Query String/Form/Json/Xml` submission methods and mapping the submitted parameters to the request parameter receiving objects. +- Only the return parameter structures have `json` tags because the returned data usually needs to be converted to `json` format for use by the frontend, and parameter naming in `snake` style is more in line with frontend naming conventions. + +:::tip +In a `RESTful` style API design, we typically use `POST` from the `HTTP Method` to denote write operations and `PUT` to denote update operations. +::: + +## `Delete` + +```go title="api/user/v1/user.go" +type DeleteReq struct { + g.Meta `path:"/user/{id}" method:"delete" tags:"User" summary:"Delete user"` + Id int64 `v:"required" dc:"user id"` +} +type DeleteRes struct{} +``` + +The route tag `path` uses `/user/{id}`, where `{id}` indicates a field-matching route, passed through the `URL Path`, with the parameter name `id`. As seen, we define an `Id` parameter in the request parameter object, and the `id` parameter from the route will **map directly without case sensitivity** to this `Id`. + +For example: In the route `/user/1`, the `id` parameter value is `1`; in the route `/user/100`, the `id` parameter value is `100`. + +## `Update` + +```go title="api/user/v1/user.go" +// Status marks user status. +type Status int + +const ( + StatusOK Status = 0 // User is OK. + StatusDisabled Status = 1 // User is disabled. +) + +type UpdateReq struct { + g.Meta `path:"/user/{id}" method:"put" tags:"User" summary:"Update user"` + Id int64 `v:"required" dc:"user id"` + Name *string `v:"length:3,10" dc:"user name"` + Age *uint `v:"between:18,200" dc:"user age"` + Status *Status `v:"in:0,1" dc:"user status"` +} +type UpdateRes struct{} +``` + +Here: +- We define a user status type `Status`, using the conventional `enums` definition style in `Golang`. Just for understanding here. +- The validation for the `Status` parameter uses the `in:0,1` rule, which checks that the passed `Status` value must be one of the two constants we defined, `StatusOK/StatusDisabled`, i.e., `0/1`. +- The API parameters use pointers to avoid default type values affecting our update API. For example, if `Status` is not defined as a pointer, it will be affected by the default value `0`. During processing logic, it's hard to determine whether the caller has passed the parameter and whether to actually change the value to `0`. By using pointers, when users don't pass the parameter, its default value is `nil`, making it easy to judge in processing logic. + +## `GetOne` + +```go title="api/user/v1/user.go" +type GetOneReq struct { + g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"Get one user"` + Id int64 `v:"required" dc:"user id"` +} +type GetOneRes struct { + *entity.User `dc:"user"` +} +``` + +Here, the return result uses the `*entity.User` structure, which was generated by the `make dao` command earlier. This data structure corresponds to the database table fields one-to-one. + +## `GetList` + +```go title="api/user/v1/user.go" +type GetListReq struct { + g.Meta `path:"/user" method:"get" tags:"User" summary:"Get users"` + Age *uint `v:"between:18,200" dc:"user age"` + Status *Status `v:"in:0,1" dc:"user age"` +} +type GetListRes struct { + List []*entity.User `json:"list" dc:"user list"` +} +``` +This API can query by `Age` and `Status`, returning multiple records `List []*entity.User`. + +## Learning Summary + +Sample code for this chapter: https://github.com/gogf/quick-demo/blob/main/api/user/v1/user.go + +As shown, defining `api` APIs in the `GoFrame` framework's scaffolding project is quite elegant, with support for automatic data validation, metadata injection, flexible route configuration, and other practical features. This method of API definition allows for the automatic generation of API documentation, where the code serves as documentation, ensuring consistency between code and documentation. + +Moreover, this is not the full charm of `GoFrame`, just a single petal on the rose. Next, we will use scaffold tools to automatically generate the corresponding `controller` control code for us. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" new file mode 100644 index 00000000000..f6e701247db --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" @@ -0,0 +1,149 @@ +--- +slug: '/quick/scaffold-api-controller' +title: 'Step4 - Generate Controller' +hide_title: true +sidebar_position: 4 +keywords: [GoFrame, API Code Generation, Controller Code, Generate Controller, Code Auto Generation, API Implementation, GoFrame Framework, Route Object Management, API Route Implementation, Code Template] +description: "Generate controller code based on API definition, including API API files, route object management, and route implementation code, using GoFrame framework's command tool to quickly generate relevant code templates, ensuring complete implementation of the API, and demonstrating how to implement specific business logic through files." +--- + +## Generate Codes From API Definition + +Once the `api` definition is completed, we generate the controller code through the `make ctrl` command (or `gf gen ctrl`). + +```text +$ make ctrl +generated: /Users/john/Temp/demo/api/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user_new.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_create.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_update.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_delete.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_one.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_list.go +done! +``` + +![goframe api API controller](QQ_1731678085194.png) + +The generated codes mainly include `3` types of files. + +## Abstraction API For API + +Defines the `api API` to ensure the completeness of the controller's API implementation, avoiding issues of missing API implementations in `controller`. Since `GoFrame` is a rigorous development framework, it controls such details well. Whether or not this feature is used by developers can be decided based on specific scenarios and needs. + +```text +/Users/john/Temp/demo/api/user/user.go +``` + +This file is maintained by the development tool, and developers do not need to worry about it. + +Content is as follows: +```go title="api/user/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package user + +import ( + "context" + + "demo/api/user/v1" +) + +type IUserV1 interface { + Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) + Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) + Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) + GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) + GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) +} +``` + +## Controller Router Object + +Used to manage the initialization of the controller, as well as data structures and constant definitions used internally by the control. + +```text +generated: /Users/john/Temp/demo/internal/controller/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user_new.go +``` + +Among them, `internal/controller/user/user.go` is an empty source file, which can be used to define data structures, constants, and other content used by the controller internally. +```go title="internal/controller/user/user.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package user + +``` + +Another file `internal/controller/user/user_new.go` is an auto-generated route object creation file. +```go title="internal/controller/user/user_new.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package user + +import ( + "demo/api/user" +) + +type ControllerV1 struct{} + +func NewV1() user.IUserV1 { + return &ControllerV1{} +} +``` +Both of these files will only be generated once, after which developers can freely modify and extend them. + +:::tip +If later we need to define a `v2` API, the `make ctrl` command will similarly generate a `type ControllerV2 struct{}` structure definition and a `func NewV2() user.IUserV2` initialization method. +::: + +## Controller Router Implementation + +Used for the implementation code files of specific `api` APIs. By default, codes are generated in the form of one source file per `api` API. Of course, it is also possible to control the aggregation of APIs defined in `api` files into a corresponding single source file. For specific command introductions and configurations, please refer to the chapter [Controller Generating](../../../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md). + +```text +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_create.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_update.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_delete.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_one.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_list.go +``` + +Let's open one of the files to view the generated code template: + +```go title="internal/controller/user/user_v1_create.go" +package user + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "demo/api/user/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + return nil, gerror.NewCode(gcode.CodeNotImplemented) +} +``` +As we can see, this is just the implementation template for the create API we defined. We just need to complete the specific business logic of this route function. + +## Learning Summary + +Example source code for this chapter: https://github.com/gogf/quick-demo/tree/main/internal/controller/user + +The `GoFrame` scaffolding tool helps us generate all the code unrelated to business logic. We only need to focus on the implementation of the business logic. Moreover, these auto-generated code files are mostly fully maintained by the tool, except for a few code files that developers can extend, thus we do not need to worry about their future maintenance. + +Yes, the goal of the `GoFrame` scaffolding tool is to allow developers to focus on the business logic itself, while all work other than business logic is handled by the development framework and scaffolding tool. + +Such a development method is extremely convenient, not too comfortable! Do you think that’s all? Of course not, in the Quick Start chapter we only introduce some beginner-level features. When you delve deeper into her, you will discover more of her goodness and her understanding nature. + +Next, we will complete the business logic implementation of the API and feel the charm of the `GoFrame` database `ORM` component. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" new file mode 100644 index 00000000000..2c31a4d3e9c --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" @@ -0,0 +1,161 @@ +--- +slug: '/quick/scaffold-api-implements' +title: 'Step5 - Implement API Logic' +hide_title: true +sidebar_position: 5 +keywords: [GoFrame, CRUD Logic, API Creation, Parameter Validation, Update API, Delete API, Retrieve API, Database Operations, Scaffold Tool, Business Logic Implementation] +description: "Use GoFrame framework to complete API logic implementation. By using the project scaffolding, pre-generate code unrelated to project business logic and focus on business logic implementation. The specific implementation process of CRUD operations, including creation, update, deletion, and retrieval of APIs, is introduced. It elaborates on parameter validation, form data insertion and update, intelligent data mapping, validation mechanism, and efficiently utilizing GoFrame framework functionalities during data operations." +--- + +As you can see, with the project scaffold tool, a lot of code unrelated to the project's business logic has already been pre-generated, so we only need to focus on the business logic implementation. Let's take a look at how to implement the specific `CRUD` logic. + +## `Create` + +### Implementation of Creation Logic +```go title="internal/controller/user/user_v1_create.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + insertId, err := dao.User.Ctx(ctx).Data(do.User{ + Name: req.Name, + Status: v1.StatusOK, + Age: req.Age, + }).InsertAndGetId() + if err != nil { + return nil, err + } + res = &v1.CreateRes{ + Id: insertId, + } + return +} +``` +In the `Create` implementation method: +- We use `dao.User` through the `dao` component to operate the `user` table. +- Each `dao` operation requires passing the `ctx` parameter, so we create a `gdb.Model` object with the `Ctx(ctx)` method. This object is the framework's model object used to operate specific data tables. +- We use `Data` to pass the data that needs to be written to the data table. Here, we use the `do` conversion model object to input our data. The `do` conversion model automatically filters `nil` data and converts it to the corresponding data table field type at a lower level. In most cases, we use the `do` conversion model to pass write/update parameters, query conditions, and other data to the database operation object. +- The `InsertAndGetId` method writes the `Data` parameters into the database and returns the newly created record's primary key `id`. + +### Parameter Validation Implementation + +Hold on, you might wonder why there is no validation logic here. This is because the validation logic is already configured on the request parameter object `CreateReq`. Remember the `v` tag introduced earlier? Let's take another look at this request parameter object: +```go title="api/user/v1/user.go" +type CreateReq struct { + g.Meta `path:"/user" method:"put" tags:"User" summary:"Create user"` + Name string `v:"required|length:3,10" dc:"user name"` + Age uint `v:"required|between:18,200" dc:"user age"` +} +type CreateRes struct { + Id int64 `json:"id" dc:"user id"` +} +``` +The `required/length/between` validation rules here are automatically executed by the `GoFrame` framework's `Server` before calling the route function `Create`. +If the request parameter validation fails, an error will be returned immediately, and the route function will not be executed. This mechanism of the `GoFrame` framework greatly simplifies the development process, +developers only need to focus on the business logic implementation in this route function. +:::info +Of course, if there are some additional, customized business logic validations, they need to be implemented in the route function by yourself. +::: +## `Delete` + +```go title="internal/controller/user/user_v1_delete.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" +) + +func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { + _, err = dao.User.Ctx(ctx).WherePri(req.Id).Delete() + return +} +``` +The deletion logic is relatively straightforward. Here, we use a `WherePri` method, which takes the given parameter `req.Id` as the primary key for `Where` condition restriction. + +## `Update` + +```go title="internal/controller/user/user_v1_update.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Name: req.Name, + Status: req.Status, + Age: req.Age, + }).WherePri(req.Id).Update() + return +} +``` +The update API is also straightforward. Besides the already introduced `WherePri` method, it also requires using the `Data` method to pass the data to be updated when updating the data. + +## `GetOne` + +```go title="internal/controller/user/user_v1_get_one.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" +) + +func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) { + res = &v1.GetOneRes{} + err = dao.User.Ctx(ctx).WherePri(req.Id).Scan(&res.User) + return +} +``` +In the data retrieval API, we use a `Scan` method, which can intelligently map the retrieved single data table record to a structure object. It should be noted that the `User` attribute object in `&res.User` is actually not initialized and its value is `nil`. If data is retrieved, the `Scan` method will initialize and assign it. If no data is retrieved, the `Scan` method will do nothing, and its value will remain `nil`. + +## `GetList` + +```go title="internal/controller/user/user_v1_get_list.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) { + res = &v1.GetListRes{} + err = dao.User.Ctx(ctx).Where(do.User{ + Age: req.Age, + Status: req.Status, + }).Scan(&res.List) + return +} +``` +When retrieving list data, we also use the `Scan` method, which is very powerful. Like retrieving single data's logic, it only initializes `&res.List` when data is retrieved. + +## Learning Summary + +Example source code for this chapter: https://github.com/gogf/quick-demo/tree/main/internal/controller/user + +As you can see, using the `GoFrame` database `ORM` component can very quickly and efficiently complete API development work. Throughout the `CRUD` API development, the business logic that developers need to implement requires only a few lines of code😼. + +The improvement in development efficiency is not only due to the scaffold tool automatically generating the `dao` and `controller` code but also thanks to the powerful database `ORM` component. As you can see, when we operate the database table, the code is very concise and elegant, but the internal design of the database `ORM` component involves a lot of fine design, strict code testing, and the result of years of functional iteration. + +With the API logic development completed, in the next step, we need to perform some database configuration and route registration operations, which is also very simple. Let's take a look together. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" new file mode 100644 index 00000000000..4c3176d8392 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" @@ -0,0 +1,107 @@ +--- +slug: '/quick/scaffold-api-config-and-route' +title: 'Step6 - Configuration' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame, GoFrame Framework, Database Driver, MySQL, Routing, Configuration, API, Server Configuration, Log Module, Business Module] +description: "Introduce MySQL database driver, with steps for adding database configuration and route registration. Detailed explanation of configurations in the scaffold project template, including tool configuration and business configuration, and how to modify configuration files." +--- + +## Import Database Driver + +The `GoFrame` database component uses an API-based design, separating API and implementation to provide better abstraction and extensibility. Here, we use the `MySQL` database, so we need to import the specific `MySQL` driver implementation. We can add `_ "github.com/gogf/gf/contrib/drivers/mysql/v2"` in `main.go`. + +Sample source code: https://github.com/gogf/quick-demo/blob/main/main.go + +```go title="main.go" +package main + +import ( + _ "demo/internal/packed" + + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "demo/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +You only need to introduce the database driver once in the project; no further adjustments are needed later. For more support and detailed introduction on database drivers, please refer to the section [Database ORM🔥](../../../docs/核心组件/数据库ORM/数据库ORM.md). + +If you execute database operations without introducing the database driver, the Database ORM component will report the following error message: +```text +cannot find database driver for specified database type "mysql", did you misspell type name "mysql" or forget importing the database driver? possible reference: https://github.com/gogf/gf/tree/master/contrib/drivers +``` + +:::tip +In the scaffold project template `main.go`, the `import` statement contains `_ "demo/internal/packed"`, indicating the resource management of the `GoFrame` framework, which is an advanced feature. This feature allows any resource to be packaged into the binary file, so when publishing, only one binary file needs to be released. We do not use this feature here, so just understand it for now, and if you're interested, you can check out the relevant section in the development manual later. +::: + +## Database Configuration + +There are mainly two configuration files in the scaffold project template. + +### CLI Tool Configuration `hack/config.yaml` +Introduced in previous sections, this configuration file is mainly used for local development. When the `cli` scaffold tool executes, it will automatically read the configuration content in it. + +Sample source code: https://github.com/gogf/quick-demo/blob/main/hack/config.yaml + +### Business Configuration `manifest/config/config.yaml` +Mainly maintains configuration information for business project components and business modules, entirely maintained by developers. This configuration file is read when the program starts. The default business configuration provided by the scaffold project template is as follows: +```yaml title="manifest/config/config.yaml" +# https://goframe.org/docs/web/server-config-file-template +server: + address: ":8000" + openapiPath: "/api.json" + swaggerPath: "/swagger" + +# https://goframe.org/docs/core/glog-config +logger: + level : "all" + stdout: true + +# https://goframe.org/docs/core/gdb-config-file +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +``` + +The default provides `3` component configurations: +- `server`: Configuration for the `Web Server`. The default listening address is `:8000`, and the API documentation feature is enabled. +- `logger`: Configuration for the default log component. The log level is set to output all logs, and all logs will be printed to standard output. +- `database`: Configuration for the database component. This is just a template; we need to modify the connection address based on the actual situation. + +Each component configuration has a reference link to the official documentation for configuration. Here, we need to modify the connection information in the database configuration to match the actual usable connection information. For detailed introduction on database configuration, please refer to the section: [ORM Configuration - File](../../../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置-配置文件.md) + +Sample source code: https://github.com/gogf/quick-demo/blob/main/manifest/config/config.yaml + +## Add Route Registration + +Adding our newly filled `API` to the route is very simple, as follows: + +![goframe route registration](QQ_1731680426319.png) + +In the `group.Bind` method of the grouped routes, you can add our route object through `user.NewV1()`. + +Sample source code: https://github.com/gogf/quick-demo/blob/main/internal/cmd/cmd.go + +At this point, our API development is complete. The next step is to start the service and perform some API testing to see the effect. + +## Learning Summary + +When we use database functionality, we need to introduce the specific database driver. The `GoFrame` official repository provides driver implementations for commonly used databases in the form of community components. Our program mainly uses the business configuration, and we need to modify the database connection address within it to the address of the database we set up. + +Route registration is very simple; just add a `controller` object to the group route registration through `group.Bind`. + +So far, we have completed the development of the `CRUD` API. 👏👏 We mainly focused on the following tasks: +- Database table design +- `API` API definition +- Implementation of API business logic +- Simple configuration and route registration + +Next, let's start the program to see the effect. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..042f2047921 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" @@ -0,0 +1,143 @@ +--- +slug: '/quick/scaffold-api-run-and-test' +title: 'Step7 - Run and Test' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame, start service, API documentation, API testing, RESTful API, CRUD, API validation, data query, data modification, data deletion] +description: "Start the service through the command line and generate API documentation using Swagger. Supports RESTful API APIs for creating users, querying user information, modifying user data, and deleting users. Also supports API testing using the curl command, providing detailed validation rules and error codes to ensure data accuracy and reliability." +--- + +## Start the Service + +We can use the command line or the `IDE`'s built-in start tool to start the service. To simplify the example, we directly use `go run main.go` in the command line to start our service: + +```text +$ go run main.go +2024-11-16 16:40:07.394 [INFO] pid[39511]: http server started listening on [:8000] +2024-11-16 16:40:07.394 [INFO] {18594fad2e66081870e88c6e1440060b} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-16 16:40:07.394 [INFO] {18594fad2e66081870e88c6e1440060b} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | demo/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /user | demo/internal/controller/user.(*ControllerV1).GetList | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | POST | /user | demo/internal/controller/user.(*ControllerV1).Create | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | DELETE | /user/{id} | demo/internal/controller/user.(*ControllerV1).Delete | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /user/{id} | demo/internal/controller/user.(*ControllerV1).GetOne | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | PUT | /user/{id} | demo/internal/controller/user.(*ControllerV1).Update | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- +``` + +As we can see, the `CRUD` APIs we developed have been successfully registered on the `Web Server` and displayed correctly on the terminal. Meanwhile, we have enabled the API documentation feature, so let's look at the auto-generated API documentation. + +## API Documentation + +According to the address printed in the terminal, we visit: http://127.0.0.1:8000/swagger/ + +![goframe api swagger](QQ_1731747246720.png) + +:::tip +Automated API documentation generation is one of the very powerful features provided by the `GoFrame` framework. We won't go into detail here, but those interested can refer to the section: [API Document](../../../docs/WEB服务开发/接口文档/接口文档.md) +::: + +## API Testing + +To simplify testing operations, we use the `curl` command for testing and submit and receive return parameters in `json` format. + +:::tip +The `$` symbol at the front of the execution commands below represents the terminal command line tool's prompt and is not a part of the command we enter. Different terminal command lines have different prompt symbols. +::: + +### `Create` + +A creation request needs to be submitted using the `POST` method. + +```bash +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"john","age":20}' +{"code":0,"message":"","data":{"id":1}} + +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"alice","age":18}' +{"code":0,"message":"","data":{"id":2}} +``` +The returned `code` of `0` indicates that the execution was successful. + +Let's construct requests that do not meet the validation rules and see the effect: + +```bash +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"smith","age":16}' +{"code":51,"message":"The Age value `16` must be between 18 and 200","data":null} + +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"sm","age":18}' +{"code":51,"message":"The Name value `sm` length must be between 3 and 10","data":null} +``` + +As we can see, the validation rules have taken effect and returned specific validation error messages. The error code `51` is a built-in error code in the framework, indicating a validation error. Developers can also customize error codes. For more information on error codes, please refer to the relevant sections of the developer's manual. + + +### `GetOne` +```bash +$ curl -X GET 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":{"id":1,"name":"john","status":0,"age":20}} + +$ curl -X GET 'http://127.0.0.1:8000/user/2' +{"code":0,"message":"","data":{"id":2,"name":"alice","status":0,"age":18}} +``` + +### `GetList` + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user' +{"code":0,"message":"","data":{"list":[{"id":1,"name":"john","status":0,"age":20},{"id":2,"name":"alice","status":0,"age":18}]}} + +$ curl -X GET 'http://127.0.0.1:8000/user?age=18' +{"code":0,"message":"","data":{"list":[{"id":2,"name":"alice","status":0,"age":18}]}} +``` + +### `Update` + +An update request needs to be submitted using the `PUT` method. + +```bash +$ curl -X PUT 'http://127.0.0.1:8000/user/1' -d '{"age":26}' +{"code":0,"message":"","data":null} +``` + +After a successful execution, we query the corresponding data to see if it has been successfully modified: + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":{"id":1,"name":"john","status":0,"age":26}} +``` + +### `Delete` + +A deletion request needs to be submitted using the `DELETE` method. + +```bash +$ curl -X DELETE 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":null} +``` + +After a successful execution, we query all the data lists to see if it has been deleted: + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user' +{"code":0,"message":"","data":{"list":[{"id":2,"name":"alice","status":0,"age":18}]}} +``` + +As we can see, the data has been successfully deleted. + +## Learning Summary + +As we can see, using the scaffold tool of the `GoFrame` framework, we can efficiently and quickly complete API development work with the project template generated, and it can automatically generate API documentation for convenient frontend-backend collaboration. + +With this, a `CRUD` API project using the `GoFrame` framework has been quickly completed. However, the excellence of the `GoFrame` framework is far more than this; more features await your further exploration. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..dd238bfd4e1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,19 @@ +--- +slug: '/quick/scaffold-api' +title: 'API Development Tutorial🌟' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame, REST API, CRUD operations, API development, scaffolding tools, backend development, Go programming, web services, database integration, API design] +description: "Master API development with GoFrame's powerful scaffolding tools. This hands-on tutorial walks you through building complete CRUD operations, from database design to API implementation. Perfect for developers looking to quickly build robust REST APIs with Go." +translator: claude3.5 +--- + +Ready to build your first API with GoFrame? This chapter will show you how to create professional-grade CRUD endpoints using GoFrame's scaffolding tools. Whether you're new to Go or an experienced developer, you'll find our streamlined approach both powerful and intuitive. + +:::tip +Find the complete source code for this tutorial at: https://github.com/gogf/quick-demo +::: + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" new file mode 100644 index 00000000000..94cf23dffa6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" @@ -0,0 +1,128 @@ +--- +slug: '/quick/scaffold-boost' +title: 'Project Initialization' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame, project initialization, application startup, routing configuration, HTTP server setup, API versioning, middleware integration, Swagger documentation, project structure] +description: "Learn how to initialize and bootstrap your GoFrame application. This guide walks you through the project startup process, from understanding the main entry point to configuring routes and middleware. You'll gain insights into GoFrame's architectural design and best practices for structuring your web applications." +translator: claude3.5 +--- + +New to the project structure? Don't worry! This chapter builds on our [Project Structure Guide🔥](../../docs/框架设计/工程开发设计/工程目录设计.md) to explain how your application starts up and how different components work together. + +## The Main Entry Point + +Every GoFrame application begins with `main.go`. This file delegates to the `internal/cmd` package to orchestrate the application startup. In our project template, it specifically calls the `Run` command of the `Main` object in the `internal/cmd` package. + +All core business logic resides in the `internal` directory - a Go feature that prevents external packages from importing these files, enhancing security and maintaining clean architecture. + +:::tip +Framework core components require a `context` parameter. We use `gctx.GetInitCtx` to inherit trace information from the parent process. If no parent process exists, it creates a new context with tracing capabilities for downstream operations. +::: + +![main.go](QQ_1731652866651.png) + +## Bootstrap Process + +The `Main` object's `Run` command handles bootstrap initialization, where you can place dynamic startup logic. By default, our template: +1. Creates an HTTP server +2. Registers routes using group routing +3. Starts the HTTP server +4. Blocks for incoming requests while monitoring system signals +5. Gracefully shuts down when receiving exit signals + +:::tip +For detailed command-line management information, see: [Command Management](../../docs/核心组件/命令管理/命令管理.md) +::: + +![main command](QQ_1731653678736.png) + +## Configuring Routes + +The template uses group routing via the `Group` method - one of several routing approaches supported by GoFrame's HTTP server. + +```go +s := g.Server() +s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Bind( + hello.NewV1(), + ) +}) +``` + +This setup: +- Registers a middleware using `Middleware` to standardize route responses +- Binds route objects returned by `hello.NewV1()` using the `Bind` method +- Automatically registers all public methods of the route object +- Supports API versioning (default version is `v1`) + +:::tip +For comprehensive routing documentation, see: [Router Guide 🔥](../../docs/WEB服务开发/路由管理/路由管理.md) +::: + +## Route Objects + + +### Creating Route Objects +Let's look at how `hello.NewV1()` works: + +![Route Object Creation](QQ_1731655173428.png) + +Notice it returns an API interface rather than a concrete object: + +![API Interface](QQ_1731655571221.png) + +Why use an interface instead of returning the `ControllerV1` object directly? + +This design enables early error detection. If your `controller` doesn't implement all required API methods, you'll get compilation errors and IDE warnings, rather than runtime failures. + +:::tip +- This pattern is optional but recommended for robust code organization +- Most of this code can be auto-generated using the `gf gen ctrl` command based on API definitions +::: + +### Defining Route Handlers +Here's a typical route handler: + +![Route Handler](QQ_1731655216354.png) + +Route parameters are defined in the `HelloReq` input object: + +![Request Object](QQ_1731655423345.png) + +:::tip +This approach of using middleware for standardized responses and route objects is called **standardized routing**. +Learn more in our [Standard Router Guide](../../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md). +::: + +## Running Your Application + +### Server Operation + +The HTTP server starts with the `Run` method, which: +- Blocks for incoming requests +- Monitors process signals +- Handles server restart/shutdown + +You'll see this output on startup: + +![Server Output](QQ_1731657619286.png) + +Notice that API documentation and Swagger UI are automatically enabled. + +### Testing the API + +Visit http://127.0.0.1:8000/hello to test the API: + +![API Response](QQ_1731657717720.png) + +Access the API documentation at http://127.0.0.1:8000/swagger: + +![Swagger UI](QQ_1731657799765.png) + +## Chapter Summary + +You now understand how a GoFrame application bootstraps and runs. While we've covered the basics, there's much more to explore in our detailed documentation. + +Next, we'll build a complete CRUD API using this template, implementing database operations for creating, reading, updating, and deleting records. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" new file mode 100644 index 00000000000..d1a4c5c0cb3 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" @@ -0,0 +1,13 @@ +--- +slug: '/quick/scaffold' +title: 'Project Scaffold🌟' +sidebar_position: 1 +hide_title: true +description: "Learn how to leverage GoFrame's powerful project scaffolding to jumpstart your development. Perfect for developers looking to build modern web applications with Go, this guide demonstrates how to seamlessly integrate front-end and back-end technologies using GoFrame's efficient project structure and tools." +keywords: [GoFrame, project scaffold, web development, Go framework, full-stack development, rapid prototyping, project structure, web application, backend development, development workflow] +translator: claude3.5 +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/QQ_1731813654454.png b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/QQ_1731813654454.png new file mode 100755 index 00000000000..6f01877e72b Binary files /dev/null and b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/QQ_1731813654454.png differ diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.0 2022-03-09.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.0 2022-03-09.md new file mode 100644 index 00000000000..e5c89549cf9 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.0 2022-03-09.md @@ -0,0 +1,270 @@ +--- +slug: '/release/v2.0.0' +title: 'v2.0 2022-03-09' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame Framework, v2.0 Release, Full Link Tracing, ORM Improvements, Component Interfacing, New Features, Development Efficiency, Error Stack, Interface Design] +description: "The GoFrame v2.0 version is released, enhancing engineering design and full link tracing capabilities, and providing strictly regulated naming and parameter passing methods. New features include comprehensive error code support and component interfacing design, improving scalability and development efficiency. Meanwhile, significant improvements have been made to the ORM and logging components, making the framework more user-friendly." +--- + +Hello everyone! The much-anticipated `GoFrame v2` version has finally been released! This version includes a large number of improvements and new features, as well as some groundbreaking functionalities. + +From last summer to this spring, we have worked hard all the way, and we hope everyone is satisfied. + +Thanks to all community partners for their contributions and support from community friends! + +In the new year, we continue to stay grounded, never forgetting our original intention! + +Upgrade Guide: [Happily Upgrade from v1 to v2](../docs/其他资料/如何从v1愉快升级到v2.md) + +## 1. Important Features + +### 1. New Engineering Design + +- More rigorous and standardized +- Standardization of naming style +- Standardization of pointers and value passing parameters +- Further simplified, improving development efficiency +- New version development tools support the accurate implementation of engineering specifications +- `Entity/DAO/DO` features +- Interface-oriented design +- More detailed introduction: [Engineering Design 🔥](../docs/框架设计/工程开发设计/工程开发设计.md) + +### 2. Full Link Tracing Features + +- Observability further: bold foresight and determination +- The framework enables `OpenTelemetry` features by default +- The framework creates `TraceID` by default, according to `OpenTelemetry` generation standards +- All core components of the framework support link tracing information transmission +- The logging component supports printing link information +- More detailed introduction: [OpenTelemetry Tracing](../docs/框架设计/全链路跟踪设计.md) + +### 3. Standardized Route Registration Features + +- Standardized API structured programming design +- Standardized API interface method parameter style definition +- Simplified route registration and maintenance +- Unified interface return data format design +- Automatic API parameter object reception and validation +- Automatically generates interface documentation based on the standard `OpenAPIv3` protocol +- Automatically generates `SwaggerUI` page +- More detailed introduction: [Standard Router](../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md) + +### 4. Full Error Stack Feature + +- A major decision made at the framework level +- **All** framework component errors support error stacks +- Detailed introduction: [Error Stack](../docs/框架设计/全错误堆栈设计.md) + +### 5. New Error Code Feature + +- Uses interface design, highly extensible +- Provides selectable common error codes +- Core component base layer of the framework has enhanced error code support, e.g., identifying if `DB` execution error through error code in `error` +- More detailed introduction: [Error Handling - Error Code](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码特性.md) + +### 6. Component Interface Design + +- Top-down unified interface design +- Core components all use interface design +- Higher extensibility and customizability +- More detailed introduction: [Interface and Generic](../docs/框架设计/接口化与泛型设计.md) + +### 7. Support for Generic Framework + +- What is the framework `gvar` generic? +- Extensive use of `gvar` generic in core components of the framework +- The significant value of `gvar` generic in the framework +- Why it is not recommended to use generics in top-level business +- More detailed introduction: [Interface and Generic](../docs/框架设计/接口化与泛型设计.md) + +### 8. Plenty of ORM Improvements + +- Detailed introduction: [Database ORM🔥](../docs/核心组件/数据库ORM/数据库ORM.md) + +### 9. Other Important Improvements + +#### 1) Logging Component `Handler` Feature + +- Middleware design +- Supports multiple `Handler` processing +- Provides more flexible and powerful support for developer-defined log processing +- More detailed introduction: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + +#### 2) Logging Component Color Printing + +- Default color printing output in terminal +- Different colors for different levels by default, configurable +- Output to file/custom `Writer` default off, can be turned on via configuration +- More detailed introduction: [Logging - Color Printing](../docs/核心组件/日志组件/日志组件-颜色打印.md) + +#### 4) Improved Debug Mode Introduction + +- More detailed introduction: [Debug Mode](../docs/核心组件/调试模式.md) + +## 2. Function Improvements + +### 1. Data Components + +1. `/database/gdb` +1. Deprecated `Table` method, unified use of `Model` method to create `Model` object. +2. Deprecated `Struct/Structs` methods in `Model`, unified use of `Scan` method to execute query results conversion to `Struct` object/objects array: [Model Query - Scan](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Scan映射.md) +3. Deprecated `BatchInsert/BatchReplace/BatchSave` methods, unified use of `Insert/Replace/Save` methods for implementation, internally auto-recognizing parameter type to decide single or batch write: [ORM Model - Insert/Save](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) +4. Added `DoFilter` interface method for custom filtering of `SQL&Args` before `ORM` submits to underlying `driver`: [ORM Interface - Callback](../docs/核心组件/数据库ORM/ORM接口开发/ORM接口开发-回调处理.md) +5. Added `DoCommit` interface method for custom processing before `ORM` submits `SQL&Args` to underlying `driver` +6. Added `ConvertDataForRecord` interface method for customized data conversion handling. [ORM Interface - Callback](../docs/核心组件/数据库ORM/ORM接口开发/ORM接口开发-回调处理.md) +7. Added `Raw` method to build `Model` object through raw `SQL` statement, followed by using `Model` chaining operations and features: [ORM Model - Model](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型创建.md) +8. Added `Handler` feature for customized `Model` object modification and returning new `Model` object, allowing easy reuse of common logic: [ORM Model - Handler](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Handler特性.md) +9. Added `Union/UnionAll` feature for merging results of multiple `SQL/Model` queries: [Model Query - Union/UnionAll](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-UnionUnionAll.md) +10. Added `With` feature for condition query and sort statement configuration support: [Model Association - With](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) +11. Added `OnDuplicate/OnDuplicateEx` methods for specifying update/do-not-update field of `Save` methods: [ORM Model - Insert/Save](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) +12. Added `Wheref/WhereOrf` methods for condition passing with formatted string statement: [Model Query - Where](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +13. Added `WhereLT/WhereLTE/WhereGT/WhereGTE` and `WhereOrLT/WhereOrLTE/WhereOrGT/WhereOrGTE` methods for adding common comparison conditions to ORM. +14. Added `WherePrefix/WhereOrPrefix` methods for adding table prefixes to condition fields, commonly used in association queries +15. Added `FieldsPrefix/FieldsExPrefix` methods for adding custom table prefix to query fields, commonly used in association queries +16. Added `FieldsCount/FieldsSum/FieldsMin/FieldsMax/FieldsAvg` methods for adding common unified query conditions +17. Added `LeftJoinOnField/RightJoinOnField/InnerJoinOnField` methods for convenient association with tables having the same field name +18. Added `OmitEmptyWhere/OmitEmptyData` methods for specifically filtering empty value data in `Where` conditions and `Data`: [ORM Model - Fields Filtering](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) +19. Added `OmitNil/OmitNilWhere/OmitNilData` methods for specifically filtering `nil` data in `Where` conditions and `Data`: [ORM Model - Fields Filtering](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) +20. Added `TimeZone` configuration item for custom time zone conversion in database queries (currently supports `mysql/pgsql`): [ORM - Configuration](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +21. Improved `Cache` feature, supports accurate cache parameter control +22. Added `Close` method for manually closing database connection +23. Removed `ORM` configuration limit of default `100` connections when custom configuration not used. +24. Improved time maintenance feature, no longer automatically filtering `CreatedAt/UpdatedAt/DeletedAt` related parameters submitted by developers, allowing custom updates to related time fields in `ORM` operations. +25. Improved database execution SQL log recording, added affected row count recording +26. Interface method `HandleSqlBeforeCommit` renamed to `DoCommit`. +27. Database method operations uniformly added `context.Context` as the first required parameter. +28. Fixed `gdb` component `With` feature multi-level query invalid issue. +29. Removed all deprecated methods of query result types `Record/Result`. +30. Completed unit tests. +31. `/database/gredis` +32. Adopted adapter pattern, refactored the component with interface design to improve extensibility: [Redis - Interface](../docs/组件列表/NoSQL%20Redis/Redis-接口化设计.md) + +33. Provided default adapter implementation based on third-party `goredis` package, added support for `Redis` cluster: [Redis - Configuration](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) +34. Due to support for cluster features, configuration file format changed: [Redis - Configuration](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) + +### 2. Network Components + +1. `/net/ghttp` +1. New route registration method: [Standard Router](../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md) +2. Default `Request` object injection into `ctx` context object, with added `RequestFromCtx/g.RequestFromCtx` methods to obtain `Request` object in `ctx`. +3. Abstracted and encapsulated `Client` features into `gclient` component: [HTTPClient](../docs/WEB服务开发/HTTPClient/HTTPClient.md) +4. `Server` logs added support for printing `ctx` context link information and improved log format: [Link Tracing](https://wiki.goframe.org/pages/viewpage.action?pageId=7298186) +5. Parameter acquisition returns unified use of `*gvar.Var` generic object. +6. Deprecated direct operation methods of `HTTP Client` in `ghttp`, must create `Client` object for client access operations. +7. Deprecated `Controller` route registration method, removed related implementation code. +2. `/net/gtrace` +1. Upgraded `go.opentelemetry.io/otel` to the latest official version. +2. Improved new link tracing usage documentation: [Service Tracing](../docs/服务可观测性/服务链路跟踪/服务链路跟踪.md) + +### 3. System Components + +1. `/os/glog` + 1. To promote observability features and implement link tracing specifications, all log printing methods added `context.Context` parameter. + 2. Logging component added `Handler` feature, using middleware design and supporting multiple `Handler` processing, providing more flexible and powerful support for developer-defined log processing: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 3. Logging component added support for color printing of content, default color printing output in terminal, output to file/custom `Writer` default off, can be enabled via related configuration: [Logging - Color Printing](../docs/核心组件/日志组件/日志组件-颜色打印.md) + 4. Deprecated `Println` method. + 5. Documentation update: [Logging](../docs/核心组件/日志组件/日志组件.md) +2. `/os/gres` + 1. Added `Export` method for exporting files from resource component to local disk: [Resource - Methods](../docs/核心组件/资源管理/资源管理-方法介绍.md) +3. `/os/gfile` + 1. Added `SizeFormat` method for getting formatted size string of specified file. + 2. Documentation update: [File](../docs/组件列表/系统相关/文件管理-gfile.md) +4. `/os/gcache` + 1. Adopted adapter pattern, refactored the component with interface design to improve extensibility: [Caching - Interface](../docs/核心组件/缓存管理/缓存管理-接口设计.md) + 2. Provided default cache implementation based on process memory: [Caching - In-Memory](../docs/核心组件/缓存管理/缓存管理-内存缓存.md) + 3. All operation methods added `context.Context` context parameter. + 4. Parameter acquisition returns unified use of `*gvar.Var` generic object. + 5. Added `Must*` methods for directly obtaining parameters and triggering `panic` on errors. +5. `/os/gcfg` + 1. Adopted adapter pattern, refactored the component with interface design to improve extensibility: [Configuration - Interface](../docs/核心组件/配置管理/配置管理-接口化设计/配置管理-接口化设计.md) + 2. Provided default configuration management based on file system: [Configuration](../docs/核心组件/配置管理/配置管理.md) + 3. Parameter acquisition returns unified use of `*gvar.Var` generic object. + 4. All operation methods added `context.Context` context parameter. + 5. Added `GetWithEnv` method, automatically reading corresponding parameters from environment variables when they cannot be found in configuration adapter: [Configuration Management - Configuration Reading](https://wiki.goframe.org/pages/viewpage.action?pageId=1114668#id-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86-%E9%85%8D%E7%BD%AE%E8%AF%BB%E5%8F%96) + 6. Added `GetWithCmd` method, automatically reading corresponding parameters from command line parameters when they cannot be found in configuration adapter: [Configuration Management - Configuration Reading](https://wiki.goframe.org/pages/viewpage.action?pageId=1114668#id-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86-%E9%85%8D%E7%BD%AE%E8%AF%BB%E5%8F%96) + 7. Added `Must*` methods for directly obtaining parameters and triggering `panic` on errors. + 8. Configuration component usability improvement, accessing configuration component via singleton object will automatically search configuration files according to `toml/yaml/yml/json/ini/xml` file suffix: [Configuration](../docs/核心组件/配置管理/配置管理.md) +6. `/os/gcmd` + 1. Parameter acquisition returns unified use of `*gvar.Var` generic object. + 2. Brand new multi-level command line management method, supports automatic generation of command line usage tips: [Command - Object](../docs/核心组件/命令管理/命令管理-命令行对象.md) + 3. Added object-based command line management method, more suitable for large terminal command scenes: [Command - Structure](../docs/核心组件/命令管理/命令管理-结构化参数.md) +7. `/os/genv` + 1. Parameter acquisition returns unified use of `*gvar.Var` generic object. +8. `/os/gcron` + 1. Added `context.Context` parameter to defined scheduled task methods. + 2. Added `context.Context` parameter to all create scheduled task methods. + 3. Documentation update: [Cron Job](../docs/组件列表/系统相关/定时任务-gcron/定时任务-gcron.md) +9. `/os/gtime` + 1. Deprecated `Second/Millisecond/Microsecond/Nanosecond` package methods, replaced with `Timestamp/TimestampMilli/TimestampMicro/TimestampNano` methods. + 2. Documentation update: [Time](../docs/组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) +10. `/os/gtimer` + 1. Added `context.Context` parameter to defined timer methods. + 2. Added `context.Context` parameter to all create timer methods. + 3. Improved the timer task execution detection mechanism based on priority queue data structure storage, enhancing execution performance. + 4. Documentation update: [Timer](../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +11. `/os/grpool` + 1. Added `context.Context` parameter to callback method definitions. + 2. Added `context.Context` parameter to `goroutine` pool task addition methods. + 3. Documentation update: [Coroutine Management - grpool](https://wiki.goframe.org/pages/viewpage.action?pageId=1114246) +12. `/os/gsession` + 1. Added `ctx` context parameter input to `gsession.Storage` interface for receiving context information and implementing complete link tracing. For rigor, added `error` return parameter: [Session](../docs/WEB服务开发/Session/Session.md) + 2. Parameter acquisition returns unified use of `*gvar.Var` generic object. +13. `/os/gview` + 1. Added `context.Context` parameter to template parsing methods. + 2. Added `plus/minus/times/divide` arithmetic template methods. + 3. Documentation update: [Template Engine](../docs/核心组件/模板引擎/模板引擎.md) +14. `/os/gstructs` + 1. Opened `structs` package from `internal` of framework, named as `gstructs`, used for advanced usage of `struct` reflection operations: [Object Information](../docs/组件列表/系统相关/对象信息-gstructs.md) + +### 4. Error Handling + +1. `/errors/gerror` + +1. Added `Message` method for obtaining error message of specified error code. +2. Added `CodeMessage` method for obtaining error code information of specified error. +3. Added `NewOption` method for custom-configured error object creation, dedicated to advanced players of the framework. +4. Added `HasStack` method to determine whether given error interface object has implemented (contains) stack information. +5. Changed error code from integer to interface object to achieve customizability and improve extensibility, refer to `gcode` component introduction for details: [Error Code - Example](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) +6. Improved usability, revised `NewCode/NewCodeSkip/WrapCode/WrapCodeSkip` methods, optional `text` input parameter, default using `Message` info of corresponding error code. +2. `/errors/gcode` +1. Added `gcode` error code component, providing highly customizable and extensible error code management, combined with `gerror` component for powerful error handling: [Error Code - Example](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) + +### 5. Other Components + +1. `/container/garray` +1. Added `At` method to each array type for directly obtaining returned data at specific index position. +2. Documentation update: [Array - Methods](../docs/组件列表/数据结构/数组类型-garray/数组类型-方法介绍.md) +2. `/debug/gdebug` +1. Added `TestDataContent` method for directly obtaining file content of specified path under `testdata` directory in testing package. +2. Documentation update: [Debugging](../docs/组件列表/功能调试/调试功能-gdebug.md) +3. `/encoding/gjson` +1. Deprecated most `Get*` methods, unified use of `Get` method to obtain the content of specified `pattern`, uniformly returns `*gvar.Var` generic object, developers can use corresponding methods to easily convert to specific type variables according to business scenario. +2. Added several `Must*` methods. +3. Comprehensive update on usage documentation +4. `/frame/g` +1. Added `ModelRaw` method for conveniently creating native `SQL` based database `Model` object. +2. Added `logger` configuration to `ORM` objects created via `/frame/g` module, auto-initialized by automatically reading configuration files: [ORM - Configuration](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +3. Added `logger` configuration to `Server` objects created via `/frame/g` module, auto-initialized by automatically reading configuration files: [Configuration](../docs/WEB服务开发/服务配置/服务配置.md) +5. `/frame/gmvc` +1. Deprecated `gmvc` coupled module, no further support in the future. +6. `/util/gutil` +1. Improved implementation of `Dump` method, no longer using `json` package to implement type printing, instead self-implemented feature for printing any type, supporting detailed data type printing: [Utility Functions](../docs/组件列表/实用工具/工具方法-gutil.md) +2. Added `SliceToMapWithColumnAsKey` method for converting `Slice` to `Map` according to certain rules. +7. `/utils/gvalid` +1. Added `bail` validation rule, and `Bail` chaining method for immediately exiting validation when data validation fails and not executing subsequent validation rules. +2. Added `datetime` validation rule for validating common date and time types, with date separator supporting only `-`, format like: `2006-01-02 12:00:00`. +3. Removed package validation methods, unified implementation of data validation using chaining operations. +4. All validation methods added `context.Context` parameter. +5. Brand new, highly complete usage documentation of the data validation component: [Data Validation](../docs/核心组件/数据校验/数据校验.md) + +For other numerous improvement details, we won't elaborate here. Interested friends can refer to the official website [goframe.org](https://wiki.goframe.org) + +## 3. CLI Toolchain + +1. Refactoring implemented with the encapsulation of the brand new `gcmd` command line object. +2. Improved `init` command, supports `SingleRepo/MonoRepo` two repository initialization. Project initialization no longer depends on remote repository. +3. Improved `gen dao` command, adopting the brand new `V2` engineering design, automatically generating `entity/dao/do` code files. +4. Removed `update` command, tool updates unified through [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) +5. Removed `get` command. +6. All-new documentation: [CLI Tool](../docs/开发工具/开发工具.md) \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.1 2022-06-22.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.1 2022-06-22.md new file mode 100644 index 00000000000..a9d597ebe72 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.1 2022-06-22.md @@ -0,0 +1,152 @@ +--- +slug: '/release/v2.1.0' +title: 'v2.1 2022-06-22' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,Version Update,Feature Improvement,Bug Fix,Database Component,Service Registration,Load Balancing,Network Component,System Component,Encoding Support] +description: "The GoFrame v2.1 release introduces several practical business features, including improvements to the database and network components, supporting more flexible SQL condition combinations and custom hook event handling. Service registration, load balancing, and system component functionalities are enhanced. Memory usage optimization and improved service discovery logic." +--- + +Hello everyone, the `v2.1` release includes some business-related function features, improvements, and bug fixes. It's recommended to upgrade. + +Video Introduction: [2022-06-22 GoFrame v2.1 Features & Q&A](../community/社区交流/技术分享交流/5-2022-06-22%20GoFrame%20v2.1功能特性&使用答疑.md) + +## New Features + +1. The development tool introduces the `gen service` command, supporting automated generation of `service` interface code and implementation injection based on the `logic` layer code: [Service Generating](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +2. Database Features: +1. Added `WhereBuilder` feature for more flexible `SQL` condition statement combinations: [Model Query - Where](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +2. Added `Hook` feature for custom hook event handling: [ORM Model - Hook](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Hook特性.md) +3. The framework adds a `DeepCopy` feature for deep copying of types: +1. Added `gutil.Copy` method for deep copying specified content. +2. Generic types add a `Copy` method for deep copying their own content. +3. Some data types of the framework now support the deep copy feature, such as basic container types like `gvar, garray, gmap`. + +## Major Improvements + +### Community Components + +**ORM Driver Implementation** + +1. Added `drivers/clickhouse` for hooking `clickhouse` to the `goframe ORM` component. +2. Complete unit tests for `clickhouse/mssql/pgsql/sqlite/oracle` components. +3. Moved the `mysql` driver from the main library to the community module to facilitate decoupling from the main library. Therefore, from subsequent versions, developers need to manually import driver dependencies: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +**Registration Discovery Implementation** + +1. Added `polaris` North Star service registration interface implementation: [https://github.com/gogf/gf/tree/master/contrib/registry/polaris](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) +2. Improved the `etcd` service registration discovery interface implementation component: [https://github.com/gogf/gf/tree/master/contrib/registry/etcd](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) + +### Registration Discovery + +1. Improved the `Service` implementation object to an interface definition and provided a default `Service` implementation for improved extensibility and usability. +2. Improved `HTTP/GRPC Client&Server` interface implementations. + +### Load Balancing + +1. Improved the `Node` interface definition and added the `Nodes` interface definition. +2. Fixed the load balancing issue under the `HTTP Client`. + +### Network Components + +1. `gclient` +1. Improved service discovery implementation logic. +2. Fixed client shutdown error causing connection pool reuse issues. +2. `ghttp` +1. Improved `Request.GetUrl` method details for `URL Schema`. +2. Parameter reception supports automatic receipt of `UploadFile` attributes. +3. Added custom UI guidance documentation for interface documentation: [API Document - Custom UI](../docs/WEB服务开发/接口文档/接口文档-自定义UI.md) +4. Changed the default external `JS CDN` dependency for interface documentation to `unpkg.com`. +5. Improved service registration implementation logic. +6. Improved internal detail implementation logic. +7. Fixed the parameter empty judgment issue. +3. `goai` +1. Improved adherence to the `OpenAPIV3` protocol implementation. +2. Supports all custom tags starting with `x-`, automatically adding them to the `OpenAPIV3` results. +3. The component moved from the `protocol` category to the `net` category, changing the `import` path. + +### System Components + +1. `gcfg` +1. The default file system interface implements support for the `property` file format. +2. `gcmd` +1. Parameter parsing adds `CaseSensitive` configuration, default parsing is case-insensitive, especially affecting structured parameter reception: [Command - Structure](../docs/核心组件/命令管理/命令管理-结构化参数.md) +2. Added cross-process link tracking feature: [Command - Tracing](../docs/核心组件/命令管理/命令管理-链路跟踪.md) +3. `glog` +1. Added a global `Handler` setting function, allowing developers to globally customize handling of all logs of the `glog` component, such as globally outputting in the `JSON` file format: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) +2. Added the default `JSON` format `Handler` for developers to use: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) +4. `gsession` +1. Resolved excessive memory usage issues caused by too many user accesses. +5. `gproc` +1. Added cross-process link tracking feature: [Process - Tracing](../docs/组件列表/系统相关/进程管理-gproc/进程管理-链路跟踪.md) + +### Container Components + +1. `garray` +1. Improved the `Unique` method performance, added `DeepCopy` interface implementation. +2. `glist` +1. Added `DeepCopy` interface implementation. +3. `gmap` +1. Added `DeepCopy` interface implementation. +4. `gset` +1. Added `DeepCopy` interface implementation. +5. `gtype` +1. Added `DeepCopy` interface implementation. +6. `gvar` +1. Added `Copy` method for deep copying the current generic object. +2. Added `DeepCopy` interface implementation. + +### Database Components + +1. `gdb` +1. Added `WhereBuilder` feature for more flexible `SQL` condition statement combinations: [Model Query - Where](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +2. Added `HOOK` feature for custom hook event handling: [ORM Model - Hook](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Hook特性.md) +3. Improved the logic of data conversion processing before submitting to the underlying `driver`. +4. Moved the `mysql` driver from the main library to the community module to facilitate decoupling from the main library. Therefore, from subsequent versions, developers need to manually import driver dependencies: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +### Encoding Components + +1. `gproperty` +1. Added `gproperty` component for parsing `Java Property` format files. +2. `gjson` +1. Added support for encoding, decoding, and manipulating `property` file format data: [General Codec](../docs/组件列表/编码解码/通用编解码-gjson/通用编解码-gjson.md) +2. Fixed the issue of precision loss when reading large integers. + +### Text Processing + +1. `gstr` +1. Improved `WordWrap` method to be more friendly with `Unicode`, especially Chinese line breaks. +2. Fixed the issue where `RepliceI` ignoring case string replacement in certain scenarios. + +### Error Handling + +1. `gerror` +1. Added `Unwrap` method (same as `Next` method) to support the new `Golang` version `Unwrap` error interface. +2. Added `Equal` method for comparing whether two errors are equal: [Error Handling - Comparison](../docs/核心组件/错误处理/错误处理-错误比较.md) +3. Added `Is` method to support the new `Golang` version `Is` error interface: [Error Handling - Comparison](../docs/核心组件/错误处理/错误处理-错误比较.md) + +### Utility Methods + +1. `gconv` +1. Removed support for octal strings when converting integers. +2. Improved internal implementation logic for readability and maintainability. +2. `gutil` +1. Added `gutil.Copy` method for deep copying specified content. +2. Improved `gutil.Dump` method. + +## Development Tools + +Compared to the stable code components of the main library, CLI development tools have some incompatible updates in recent versions. Please pay attention to release records during upgrades; details are available in the source code adjustments. + +1. Improved `build` command, supporting specifying the generation directory of `pack` code files, with some parameter adjustments. +2. Improved `docker` command, supporting multiple `docker tag` renaming and automatic repository pushing. +3. Improved `gen dao` command, supporting custom `dao/do/entity` code generation directory, no longer forced to generate in the `service/internal` directory: [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +4. Added `gen service` command, supporting automated generation of `service` interface code based on `logic` layer code: [Service Generating](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +5. Fixed issues with the `run` command's custom program startup parameters, `gofmt/goimports` program path containing spaces. + +## Incompatibilities + +1. Moved the `mysql` driver from the main library to the community module to facilitate decoupling from the main library. Therefore, from subsequent versions, developers need to manually import driver dependencies: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) +2. The `ghttp.Response.WriteJson/Xml` methods no longer return `error`. Adjust according to the compilation error. +3. The `goai` component moved from the `protocol` category to the `net` category, changing the `import` path. Adjust according to the compilation error. +4. Database `ORM` operations with `gtime.Time` type parameters will automatically convert them to `time.Time` type before submission to the underlying database `driver` to solve the precision loss issue. This means that `gtime.Time` type parameters are also affected by the database configuration's timezone parameters. For details, see: [ORM - Timezone](../docs/核心组件/数据库ORM/ORM时区处理.md) \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.2 2022-10-11.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.2 2022-10-11.md new file mode 100644 index 00000000000..a72a657c6a6 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.2 2022-10-11.md @@ -0,0 +1,145 @@ +--- +slug: '/release/v2.2.0' +title: 'v2.2 2022-10-11' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame Framework,v2.2.0,Validation Component,Database Component,Dameng Database,Apollo,Polaris,Kubernetes ConfigMap,open source] +description: "The GoFrame Framework v2.2.0 release includes a restructured validation component and improved database component interface design, with support for Dameng Database and new configuration center implementations. Thanks to over 44 code contributors, the GoFrame framework is becoming more powerful and flexible." +--- + +👋 Hi, friends! The `GoFrame` framework `v2.2.0` was officially released today! + +The major highlights of this version include: + +- The validation component, most frequently used by developers, has been restructured and improved, making it easier for community developers to develop and maintain built-in validation rules. It also enhances some common validation rules, making the component's built-in rules richer and stronger. +- The database component interface design has been reconstructed, making it easier for community developers to add a new database type `driver`. Currently, the framework provides `9` types of database `driver` implementations through community components, meeting the needs of most business projects. Notably, this version adds support for the domestic Dameng Database. We hope that in the future, community developers can provide more `driver` implementations for domestic databases and contribute to the open-source community. +- The richness of community components is expanded by adding `3` types of configuration center interface implementations in this release, supporting `Apollo/Polaris/Kubernetes ConfigMap`. The `GoFrame` framework adopts a modular low-coupling design, with components split into the main framework library and community components. The main framework library provides core, general, and lightweight basic components, while the community components are standalone packages decoupled from the main framework library, expanding the capabilities of the framework while ensuring the core's lightness. +- With over `44` code contributors in this release, the total number of contributors to the framework has reached `107`. We appreciate everyone's efforts and contributions to the community! 💖 + +Github ChangeLog: [https://github.com/gogf/gf/releases/tag/v2.2.0](https://github.com/gogf/gf/releases/tag/v2.2.0) + +## New Features + +1. Refactored the built-in validation rules manager of the validation component, increasing and supporting `59` commonly used built-in validation rules: [Data Validation - Rules](../docs/核心组件/数据校验/数据校验-校验规则.md) +2. Added the community component `contrib/config/kubecm`, implementing a configuration component `Adapter` based on `kubernetes configmap`: [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) +3. Added the community component `contrib/config/apollo`, implementing a configuration component `Adapter` based on the `apollo` configuration center: [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) +4. Added the community component `contrib/config/polaris`, implementing a configuration component `Adapter` based on the `polaris` configuration center: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) +5. Added `contrib/drivers/dm` support for the **domestic Dameng Database**: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## Major Improvements + +### Community Components + +#### ORM Driver Implementation + +1. Improved `contrib/drivers/pgsql` to support more `pgsql` built-in data types at the ORM component level. +2. Improved `contrib/drivers/pgsql` to support the `LastInsertId` feature for write operations. +3. Improved `contrib/drivers/clickhouse` to support `decimal.Decimal` data types. +4. Added `contrib/drivers/dm` support for the **domestic Dameng Database**: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +#### Service Discovery Components + +1. Improved the `contrib/registry/etcd` implementation by turning the log object into an interface property, allowing external registration of custom log objects. + +#### Configuration Components + +1. Added the community component `contrib/config/kubecm`, implementing a configuration component `Adapter` based on `kubernetes configmap`: [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) +2. Added the community component `contrib/config/apollo`, implementing a configuration component `Adapter` based on the `apollo` configuration center: [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) +3. Added the community component `contrib/config/polaris`, implementing a configuration component `Adapter` based on the `polaris` configuration center: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) + +### Database ORM + +1. Unified the configuration management format of single-line strings for different database types and maintained compatibility and support for the existing specific configuration formats for different database types: [ORM - Configuration](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +2. Improved interface design, simplifying `driver` implementation logic, making it easier to add more database `driver` support in the future. +3. Added the `ToSQL` method to generate SQL statements for debugging ORM operations without actually executing the SQL. +4. Added the `CatchSQL` method to obtain a list of internally executed SQL statements through a closure method. +5. Deprecated the `Core` object's `GetStruct/GetStructs` methods, opting for a unified `Scan` method for better usability. +6. Changed the database object's log object to an interface property, allowing for external registration of custom log objects. +7. Added `Extra` and `Protocol` configurations for setting additional configuration parameters and link protocols, with default automatic parsing through `Link` configuration. +8. Removed the `Filtered` interface, adopting a default implementation to simplify complexity and improve usability. +9. Added `ConvertValueForLocal` and `CheckLocalTypeForField` interfaces for custom data type conversion and fetching, with default implementation provided. +10. Added `ClearTableFields` method for clearing data structure cache specific to a particular database table: [ORM Senior - Field Mapping](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-字段映射.md) +11. Added `ClearTableFieldsAll` method for clearing all data structure caches for all tables in the current database object: [ORM Senior - Field Mapping](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-字段映射.md) +12. Added `ClearCache` method for clearing all query caches for a particular database table: [ORM Model - Query Cache](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) +13. Added `ClearCacheAll` method for clearing all query caches for the current database object: [ORM Model - Query Cache](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) +14. Various other improvements. + +### Encoding and Decoding Component + +1. `gcompress` +1. Added `GzipPathWriter` method for compressing files from a specific path into a specified `io.Writer`. +2. Added `ZipPathContent` method for packaging and compressing files/directories using the `zip` algorithm and returning byte content. +2. `gjson` +1. Adjusted `ContentTypeJson` and other string parameters to `ContentType` type. + +### Error Handling + +1. Added command-line parameter `--gf.gerror.brief` and `GF_GERROR_BRIEF` environment variable switch, controlling whether the framework stack is filtered when printing error stacks: [Error Handling - Other Features](../docs/核心组件/错误处理/错误处理-其他特性.md) +2. Various other detail improvements. + +### Network Component + +1. `ghttp` +1. Added commonly used middleware `MiddlewareJsonBody` for validating whether the request `Body` is in `JSON` format. +2. Added `GetListenedAddress` method for getting the listening address when `HTTP Server` listens on the specified `:0` port, where the system randomly assigns a listening address. +3. Modified the return HTTP status code to `500` when an error occurs on the server side. +2. `gtcp` +1. Added `GetListenedAddress/GetListenedPort` methods for obtaining the listening address/port when `TCP Server` listens on the specified `:0` port, where the system randomly assigns a listening address/port. +3. `gudp` +1. Added `GetListenedAddress/GetListenedPort` methods for obtaining the listening address/port when `UDP Server` listens on the specified `:0` port, where the system randomly assigns a listening address/port. +4. `goai` +1. Added support for properties defined in embedded structs when generating API documentation. +2. Removed duplicate parameter descriptions in the API documentation, especially when identical parameters exist in both `URL` and `Body`. +5. `gtrace` +1. Improved the error prompts for the `WithTraceID` method with more explicit information. +2. Added `WithUUID` method for converting standard `UUID` to `OpenTelemetry` `TraceID`. + +### System Component + +1. `gcfg` +1. Adjusted the `Available` interface method definition, making the `resource` parameter optional. +2. `gcron` +1. Added time difference calculation logic for scenarios of job delay, minimizing the impact on scheduled jobs whenever possible. +3. `gctx` +1. Added support for cross-process link tracking. +2. Added `GetInitCtx/SetInitCtx` method for obtaining and setting the context during the execution of the `main` package and package `init` initialization methods. +4. `glog` +1. Added `ILogger` interface definition for decoupled and interface-based use of log components across components. +2. Other detail improvements. +5. `gres` +1. Added `Export` method to the `File` object, enabling the export of resources associated with this object to a specified disk path. +6. `gstructs` +1. Improved `RecursiveOption` from `int` type to a custom type, along with adjustments to the corresponding method parameters. + +### Text Processing + +1. Added `gstr.IsGNUVersion` method for determining whether a given string complies with `GNU` version rules. + +### Utility Methods + +1. `gconv` +1. Improved `int64/uint64` conversion support for the special string `NaN`. +2. `gutil` +1. Added `GetOrDefaultStr/GetOrDefaultAny` methods for convenient handling of default values and optional parameters. +3. `gvalid` +1. Refactored the built-in validation rules manager, making it very convenient to add a new built-in validation. +2. Increased and supported `59` commonly used built-in validation rules: [Data Validation - Rules](../docs/核心组件/数据校验/数据校验-校验规则.md) + +## Bug Fixes + +1. Fixed `panic` issue of `DeepCopy` in `garray/gmap/gset/glist/gtype/gvar` when the container object is `nil`. +2. Fixed `panic` issue of `DeepCopy` in `gtime` when the object is `nil`. +3. Fixed the issue of overwriting multiple sort conditions in the `Group` method of the ORM chain operation. +4. Fixed the problem of duplicate output while returning `JSON` format strings in `HTTP Server`. +5. Fixed out-of-bounds array access in the `gstr.Nl2Br` method caused by logical judgment in some scenarios. +6. Fixed error print issue when table name is empty while fetching database table field information. +7. Fixed parameter reception issue of object properties as `*gjson.Json` type in `Req`. + +## Development Tools + +1. Improved `gen dao` command by adding the `clear` parameter to automatically clean up local data model Go files that do not exist in the target database: [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +2. Improved `gen service` command: [Service Generating](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +1. Integrated interface generation of struct objects by business module. +2. Added `clear` parameter to automatically clean up interface code and files not corresponding to `logic`. +3. Various other detail improvements. +3. Improved `run` command by adding custom program run parameters. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.3 2023-01-18.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.3 2023-01-18.md new file mode 100644 index 00000000000..2bd7955e038 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.3 2023-01-18.md @@ -0,0 +1,175 @@ +--- +slug: '/release/v2.3.0' +title: 'v2.3 2023-01-18' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame framework, interface design, microservice development, redis component, database ORM, configuration management, registration and discovery, nacos, zookeeper, ghttp] +description: "This update of the GoFrame framework includes an interface design to reduce coupling with third-party components, enhancing system flexibility. It also introduces community components like redis, increasing usability and performance. New implementations for nacos and zookeeper component interfaces are added to support common services, and tool upgrades enhance the framework usage experience. These updates pave the way for future microservice component expansions." +--- + +Hello everyone! This version is the last one for the year `2022` and the first one for `2023`. The main goals of this version are: + +- Using an interface design to solve the coupling issue of the main framework with the third-party open-source component `go-redis`, making the main framework lighter and more friendly for tool-based usage scenarios. +- Providing upgrade commands for the main framework, community components, and development tools via development tools, improving the overall usability of the framework and addressing the common issue of version inconsistency between community components and the main framework. +- Further designing according to the separation of interfaces and implementations, enhancing the integration of registration discovery and configuration management for common services through community components, preparing for the subsequent release of microservice components. + +Complete change list: [https://github.com/gogf/gf/compare/v2.2.0...v2.3.1](https://github.com/gogf/gf/compare/v2.2.0...v2.3.1) + +## New Features + +1. The heavily coupled `redis` component is decoupled from the main framework and provided as a community component. The main framework adds a `redis` interface definition, while the community component `redis` provides a concrete implementation. Therefore, please note that the usage of the main framework's `gredis` component has changed, and in projects relying on `redis`, it is necessary to introduce the community component `redis` implementation, otherwise method execution will return an error. The original `Do/DoVar` methods are retained for compatibility, and more than `100` commonly used `redis` operation methods are added: [NoSQL Redis](../docs/组件列表/NoSQL%20Redis/NoSQL%20Redis.md) +2. New common service interface implementations for configuration management and registration discovery components: + - **Configuration Management** (`nacos`): [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) + - **Registration Discovery** (`zookeeper`): [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) +3. Added tool command `gf up` for convenient framework upgrades, see: [Version Upgrade](../docs/开发工具/框架升级-up.md) + +## Improvements + +### Community Components + +#### Configuration Management + +1. Added `nacos` interface implementation: [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) + +#### Registration Discovery + +1. Added `zookeeper` interface implementation: [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) + +#### Database Driver + +1. Fixed the error issue when configuring the `Charset` parameter for `clickhouse`. +2. Improved the retrieval of data table structure results for `clickhouse`, with the returned `Index` order consistently starting from `0`. +3. Improved the `oracle` table structure retrieval SQL to support the `float64` data type. +4. Fixed the implementation of the `CheckLocalTypeForField` interface for `pgsql`, where the name was incorrectly defined as `CheckLocalTypeForValue`, causing attribute field type errors when generating `dao` code files. +5. Improved `pgsql` to add support for `schema`. Since `schema` represents "database name" in most database services, and to maintain backward compatibility, a `Namespace` configuration parameter is added to represent `pgsql`'s `Schema`, while the original `Schema` object represents `pgsql`'s `catalog`. + +#### NoSQL Components + +1. Added `redis` community component to implement `gredis` related interfaces: [https://github.com/gogf/gf/tree/master/contrib/nosql/redis](https://github.com/gogf/gf/tree/master/contrib/nosql/redis) + +### Database Components + +1. `gdb` +1. To enhance extensibility, the `TX` transaction object is changed to an interface definition, and the original `TX` object is renamed to `TXCore` for convenient custom interface implementation object nesting: [ORM - Transaction](../docs/核心组件/数据库ORM/ORM事务处理/ORM事务处理.md) +2. Added `Namespace` configuration item to support issues of distinguishing `Catalog&Schema` in some database services. The original `Schema` continues to represent the database name, while the new `Namespace` represents the `Schema` configuration in some database services: [ORM - Configuration](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +3. Improved database name configuration to support Chinese database names. +4. Added the current database name printout in the `SQL` execution log. +5. Fixed the cache issue of the `Count` method. +2. `gredis` +1. Added `redis` community component to decouple the heavily coupled `redis` component from the main framework, and provide it as a community component. The main framework adds a `redis` interface definition, and the community component `redis` provides a concrete implementation. Therefore, please note that the usage of the main framework's `gredis` component has changed, and in projects relying on `redis`, it is necessary to introduce the community component `redis` implementation, otherwise method execution will return an error. The original `Do/DoVar` methods are retained for compatibility, and more than `100` commonly used `redis` operation methods are added: [NoSQL Redis](../docs/组件列表/NoSQL%20Redis/NoSQL%20Redis.md) + +### Container Components + +1. `gpool` +1. Added the `MustPut` method to directly `panic` instead of returning an error object when `Put` execution errors occur. +2. `gqueue` +1. Improved the `Len/Size` methods to resolve potential queue count inaccuracies. +2. Improved the `Len/Size` methods, with the return parameter type changed from `int` to `int64`. + +### Error Handling + +1. `gcode` +1. Added `CodeNecessaryPackageNotImport` error code. +2. `gerror` +1. Improved stack prints, using spaces to replace `\t` to ensure the print format is compatible with different display terminals. + +### Object Management + +1. `gins` +1. Added group lock mechanism for singleton objects to enhance lock mechanism performance under high concurrency. + +### Network Components + +1. `ghttp` +1. Support for obtaining the current execution route method in middleware. +2. When the execution result of the route method is not `200`, support obtaining internal errors through the `Request.GetError` method in middleware. +3. Added `Response.ServeContent` method for custom content output interface implementation. +4. Improved reverse proxy support and added reverse proxy example: [https://github.com/gogf/gf/blob/master/example/httpserver/proxy/main.go](https://github.com/gogf/gf/blob/master/example/httpserver/proxy/main.go) +5. Improved error log output, using the `error` log level, making it easier for developers to identify log types in custom log `Handlers`. +2. `goai` +1. Added support for the `security` tag to configure `OpenAPIv3` security keys. +2. Improved structure attribute name retrieval when the `json` tag contains `,`. +3. `gtcp` +1. Renamed `SetSendDeadline` method to `SetDeadlineSend`. +2. Renamed `SetReceiveDeadline` method to `SetDeadlineRecv`. +3. Renamed `SetReceiveBufferWait` method to `SetBufferWaitRecv`. +4. `gudp` +1. Renamed `SetSendDeadline` method to `SetDeadlineSend`. +2. Renamed `SetReceiveDeadline` method to `SetDeadlineRecv`. +3. Renamed `SetReceiveBufferWait` method to `SetBufferWaitRecv`. + +### System Components + +1. `gcache` +1. Fixed the `MustGetOrSetFunc` method logic issue. +2. Improved the `LRU` cache expiration recycling mechanism implementation. +2. `gcmd` +1. Improved structured command line object generation, automatically reading `dc` tag content as `brief` when the `brief` tag is empty, ensuring that command line and interface definition tag habits are the same. +3. `gcron` +1. Improved log handling, using `glog.DefaultLogger` to output error logs when a scheduled task method `panic` occurs and the developer hasn't set the `Logger` interface. +2. Improved scheduled trigger judgment logic, resolving scheduled task triggering issues caused by inaccurate execution intervals of the underlying timer. +4. `glog` +1. Improved initialization logic when rolling split feature is enabled, resolving failure of rolling split execution due to initialization failure in some scenarios. +2. Improved the `Clone` method for further shallow copying to enhance chain operation performance. +3. Added `LevelPrint` configuration to control whether the default log `Handler` prints the log level string. +5. `gres` +1. Added `Pack*WithOption` methods to provide finer-grained resource packaging option control. +2. Deprecated methods: `Pack/PackToFile/PackToGoFile`. +3. Added `KeepPath` packaging option to control whether to retain the given relative path in the packaged file instead of using a path with a local packaging directory prefix (equivalent to removing the directory prefix). +6. `grpool` +1. Added `supervisor` feature to solve the issue of low probability of quitting at the same time in scenarios with a small number of `workers`. +7. `gstructs` +1. Added `Tag*` methods to obtain common tag values. +8. `gtime` +1. Improved `Equal/After/Sub` methods to resolve detailed issues in some scenarios. +2. Improved `EndOf*` methods, allowing developers to control the granularity of `EndOf` calculation in the returned time object. The default granularity is changed from nanoseconds to seconds for calculation. +3. Improved the `SetTimeZone` method to achieve cross-system compatibility, allowing only a global setting of the time zone once, with errors returned for multiple calls with different time zones: [Time - Time Zone](../docs/组件列表/系统相关/时间管理-gtime/时间管理-时区设置.md) + +### Text Processing + +1. `gstr` +1. Fixed `IsSubDomain` issue of determining that the main domain is a subdomain of a subdomain. +2. Improved `SubStr/SubStrRune` methods to support the use of a negative `start` parameter to specify right-side substring extraction. + +### Utility Components + +1. `gconv` +1. Added `Ptr*` methods for arbitrary type to specific type pointer variable conversion. +2. Improved `Map*` conversion methods for handling recursive conversions, by default, only recursively converting nested structure attributes. +3. Improved `Scan` method to resolve conversion issues from attributes of the same type and the same type pointer to target object/pointer. +2. `gtag` +1. Unified all tag names in the framework to be maintained under this module through constants. +2. Added `SetOver/SetsOver` methods for overriding custom tag key-value pairs. +3. `gutil` +1. Improved `Dump*` methods to support printing of circular nested object pointers. +2. Fixed `Dump*` method reflection error issues in some scenarios. +3. Added `OriginValueAndKind/OriginTypeAndKind` methods to obtain the reflect value/type object of a given variable, as well as the original reflect value/type object under pointer variables. + +## Bug Fixes + +1. Fixed the issue of utility installation failure in some environments. +2. Fixed array boundary issue in `New*ArrayRange` method for array object creation in some scenarios. +3. Fixed the error issue when configuring the `Charset` parameter for `contrib/drivers/clickhouse`. +4. Fixed attribute field type error issue in `pgsql` database generated `dao` code files. +5. Fixed cache issue with the `Count` method in the database ORM component. +6. Fixed `gstr.IsSubDomain` issue of determining that the main domain is a subdomain of a subdomain. +7. Fixed `gutil.Dump*` method reflection error issues in some scenarios. + +## Development Tools + +1. Added `gf fix` command for automatically updating local code incompatible changes when upgrading from lower to higher versions: [Compatibility Fix](../docs/开发工具/兼容修复-fix.md) +2. Added `gf up` command to upgrade local framework versions to the latest framework version: [Version Upgrade](../docs/开发工具/框架升级-up.md) +3. Improved `gf build` command to add environment variable information printing before building. +4. Improved `gf pack` command to add `KeepPath` parameter to control whether to keep the relative path after resource packaging: [Resource Packing](../docs/开发工具/资源打包-pack.md) +5. Improved `gf gen dao` command with `tx` parameter in generated `Transaction` methods changed from object pointer to interface. + +## Compatibility Warning + +1. The usage of `Redis` has changed. The old methods remain compatible, but additional community components need to be imported (interface and implementation separation). Please see the documentation for details. +2. The `TX` object of the database `ORM` is changed from a concrete implementation to an interface. This can be automatically upgraded and fixed using the new `up` or `fix` command in the development tools. + +## Next Version Goals + +- Improve and release the `grpcx` community component to provide extended support for the `grpc` interface protocol and enhance the usability of microservice development. +- Add a [Microservice Development] series chapter on the official website, mainly introducing microservice development using `goframe` with a focus on `grpc` development. +- Remove coupling with the third-party open-source component `gorilla/websocket` in the main framework, interface the support of `WebSocket`, and provide concrete implementations through community components according to the general decoupling design of the framework, providing extensibility and flexibility. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.4 2023-04-24.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.4 2023-04-24.md new file mode 100644 index 00000000000..66812f9c44c --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.4 2023-04-24.md @@ -0,0 +1,123 @@ +--- +slug: '/release/v2.4.0' +title: 'v2.4 2023-04-24' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,Microservices,Development Tools,Project Scaffolding,Database Component,Network Component,System Component,Community Component,Service Registration Discovery,Framework Update] +description: "GoFrame framework releases version v2.4.0, highlighting microservices development features and tools, with comprehensive documentation emphasizing the flexibility and extensibility of the framework. This version addresses several functional enhancements and optimizes database, network, and system components. New commands help developers build microservice applications more efficiently." +--- + +Hello everyone, the `GoFrame` framework has officially released version `v2.4.0` today! 👏👏👏👏 + +The biggest highlight of this version is the provision of **features for microservices development, development tools, and project scaffolding**, along with comprehensive **microservices development documentation**!! The design of microservices components continues to focus primarily on interface design to ensure good flexibility and extensibility. The interfaces are maintained in the main repository of the framework, while specific interface implementations are pragmatic and provided within community components. + +In fact, the feature functionality of this version has long been `Ready`, but due to the significant workload of documentation, it took about a month to complete it relatively thoroughly, thus adjusting the release date of the version accordingly. We believe that documentation is as important as code and is an integral part of a milestone release. We know how to use it well and hope to tell everyone how to use it well, to more pragmatically help more developers. This is one reason why `GoFrame` framework documentation has been able to accumulate abundantly over time. Furthermore, complete source code comments are primarily in English to assist overseas users. The official website documentation remains predominantly in Chinese to support major development teams in the Greater China region. + +Come and check out what we've updated! `Enjoy!` 🍺🍺🍺🍺🍺🍺🍺🍺 + +Full code changes: [v2.3.0...v2.4.0](https://github.com/gogf/gf/compare/v2.3.0...v2.4.0), thanks to all the contributing developers for this release: + +[![](/markdown/9c63586b568a8e84872c67b58aa9e559.png)](https://github.com/gogf/gf/releases/tag/v2.4.0) + +## New Features + +Official release of microservices development features and addition of a complete microservices development section on the official website: [Microservice Development](../docs/微服务开发/微服务开发.md) + +## Functional Improvements + +### Database Component + +1. `gdb` +1. Fixed the issue of automatic table structure query failure during cross-database operations: [https://github.com/gogf/gf/issues/2338](https://github.com/gogf/gf/issues/2338) +2. Fixed the `Namespace` configuration issue not taking effect under `pgsql`. +3. Fixed the issue of being unable to open `sqlite` database files when using the new unified configuration: [https://github.com/gogf/gf/issues/2435](https://github.com/gogf/gf/issues/2435) +4. Improved underlying database operation return logic, returning underlying errors through `gerror.Wrap` to ensure that the upper layer can obtain underlying custom error objects. +5. Fixed the issue of unsigned integer fields being converted to signed integer types when querying data tables: [https://github.com/gogf/gf/issues/2356](https://github.com/gogf/gf/issues/2356) +6. Fixed parsing issue of multi-level `Model` being used as subquery parameters during subqueries: [https://github.com/gogf/gf/issues/2339](https://github.com/gogf/gf/issues/2339) +7. Improved time maintenance feature, enabling the writing of full time (granularity down to nanoseconds) for write/update/delete operations. +8. Fixed unlimited execution issue under soft delete scenarios when given an empty `Where` condition: [https://github.com/gogf/gf/issues/2427](https://github.com/gogf/gf/issues/2427) +2. `gredis` +1. Fixed configuration handling and object initialization issues when creating objects. + +### Container Component + +1. `garray` +1. Added `Filter` method for customizing traversal and filtering of array elements. +2. Added `RemoveValues` method to support batch deletion of elements by parameter value. +3. Improved `InsertBefore` method to support batch parameter insertion capability. +2. `gmap` +1. Added `IsSubOf` method to determine if the current `map` is a subset of the specified `map`. +3. `gqueue` +1. Fixed `Len/Size` length calculation issue: [https://github.com/gogf/gf/issues/2509](https://github.com/gogf/gf/issues/2509) +2. Fixed concurrent safety issue with `Close` method: [https://github.com/gogf/gf/issues/2015](https://github.com/gogf/gf/issues/2015) + +### Network Component + +1. `gclient` +1. Added `SetDiscovery` and `SetBuilder` methods to allow callers to customize client service discovery and load balancing interface implementations. +2. `ghttp` +1. Improved support for reading specified parameters from `Header/Cookie` when receiving parameters, supporting the specification of reading `Header/Cookie` in the route's `in` tag (`in:header/cookie`). +2. Improved `ResponseWriter` to implement the `http.Flusher` interface, simplifying the user's `Stream` output development logic: [Response - Streaming](../docs/WEB服务开发/数据返回/数据返回-Stream返回.md) +3. Improved link tracing implementation logic to prevent issues from being ignored when an error occurs in reading submitted content internally. +4. Improved parameter reading logic to prevent `r.GetRequestMap()` from returning content that includes `form-data` form `body` information: [https://github.com/gogf/gf/issues/2261](https://github.com/gogf/gf/issues/2261) +5. Improved internal context reception logic: + 1. Original logic: Removed ignoring of the underlying `Request` `ctx` and created a new `ctx` supporting link tracing. + 2. Latest logic: Inherits the `ctx` object of the underlying `Request` and extends this `ctx` to support link tracing features. +6. Improved graceful shutdown logic to allow customizing the timeout of graceful shutdown. +7. Improved configuration feature to allow developers to configure custom service registration interface implementation objects. + +### System Component + +1. `gcmd` +1. Improved `AddObject` method to allow directly adding specified `*Command` or standardized `Object` objects as subcommands. +2. `gctx` +1. Fixed missing `TraceID` issue in `GetInitCtx` method: [Context](../docs/组件列表/系统相关/上下文-gctx.md) +3. `gfile` +1. Improved `Temp` method to align basic logic implementation with standard library `os.TempDir`, avoiding temporary directory conflict issues during single-machine collaboration. +4. `gtimer` +1. Added `Quick` option when creating timers to trigger the specified callback method upon adding timers. + +### Utility Component + +1. `gconv` +1. Fixed `Scan` method conversion failure issue in certain scenarios when fields are integer/floating point arrays: [https://github.com/gogf/gf/issues/2391](https://github.com/gogf/gf/issues/2391) +2. Fixed issue of `Interfaces` method directly returning `[]` when attribute is 0: [https://github.com/gogf/gf/issues/2395](https://github.com/gogf/gf/issues/2395) +3. Fixed conversion issue when `json tag` is `,omitempty` without variable name. +4. Fixed conversion failure issue when target type is a pointer to a custom basic type. +5. Fixed conversion failure issue of `gvar.Var` type to common third-party package `decimal.Decimal` type: [https://github.com/gogf/gf/issues/2584](https://github.com/gogf/gf/issues/2584) +6. Improved `Struct` method to resolve conversion failure issue when field type is `time.Time/*time.Time` and provided conversion value is a non-standard library-supported string like `2022-12-15 16:11:34`: [https://github.com/gogf/gf/issues/2371](https://github.com/gogf/gf/issues/2371) +2. `gtag` +1. Added `SetGlobalEnums/GetEnumsByType` methods for automated `Golang Enums` management. Requires `gf gen enums` command to work. +3. `gutil` +1. Fixed null pointer error issue in `Dump` method under certain scenarios: [https://github.com/gogf/gf/issues/2487](https://github.com/gogf/gf/issues/2487) +4. `gvalid` +1. Added `Field` field in custom validation method parameters to address field missing issues in custom validation error prompts: [https://github.com/gogf/gf/issues/2499](https://github.com/gogf/gf/issues/2499) + +### Community Component + +#### Database Driver + +1. Improved `dm/mysql` component to resolve `QueryEscape` issue when special characters (e.g., `/`) exist in timezone configuration. + +#### NoSQL Adapter + +1. Fixed missing connection pool configuration parameters in `redis` component configuration. + +#### Service Registration Discovery + +1. Added `file` registration discovery component for file-based service registration discovery locally, typically used for single-node testing. +2. Improved implementation details of `etcd/polaris/zookeeper` and refined single test cases. + +#### Microservice Scaffolding + +1. Added `grpcx` microservice component for microservice development using the `grpc` communication protocol: [Microservice Development](../docs/微服务开发/微服务开发.md) + +## Development Tools + +1. Added `gf gen pb` command for compiling `proto` files to generate `go pb` files: [Protobuf Compilation](../docs/开发工具/代码生成-gen/协议编译-gen%20pb.md) +2. Added `gf gen pbentity` command for automatically generating `proto` data structure definition files for database tables: [DB Table To Protobuf](../docs/开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) +3. Added `gf gen enums` command for parsing specified directory `go` files and automatically generating `enums` data files based on enumeration definition specifications, mainly used for `OpenAPI` interface documentation display (experimental feature): [Enums Maintenance](../docs/开发工具/代码生成-gen/枚举维护-gen%20enums.md) +4. Improved `gf up` command by adding tool `CLI` automatic upgrade feature. +5. Improved `gf gen service` command to stop auto-generating methods into the interface definition file after the method's comment. +6. Improved `gf build` command by adding `DumpENV` option to control whether to print environment information used during compilation, with default setting off. +7. Improved `gf docker` command by adding `Tag` option to ensure compatibility with older versions. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.5 2023-07-17.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.5 2023-07-17.md new file mode 100644 index 00000000000..21fa1f915f9 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.5 2023-07-17.md @@ -0,0 +1,85 @@ +--- +slug: '/release/v2.5.0' +title: 'v2.5 2023-07-17' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame, GoFrame framework, v2.5, Golang, gf gen ctrl, API interface, code generation, database, logging component, OpenTelemetry] +description: "GoFrame framework has released version v2.5.0, enhancing development tools and functional components, adding the gf gen ctrl command to standardize API interface development and code generation, supporting Golang project interface standardization, and improving core modules such as logging and database. Multiple optimizations have also been made to community components." +--- + +Hello everyone, the `GoFrame` framework has officially released version `v2.5.0` today! 👏👏👏👏 + +This version mainly focuses on improvements to existing functional components and development tools, including: + +- The development tool introduces a new `gf gen ctrl` command to standardize the definition and development of API interfaces, and generate code for controllers and SDKs to improve development efficiency, addressing issues of interface standardization and efficiency in `Golang` project development. For details, please refer to: [Controller Generating](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md). +- The existing `gf gen dao` command adds a `TypeMapping` feature, allowing developers to customize the `Golang` type of entity object properties generated from database tables: [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + +For other changes, please refer to the following `change log`. `Enjoy!` 🍺🍺🍺🍺🍺🍺🍺🍺 + +`Github ChangeLog`: [https://github.com/gogf/gf/releases/tag/v2.5.0](https://github.com/gogf/gf/releases/tag/v2.5.0) + +Full code changes: [v2.4.0...v2.5.0](https://github.com/gogf/gf/compare/v2.4.0...v2.5.0), thanks to all the contributing developers for this release: + +[![](/markdown/3b87419b0ede464629f3813de922d965.png)](https://github.com/gogf/gf/releases/tag/v2.4.0) + +## Feature Improvements + +1. `gdb` + 1. Improved `ORM SQL` logging, with operations executed according to the order defined in the table fields. + 2. Improved `HOOK` method implementation, now supports modifying the `Table` field in the `in` parameter to change the executed table name. + 3. Added `AllAndCount/ScanAndCount` methods for convenient pagination query scenarios. + 4. Added `Model.WhereOrNot/WhereOrPrefixNot` condition methods. +2. `gi18n` + 1. Improved support for Chinese ( `Unicode` ) as key names for translation. +3. `gclient` + 1. Added `Discovery` chain operation method for setting service discovery components for the current request. +4. `ghttp` + 1. Improved request `Context` processing, where each change in `Context` will affect the underlying `http.Request` object. This supports custom `HTTP Handler` data interaction scenarios. + 2. Added support for `Endpoints` configuration item to customize the service registration discovery address of the `Server`, allowing the use of the currently listening address. +5. `goai` + 1. Improved parameter validation recognition, marking required parameters in the `OpenAPIv3` results. +6. `gsel` + 1. Fixed locking mechanism issues for `Endpoints` updates in the `RoundRobin` implementation. +7. `glog` + 1. Added `TimeFormat` configuration for customizing the time format of log output: [Logging - Configuration](../docs/核心组件/日志组件/日志组件-配置管理.md) + 2. Improved `Rotation` implementation to support log file splitting for short-running programs. +8. `gtag` + 1. Added `GetGlobalEnums` method to obtain globally registered enum types. +9. `gutil` + 1. Added `DumpJson` method to format and print any type of variable to the terminal in `JSON`, making it easier for human reading. +10. `gvalid` + 1. Added `enums` validation rule for automatic recognition and validation of enum types: [Data Validation - Rules](../docs/核心组件/数据校验/数据校验-校验规则.md) + +## Community Components + +1. Fixed load balancing issue of `contrib/registry/polaris` component when there are multiple servers. +2. Improved `contrib/drivers/pgsql` to have the `Index` field in `TableFields` return start uniformly from `0`. +3. Improved `contrib/nosql/redis` with added user configuration support. +4. Improved `contrib/rpc/grpcx` component, with the addition of `Endpoints` configuration support for `grpcx.Server`, enabling customizable service registration discovery addresses. +5. Added `contrib/sdk/httpclient` component, dependent library for `HTTP SDK` code files generated by the newly added `gf gen ctrl` command in this version. +6. Added `contrib/trace/otlpgrpc` and `contrib/trace/otlphttp` components for unified connection of tracing based on `OpenTelemetry`. + +## Development Tools + +1. Added `gf gen ctrl` command for compiling `api` definition directories, automatically generating standardized `controller`, `HTTP SDK` code: [Controller Generating](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md) +2. Improved `gf gen dao` command, added `TypeMapping` feature, allowing developers to customize the mapping of data table field types and generated `Go` entity data structure property types, and conveniently introduce third-party package types (such as `decimal` package for high-precision types): [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +3. Improved `gf gen enums` command, changed `Prefix` parameter to `Prefixes` to support specifying prefixes for multiple generated enum type packages: [Enums Maintenance](../docs/开发工具/代码生成-gen/枚举维护-gen%20enums.md) +4. Improved `gf gen service` command: + - Added method comment generation in the generated `service` file. + - When there is an `import` conflict in the generated `service` file, automatically generate `import alias`. +5. Improved command-line packaging, exposing `gfcmd.Command` type for developers to extend custom command-line functions. +6. Improved `gf docker` command, setting build file parameters as non-mandatory (for compatibility), which will be used only for `Docker` builds in the future without coupling binary build functions. For full build requirements, it is recommended to use it with `gf build`. Updated `make image` command in the project template, using `gf build+gf docker` commands. +7. Improved `gf init` command, fixed issues in some scenarios where initializing projects covered existing `.git/.gitignore` directories and files or permission errors. +8. Improved `gf up` command, fixed issues with framework version updates in certain scenarios, and installation problems under the `windows` system. +9. Improved `gf version` command, fixed issues with framework version recognition in some scenarios. +10. Fixed `gf gen pbentity` command, modifying the `float32/float64/[]byte` types in the generated `proto` file entity data structure to `float/double/bytes` types. +11. Improved development tools, some commands do not need to explicitly configure `importPrefix` parameter, such as: `gf gen dao/service` + +## Compatibility Notice + +1. In `ghttp.Request`, the `Context` and the `context.Context` returned by `GetCtx` method have removed the nesting of `NeverDoneCtx`, meaning the `ctx` context object passed by default in the controller fully inherits from the `ctx` in the standard library's `http.Request`. The `Done` method will be automatically called to terminate it at the end of a request, and this `ctx` cannot be propagated to async processes that need further execution. Therefore, starting with this version, users might encounter the following two issues: + - To propagate to **async processes or maintain compatibility with previous logic**: Add a middleware that calls `r.SetCtx(r.GetNeverDoneCtx())` to globally override the subsequent `ctx` usage with a never-ending `ctx`. + +![](/markdown/f9b3d06ba28250f95ac7c5c87df1d680.png) + +- When the client actively cancels the request, the server might encounter a `context canceled` error. This is normal, and when the client no longer needs the request result, it will cancel the request, continuing to execute on the server-side makes no sense. If you mind this error, you can refer to the middleware above to add `NeverDoneCtx`, and the server will ignore the client's cancel and continue to execute. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.6 2023-12-19.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.6 2023-12-19.md new file mode 100644 index 00000000000..b27bd6c5caa --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.6 2023-12-19.md @@ -0,0 +1,118 @@ +--- +slug: '/release/v2.6.0' +title: 'v2.6 2023-12-19' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,v2.6.0,Code Improvement,Bug Fix,Database Implementation,Service Registration Discovery,Microservice Components,Development Tools,Asynchronous Goroutine,Log Printing] +description: "GoFrame framework releases version v2.6.0, upgrading Golang version, improving asynchronous goroutine, introducing structured log printing, expanding database implementation and service registration components, optimizing development tools, and error handling module. Additionally, it improves gmutex mutex operation and gcfg configuration management, enhancing the framework's stability and feature richness." +--- + +Hello everyone, the `GoFrame` framework has officially released version `v2.6.0` today!👏👏👏👏 + +This release mainly involves a lot of code improvements and `BugFix` work. Due to the numerous changes in this version, the following is a Chinese introduction to some of the more important improvements. For a detailed `ChangeLog`, please refer to (especially for `BugFix`): [https://github.com/gogf/gf/releases/tag/v2.6.0](https://github.com/gogf/gf/releases/tag/v2.6.0) + +Complete code changes: [https://github.com/gogf/gf/compare/v2.5.0...v2.6.0](https://github.com/gogf/gf/compare/v2.5.0...v2.6.0) + +Thanks to all the contributing developers in this release: + +![](/markdown/8cbb41ea81e456eb8a5a145520c9462a.png) + +## Feature Improvements + +The minimum required Golang version for the framework has been upgraded from `v1.15` to `v1.18`. + +1. `g` + 1. Added the `g.Go` method to conveniently create asynchronous `goroutines` with `ctx` and `recover` parameters. +2. `glog` + 1. Improved the `Handler` callback processing function's `HandlerInput` input parameters by adding `Values` parameter, which is the input parameter list for log printing: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 2. Added a general method `HandlerStructure` to print log content with structured parameters: [Logging - Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 3. Improved the log file `rotate` logic to solve the problem of failing to `rotate` files in certain scenarios. +3. `gerror` + 1. Added error stack mode (`brief/detail`): In `brief` mode, the error stack will only print non-framework component stacks. In `detail` mode, the error stack will print the complete framework code call chain. The framework uses `brief` mode by default: [Error Handling - Other Features](../docs/核心组件/错误处理/错误处理-其他特性.md) +4. `gcode` + 1. Added `gcode.CodeInternalPanic` error code, and all `panic` errors captured by framework components will return with this error code. +5. `gmap` + 1. Added `Diff` method for comparing and returning the differences between two `Map` objects. +6. `gaes` + 1. Added `PKCS7Padding/PKCS7UnPadding` methods. +7. `gdb` + 1. Removed `ConvertDataForRecord` conversion method, added `ConvertValueForField` conversion method. + 2. Modified the `CheckLocalTypeForField` method, changing the return parameter type from `string` to `LocalType`. + 3. These are mainly used for database implementations and usually in community components. If a user has local implementations for the `gdb.DB` interface, they should be aware of this change. + 4. Added `Model.Partition` method, allowing users to explicitly specify partition parameters during database operations. + 5. Added `Model.LeftJoinOnFields/RightJoinOnFields/InnerJoinOnFields` methods, making it easier to implement `Join` operations. + 6. Fixed the implementation issue of the `Model.WherePrefixNotIn` method. +8. `gredis` + 1. Added `Cluster` configuration option to specify whether to use cluster mode: [Redis - Configuration](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) + 2. Added `Protocol` configuration option to specify the `RESP` version: [Redis - Configuration](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) +9. `gi18n` + 1. Improved translation file reading logic, supporting automatic file reading from resource managers: [I18N - Configuration](../docs/核心组件/I18N国际化/I18N国际化-配置管理.md) +10. `gclient` + 1. Added `NoUrlEncode` feature, allowing `GET` requests to not automatically perform `UrlEncode` on parameters. +11. `ghttp` + 1. Improved exit signal handling to support graceful exit upon capture of exit signals on the Windows platform. +12. `goai` + 1. Supports automatic recognition of `ghttp.UploadFile` type as `OpenAPIv3` `File` type. + 2. Removed redundant description information from `Path` objects and `Method` objects. + 3. Converts the example field type of the interface into the corresponding data type based on the parameter data type. +13. `gcfg` + 1. Added `AdapterContent` configuration interface implementation, allowing configuration management through specific configuration content: [Configuration - AdapterContent](../docs/核心组件/配置管理/配置管理-接口化设计/配置管理-AdapterContent.md) +14. `gctx` + 1. Added `NeverDone` method, which wraps a given `ctx` object and returns a `ctx` object that never expires or is `Cancelled`. +15. `gfile` + 1. Changed the default file creation mode from `0777` to `0755`. + 2. Improved `Copy/CopyFile/CopyDir` methods, added `CopyOption` optional parameter to control the copy logic options. +16. `gmutex` + 1. Improved `gmutex.Mutex` object using `Golang`'s new version of `mutex`, directly utilizing the `TryLock/TryRLock` from the new version standard library. The methods `LockFunc/TryLockFunc` are retained. + 2. Added `gmutex.RWMutex` object, extending the standard library's `sync.RWMutex` object, with new methods `LockFunc/TryLockFunc, RLockFunc/TryRLockFunc`. +17. `gstr` + 1. Added `List2/ListAndTrim2/List3/ListAndTrim3` methods, similar to the `PHP list` functionality, which splits a string and returns it as multiple result values. + 2. Added `CaseConvert` method to perform string naming format conversion according to a given `CaseType` type parameter. +18. `gconv` + 1. Added `ConvertWithRefer` method, which uses the given parameter as a type reference and converts the given parameter to the specified type. +19. `gutil` + 1. Added `FillStructWithDefault` method, which automatically fills the given struct object/pointer with default values by reading the `struct tag`. +20. `gvalid` + 1. Fixed the issue with `enums` validation rule not supporting `map` parameter types. + +## Community Components + +### Configuration Management + +1. Added `contrib/config/consul` component, implemented `consul` service for configuration management component interface: [https://github.com/gogf/gf/tree/master/contrib/config/consul](https://github.com/gogf/gf/tree/master/contrib/config/consul) + +### Database Implementation + +1. Improved `contrib/drivers/dm` component: +1. Supports `schema` parameter configuration. +2. Supports `time.Time/*time.Time` time type parameter operations. +2. Improved `contrib/drivers/sqlite` component to support `Insert Ignore` and `Save` operations. +3. Added `contrib/drivers/sqlitecgo` component, supporting `i386` system architecture through `cgo`. +4. Improved `contrib/nosql/redis` component: +1. Added `TLSConfig` configuration to support `TLS` connection to `Redis Server`. +2. Added `Protocol` configuration option to support the latest version of `Redis Server`. + +### Service Registration Discovery + +1. Added `contrib/registry/nacos` component, implementing microservice registration and discovery using `nacos`: [https://github.com/gogf/gf/tree/master/contrib/registry/nacos](https://github.com/gogf/gf/tree/master/contrib/registry/nacos) +2. Improved `contrib/registry/file` component to automatically delete expired registrations, preventing clients from discovering and connecting to expired server addresses. +3. Fixed some implementation issues in the `contrib/registry/polaris` component. + +### Microservice Components + +1. Improved `contrib/rpc/grpcx` component: +1. Clients support directly providing connection addresses to access the server. +2. Enhanced unit tests to improve code quality. + +## Development Tools + +1. Improved the installation method of the `cli` tool to additionally support `go install` installation: `go install github.com/gogf/gf/cmd/gf/v2@latest` +2. Improved `gf run` command by adding `WatchPaths/-w` configuration, allowing specified path list monitoring to avoid the `too many opened files` issue caused by monitoring all files in local projects by default: [Auto Compiling](../docs/开发工具/自动编译-run.md) +3. Improved `gf gen ctrl` command by adding `Merge/-m` option to control the generation of controller code files according to the `api` layer rather than splitting them into different files based on `api` interfaces: [Controller Generating](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md) +4. Improved `gf gen dao` command by adding `RemoveFieldPrefix/-rf` option to automatically remove the prefix of generated table field names: [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +5. Improved `gf gen pbentity` command by adding `RemoveFieldPrefix/-rf` option to automatically remove the prefix of generated table field names: [DB Table To Protobuf](../docs/开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) +6. Improved `gf gen service` command to automatically generate method comments for objects in the `logic` module into the `service` interface file. +7. Improved `gf version/gf -v` command for more detailed tool version, runtime environment, and framework information. +8. Improved the initialization efficiency of development tools, removing `init` package method logic that affected initialization efficiency. +9. Fixed the issue of `Link` database configuration failure in the `gf gen dao` command. +10. Other detailed fixes. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.7 2024-04-09.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.7 2024-04-09.md new file mode 100644 index 00000000000..4f3f08d82e5 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.7 2024-04-09.md @@ -0,0 +1,103 @@ +--- +slug: '/release/v2.7.0' +title: 'v2.7 2024-04-09' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,metric monitoring,OpenTelemetry,database support,scheduled tasks,gcron,HTTP monitoring,middleware,code generation,gmetric component] +description: "GoFrame framework releases version v2.7.0, introducing a new metric monitoring component and providing implementations for monitoring metrics of HTTP Client and Server, supporting Save operations for multiple databases, improving gcron scheduled task component, and enhancing gdb database component functionality. Meanwhile, several system issues have been fixed, and the framework's performance and stability have been improved." +--- + +Hello everyone, the `GoFrame` framework has officially released version `v2.7.0` today! 👏👏👏👏👏👏👏👏👏 + +The highlight of this version is the provision of the `metric` monitoring component. The main library offers an interface-based `metric` design, and the community component provides an `OpenTelemetry`-based `metric` interface implementation. This feature is disabled by default and only enabled by default when specific interface implementations or community implementations are introduced. The current version also provides implementations for monitoring metrics of `HTTP Client&Server`, and metrics for other components will be provided in subsequent versions. For details, please refer to the documentation: [Service Metrics](../docs/服务可观测性/服务监控告警/服务监控告警.md). + +Additionally, this version implements support for the `Save` operation for `dm/mssql/oracle/pgsql/sqlite` databases, thanks to community member [https://github.com/oldme-git](https://github.com/oldme-git) 💖. + +Moreover, it is worth mentioning that in this version, we have added an ignore symbol `#` for the second-level field of the `gcron` scheduled task component, used to convert the `6`-segment cron pattern into a `5`-segment Linux crontab pattern, addressing the issue of task execution inaccuracy due to delay at the second-level granularity: [Cron Job - Expressions](../docs/组件列表/系统相关/定时任务-gcron/定时任务-表达式.md). + +Due to the numerous changes in this version, below is an introduction to some important improvements in Chinese. For the detailed `ChangeLog`, please refer to: [https://github.com/gogf/gf/releases/tag/v2.7.0](https://github.com/gogf/gf/releases/tag/v2.7.0). + +For the complete code changes, please refer to: [https://github.com/gogf/gf/compare/v2.6.0...v2.7.0](https://github.com/gogf/gf/compare/v2.6.0...v2.7.0). + +Thanks to all the contributors who participated in this version 💖! + +![](/markdown/950e1af6550f59942ab68e09ffb63c72.png) + +## Component Improvements + +1. `gdb` + 1. Added `Stats` interface definition and implementation for obtaining connection pool information maintained by the current database `orm` object: [ORM Senior - Connection Pool](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-连接池状态.md) + 2. Added `FormatUpsert` interface definition and implementation, used for writing/updating operations for different database types, i.e., `Save` operation. + 3. Added `SqlType` type, changing the existing `sql type` type definition from `string` to `SqlType` type. + 4. Added `Model.OnConflict` method for implementing update policy when field unique key conflicts for some database types, particularly for `Save` operation. + 5. Fixed the issue where the `ClearTableFieldsAll` method was not effective. +2. `ghttp` + 1. Added `MiddlewareNeverDoneCtx` middleware, which developers can choose to use to avoid issues related to `context cancel` on the server side when clients cancel requests: [FAQ](../docs/WEB服务开发/常见问题.md) + 2. Added monitoring metric implementation for `http server`, which is disabled by default and does not affect performance, unless the `metrics` feature is enabled: [HTTPServer - Metrics](../docs/WEB服务开发/高级特性/HTTPServer-监控指标.md) + 3. Improved `tracing` records, changing the `span` name from `query uri` to `route uri` for easier aggregation when viewing. + 4. Changed the type of `EnterTime` and `LeaveTime` properties in the `Request` object from `int64` to `*gtime.Time`. + 5. Marked the `WebSocket` method as deprecated, it will be removed in future major versions, along with the removal of `http server's` built-in coupling support for `websocket`. It is recommended to use other open-source `websocket` components with `http server` for more decoupled flexibility in the future. + 6. Fixed a memory issue caused by large file uploads due to the `Request Body` being readable multiple times by default. + 7. Fixed the issue where the `pattern` parameter of the `StartPProfServer` method was ineffective. + 8. Added the `Access-Control-Expose-Headers Header` return to the `Request.ServeFileDownload` method to support `ajax` file requests. + 9. Added `SwaggerUITemplate` configuration option to the service configuration for quickly configuring the `HTML` content of the `SwaggerUI` page. + 10. The `http server` no longer prints built-in middleware when starting route printing. +3. `gclient` + 1. Fixed the issue that forced all `http` requests to go through service discovery domain name resolution when service discovery is enabled. + 2. Added monitoring metric implementation for `http client`, which is disabled by default and does not affect performance, unless the `metrics` feature is enabled: [HTTPClient - Metrics](../docs/WEB服务开发/HTTPClient/HTTPClient-监控指标.md) +4. `gcron` + 1. Added an ignore symbol `#`, using it as a placeholder for the **second-level field** in the `cron pattern`, indicating the ignore of the second-level field, converting a `6`-segment `cron pattern` to a `5`-segment `linux crontab pattern`: [Cron Job - Expressions](../docs/组件列表/系统相关/定时任务-gcron/定时任务-表达式.md) + 2. Fixed an issue where specified second-level tasks (for example `2 * * * * *`) might execute twice if there is inaccurate time at the underlying level. +5. `gerror` + 1. Fixed an issue with the recursive logic being ineffective in the `gerror.HasCode` method. +6. `g` + 1. Improved the `DumpJson` method for printing any variables in `JSON` format. +7. `gcache` + 1. Fixed an issue caused by excessive `goroutine` due to the creation of a large number of `Cache` objects. +8. `gcmd` + 1. Added `RunWithSpecificArgs` method, used to run command objects with custom `arguments`. + 2. Fixed the issue of parameter loss due to conflicts between parameter struct names and struct tag names in some scenarios. +9. `gfsnotify` + 1. Fixed a `panic` issue that might occur when the `gfsnotify` object is closed. +10. `glog` + 1. Fixed the redundant `rotate` issue of `.gz` suffix log compressed files related to the `rotate` feature. +11. `gmetric` + 1. Added `gmetric` component using a decoupled design, only providing the interface definition and `Noop` implementation, with the real implementation in community components. The `metric` feature will only be enabled when specific implementations are introduced: [Service Metrics](../docs/服务可观测性/服务监控告警/服务监控告警.md) +12. `gproc` + 1. Fixed an issue where process parameter parsing might fail under `windows`. + 2. Improved `Signal` signal listening implementation, allowing adding signal handling methods at runtime. +13. `gview` + 1. Fixed a robustness issue caused by the `os.Getwd` method execution failure. +14. `gconv` + 1. `json.RawMessage` supports receiving parameter conversions for the `slice` type. + 2. Fixed a recursive conversion failure issue caused by missing the `Deep` parameter in the `MapDeep` internal conversion. + 3. Improved the internal `Map` traversal logic using `MapRange`, enhancing execution performance. + +## Community Components + +1. Improvements to the community `contrib/drivers` database components: + 1. Added support for the `Save` operation to the `contrib/drivers/dm` component. + 2. Added support for the `Save` operation to the `contrib/drivers/mssql` component. + 3. Added support for the `Save` operation to the `contrib/drivers/oracle` component and fixed an issue with write parameters not supporting the `gdb.Raw` type. + 4. Added support for the `Save` operation to the `contrib/drivers/pgsql` component. + 5. Added support for the `Save` operation to the `contrib/drivers/sqlite` component. + 6. Added support for the `Save` operation to the `contrib/drivers/sqlitecgo` component. +2. Added the `contrib/metric/otelmetric` component to support `OpenTelemetry Metric`: [Service Metrics](../docs/服务可观测性/服务监控告警/服务监控告警.md) +3. Improvements to the `contrib/nosql/redis` component: + 1. Added `SentinelUsername` and `SentinelPassword` parameter configurations to expand support for the `Redis Sentinel` mode: [Redis - Configuration](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) + 2. Improved the `Redis` interface implementation, allowing developers to flexibly customize and extend the implementation of the community component object `redis.Redis` type: [Redis - Interface](../docs/组件列表/NoSQL%20Redis/Redis-接口化设计.md) +4. Improved the `contrib/registry/etcd` component, allowing developers to configure verification information for `etcd` connections. +5. Improvements to the `contrib/rpc/grpcx` component: + 1. Safely truncate request content when the `tracing` feature is enabled. + 2. Added support for the `logger` configuration option, allowing configuration of the `grpc server` log object through the `logger` configuration option in the configuration file: [GRPC Server Configuration](../docs/微服务开发/服务端配置.md) +6. Improvements to the `contrib/trace/otlphttp` and `contrib/trace/otlpgrpc` components, fixing data loss issues of `trace` that may occur even with normal `ShutDown` in short process scenarios. + +## Development Tools + +1. Improved `gen dao` generated `entity` source file by adding `orm` tags to enhance the efficiency of converting database query results into `entity` objects. +2. Improved the `gen service` command, fixing the inconsistent method order issue in the generated source files. +3. Improved the `build` command, changing the default value of the generated binary file storage directory `path` parameter from `./temp` to `.` to address the invalidation issue of the custom parameter. +4. Improved the `init` command by adding the `-module/g` parameter to explicitly specify the `go module` name when initializing a project. +5. Fixed an issue with `gen dao` deleting generated `dao` source files when using the `clear` parameter in multiple database generation configuration. +6. Fixed an issue with the `gen pbentity` command where the custom `jsonCase` parameter was invalid. +7. Fixed an issue with the `run` command's `-w` specified listening directory parameter being ineffective. \ No newline at end of file diff --git a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.8 2024-11-18.md b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.8 2024-11-18.md new file mode 100644 index 00000000000..6453de27c05 --- /dev/null +++ b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/v2.8 2024-11-18.md @@ -0,0 +1,137 @@ +--- +slug: '/release/v2.8' +title: 'v2.8 2024-11-18' +sidebar_position: -1 +hide_title: true +keywords: [GoFrame,Golang,v2.8,Component Improvement,Compatibility Notice,Database,Network Service,File Monitoring,Data Validation,Development Tools] +description: "This version update includes several important improvements, including the minimum Golang version requirement adjustment from 1.18 to 1.20, the deprecation of the gring component, the removal of the jaeger component, improvements in gjson parameter forms, and increased flexibility for ghttp and gudp. New features include database connection support for unix socket, time field type support, the addition of the Exist method in gdb, enhanced gconv conversion performance, improved gvalid validation rules and gtest assertion methods, enhanced gcron task management, and significantly improved functionality and extensibility of GoFrame development tools." +--- + +We are pleased to announce the official release of `GoFrame` version `v2.8.0`! This update brings significant improvements and new features, enhancing `GoFrame` in terms of development efficiency, execution performance, stability, and extensibility. + +## Version Highlights + +1. **Compatibility Update** + - `GoFrame v2.8.0` requires a minimum `Golang` version of `1.20` to provide more efficient performance and stability. + - The `container/gring` component has been deprecated, and the `contrib/trace/jaeger` component has been removed from the source code repository, simplifying dependency management. + +2. **Component Improvements** + - The `database/gdb` module now supports `created_at/updated_at/deleted_at` integer timestamp fields, `unix socket` connections, `time/year` field types, and introduces the `Model.Exist` method, significantly improving the flexibility and efficiency of database operations. + - The `util/gconv` component leverages type caching to enhance conversion performance, with conversion performance for complex data types improved by approximately `300%`. + - The `net/ghttp` and `net/gudp` network service components have optimized parameter configurations and request processing logic, providing a more streamlined development experience. + +3. **Development Tools Upgrade** + - The `gf init` command supports generating a multi-application project structure within a single repository. + - Optimizations to commands like `gf gen ctrl`, `gf gen dao`, and `gf run`, further enhance code generation flexibility and execution efficiency, simplifying project setup processes. + +4. **Community Component Support** + - Added support for the latest features in multiple community drivers such as `contrib/drivers/mssql` and `contrib/registry/etcd`, improving integration with external services. + +## Future Outlook + +The `GoFrame` team thanks all community users for their support. We will continue to listen to community feedback to provide users with richer features and more efficient development support. + +## Special Thanks + +💖💖💖 Thanks to all the contributing developers for this release 💖💖💖 + +![alt text](QQ_1731813654454.png) + +# Main Content + +This release contains numerous changes. Below are the major updates in this version; for a detailed `Change Log`, please refer to: +[https://github.com/gogf/gf/releases/tag/v2.8.0](https://github.com/gogf/gf/releases/tag/v2.8.0) + +For comprehensive code changes, please refer to: [https://github.com/gogf/gf/compare/v2.7.0...v2.8.0](https://github.com/gogf/gf/compare/v2.7.0...v2.8.0) + +## Compatibility Notice +1. The minimum `Golang` version requirement has been adjusted from `1.18` to `1.20`. +2. The `container/gring` component is marked as deprecated and will no longer be maintained. +3. The `contrib/trace/jaeger` component has been removed from the source code repository. +4. The parameter of `Load*` methods in the `encoding/gjson` component has been adjusted from `interface{}` to `[]byte` to improve performance. +5. The port parameter of the `StartPProfServer` method in the `net/ghttp` component has been changed from `port int` to `address string` for increased flexibility. +6. The `net/gudp` component has been refactored, with individual method parameter adjustments: [UDP](../docs/组件列表/网络组件/UDP组件/UDP组件.md) + +## Component Improvements +1. `database/gdb` + - Time maintenance features have been enhanced to support integer fields. When `created_at/updated_at/deleted_at` are integer fields, they will be updated with timestamps. For details, see: [ORM Model - Time Fields](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-时间维护/ORM链式操作-时间维护.md) + - Added the `Model.Exist` method to determine if data that meets given conditions exists: [Model Query - Exist](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Exist.md) + - Added support for `time/year` field types in the database: [ORM Senior - Type Recognition](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-类型识别.md) + - Added the `OrderRandomFunction` interface method, implemented and supported the `OrderRandom` sorting method for common databases. + - Improved the `Model.Fields` method to support `gdb.Raw` type parameters: [ORM Model - Fields Retrieval](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段获取.md) + - Enhanced the `With` feature in `orm` tags to support `unscoped`: [Model Association - With](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) + - Added support for `unix socket` local database service connections in the configuration file. + - Removed `sql` statements from trace information to reduce the size of the `trace` package. + - Improved performance of query result conversion to `struct`. + +2. `net/ghttp` + - Removed `XxxReq/XxxRes` naming restrictions for input and output objects in route registrations. + - Adjusted the port parameter of `StartPProfServer` from `port int` to `address string` for increased flexibility. + - Removed request/response data from trace information to reduce the size of the `trace` package. + - Fixed graceful shutdown issues with the `/debug/admin/shutdown` interface. + +3. `net/goai` + - Allowed specifying parameter types in request/response structure attributes using the `type` tag. + +4. `container/gtree` + - Refactored `gtree` implementation using the third-party data structure component `github.com/emirpasic/gods` to enhance extensibility and maintainability. + +5. `encoding/gjson` + - Adjusted `Load*` method parameters from `interface{}` to `[]byte` to improve performance. + +6. `os/gcron` + - Added `StopGracefully` method to allow waiting for currently executing scheduled tasks to complete before stopping: [Cron Job - Usage](../docs/组件列表/系统相关/定时任务-gcron/定时任务-基本使用.md) + +7. `os/gfsnotify` + - Improved file recursive monitoring implementation so that when a directory is being monitored, newly created subdirectories within it, or their subdirectories, will also be recursively monitored: [File Watching](../docs/组件列表/系统相关/文件监控-gfsnotify/文件监控-gfsnotify.md) + +8. `test/gtest` + - Improved `AssertIN/AssertNI` assertion methods by adding support for substring containment assertions. + +9. `util/gvalid` + - Added `required-if-all` validation rule, where a parameter is mandatory if all specified parameters and their values are equal: [Data Validation - Rules](../docs/核心组件/数据校验/数据校验-校验规则.md) + - Enhanced `phone` validation rule by adding support for `171` series phone numbers. + +10. `util/gconv` + - Enhanced conversion performance with type caching, improving conversion performance for complex data types by approximately `300%`. + +## Community Components +1. `drivers/mssql` + - Changed the base `driver` from `github.com/denisenkom/go-mssqldb` to the official component `github.com/microsoft/go-mssqldb`. + +2. `contrib/drivers/pgsql` + - Added support for `InsertIgnore` operations. + - Added support for operations from `Golang` array types to database array field types. + +3. `contrib/registry/etcd` + - Added `DialTimeout` and `AutoSyncInterval` configuration options. + +4. `contrib/registry/nacos` + - Replaced the dependency on `github.com/joy999/nacos-sdk-go` with the official component `github.com/nacos-group/nacos-sdk-go/v2`. + +5. `contrib/rpc/grpcx` + - Removed request/response data from trace information to reduce the size of the `trace` package. + - Since the `grpc` component has enabled the `grpc.Dial` method, `grpc.NewClient` is now used to replace `grpc.Dial`. + +6. `contrib/sdk/httpclient` + - Added `Handler` interface to allow users to customize the handling of `HTTP Client` return data. + +## Development Tools +1. Improved the `gf init` command by adding the `-a/monoApp` option for creating new application project templates in a large repository: [Project Scaffold](../docs/开发工具/项目创建-init.md) + +2. Enhanced the `gf pack` command by adding support for command parameter options from a configuration file, with the configuration path as `gfcli.pack`. + +3. Enhanced the `gf tpl` command by adding support for command parameter options from a configuration file, with the configuration path as `gfcli.tpl.parse`. + +4. Improved the `gf gen ctrl` command by re-implementing parsing logic using `AST`, increasing the accuracy of generated `Go` code file content, and enhancing the extensibility of the feature. + +5. Improved the `gf run` command: + - Added support for command parameter options from a configuration file, with the configuration path as `gfcli.run`. + - Automatically deletes temporary old process binary files when a new process replaces the old one at the end of a temporary subprocess. + +6. Improved the `gf gen dao` command: + - Added `field mapping` feature to support specifying field configurations for generated `Golang` data types: [Dao/Do/Entity Generating](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + - Automatically identifies integer fields with a length of `1`, such as `bit(1)/tinyint(1)/int(1)`, as `bool` type `Golang` data types. + - Automatically reads the directory name of generated code files as the package name for generating `dao/do/entity` files. + - Due to database restrictions, the `cli` has removed default support for the `dm` database. If needed, please manually modify the source code to install `cli`. + - Fixed the issue where the `link` parameter becomes invalid when both the `link` parameter and configuration file exist simultaneously. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" new file mode 100644 index 00000000000..76fcb5f8d69 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" @@ -0,0 +1,26 @@ +--- +slug: '/release/v0.1.0' +title: 'v0.1 2018-04-23' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame framework,gfsnotify,configuration management,template engine,gconv,gpage pagination,ghttp,gredis package,gdb database ORM] +description: "v0.1 version update release, adding multiple modules and improving existing features, including GoFrame framework's gfsnotify file monitoring, configuration and template engine update mechanism, gconv type conversion, gpage pagination management, diversified log processing, gredis encapsulation of Redis operations, enhanced ghttp and gdb functionalities, providing detailed development documentation, and fixing multiple issues." +--- + +1. Added gfsnotify file monitoring module; +2. Configuration management module adds automatic update detection mechanism for configuration files; +3. Template engine adds automatic update detection mechanism for template files; +4. Improved gconv package basic type conversion functionality to enhance conversion performance; +5. Added gpage pagination management package supporting dynamic pagination, static pagination, and custom pagination style features; +6. ghttp.Request adds the Exit method to mark service exit. When called before service execution, the service will no longer execute; +7. ghttp.Response removes the WriteString method, using the Write method uniformly for data stream return, with flexible parameter form; +8. Template engine adds template variable exposure interface LockFunc/RLockFunc to support developers in handling template variables flexibly; +9. ghttp.Server adds access & error log functionality and supports developers in customizing log processing callback function registration; +10. Added gredis package, supporting redis client operation encapsulation, and adding gredis.Redis object to gins singleton manager for unified configuration management maintenance; +11. gins singleton manager adds automatic update detection mechanism for singleton object configuration files. When the configuration file changes externally, it automatically refreshes the singleton objects in the manager; +12. gdb database ORM package adds And/Or conditional chain methods and improves Where/Data method parameter flexibility; +13. For newly added modules, corresponding development documentation is also added, and existing other module development documentation is organized and improved; +14. Fixed ISSUES: + - #IISWI [github.com/gogf/gf/issues/IISWI](http://github.com/gogf/gf/issues/IISWI) + - #IISMY [github.com/gogf/gf/issues/IISMY](http://github.com/gogf/gf/issues/IISMY) + - Feedback and follow-up completion of issue fixing of third-party dependency mxj package ([github.com/clbanning/mxj/issues/48](http://github.com/clbanning/mxj/issues/48)); \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" new file mode 100644 index 00000000000..7598a2475a6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" @@ -0,0 +1,68 @@ +--- +slug: '/release/v0.2.0' +title: 'v0.2 2018-05-21' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame framework,v0.2,graceful restart,gflock,gproc,gpage,ghttp,gdb,function improvement] +description: "This version v0.2 includes multiple new features and updates, such as the graceful restart feature for the GoFrame framework, the gflock file lock module, the gproc process management module, and the powerful gpage pagination management module. Additionally, new features include multi-port listening for ghttp.Server and the gspath directory search tool. Function improvements cover enhanced gredis client, performance optimization of gdb methods, and enhanced request logging in the ghttp package. Various issue fixes ensure the stability and reliability of the GoFrame framework." +--- + +### New Features + +1. Graceful restart feature ([http://gf.johng.cn/625833](http://gf.johng.cn/625833)); +2. gflock file lock module ([http://gf.johng.cn/626062](http://gf.johng.cn/626062)); +3. gproc process management and communication module ([http://gf.johng.cn/626063](http://gf.johng.cn/626063)); +4. gpage pagination management module, with powerful dynamic and static pagination features, and high flexibility for developers to customize pagination styles ([http://gf.johng.cn/597431](http://gf.johng.cn/597431)); +5. ghttp.Server adds multi-port listening feature and supports HTTP/HTTPS ([http://gf.johng.cn/494366](http://gf.johng.cn/494366), [http://gf.johng.cn/598802](http://gf.johng.cn/598802)); +6. Added gspath directory search package management tool, supporting file search features across multiple directories; +7. ghttp package controller and execution object registration adds more flexible dynamic routing features, with added support for `{method}` variable in routing rules; + +### New Functions + +1. gutil package adds MapToStruct method, supporting mapping of map data types to struct objects; +2. gconv + - gconv package adds type conversion based on type name strings; + - gconv package adds Time/TimeDuration type conversion methods; +3. ghttp + - Adds WebServer directory security access control mechanism; + - ghttp.Server adds custom status code callback function registration handling; +4. gdb + - gdb package adds gdb.GetStruct/gdb.Model.Struct methods for automatic conversion of query result records to specified objects; + - gdb adds Value/Record/Result types and a series of type conversion methods for Value type; + - gdb package adds db.GetCount, tx.GetCount, model.Count count query methods; + +### Function Improvements + +1. Improved gredis client function encapsulation; +2. Improved performance of grand package random number generation; +3. Added benchmark performance testing scripts for grand/gdb/gredis packages; +4. Improved implementation of ToStruct method in gjson/gparser packages; +5. gdb: Improved performance of gdb.New in obtaining ORM operation objects; +6. gcfg: Improved configuration file retrieval function; +7. gview: Template engine adds multiple directory search capabilities; +8. gfile: Adds method MainPkgPath to obtain source main package directory; +9. ghttp + - ghttp.Request adds logging of request entry and completion times, including these in default log content; + - ghttp.Server event callbacks support parameter passing through ghttp.Request.Param with custom parameters; +10. gdb + - Improved type conversion between gdb.Result and gdb.List, gdb.Record, and gdb.Map for easier data encoding processing (like json/xml) at the business layer; + - Improved return type for gdb.Tx.GetValue; + - gdb.Model.Data parameter supports more flexible map parameters; + +### Issue Fixes + +1. ghttp + - Fixed caching issue in ghttp package routing; + - Fixed controller and execution object method loss issue during service registration; +2. gconv + - Corrected bit size setting issue in gconv.Float64 method; + - Fixed issue with gconv.Int64(float64(xxx)); +3. gdb + - Fixed issue with gdb.GetAll where for..range.. returns slice with same pointer for returned data list; + - Fixed error in gdb.Delete method; + - Fixed gdb.Model.And/Or method; + - Fixed parameter handling issue in gdb.Model.Where method; +4. garray: Fixed lock mechanism issue with garray package Remove method; +5. gtype: Fixed logical error in methods of gtype.Float32/gtype.Float64 object types; +6. gfsnotify: Fixed issue of hot update mechanism failure caused by different file separators in file parameters on Windows; +7. Fixed gvalid package validation issue: if value is nil and require* validation is not needed, other validations would fail. Added unit test items, all tests passed. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" new file mode 100644 index 00000000000..c99eb4fb2fb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" @@ -0,0 +1,66 @@ +--- +slug: '/release/v0.3.0' +title: 'v0.3 2018-08-07' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, GoFrame framework, DES encryption, Kafka client, object reuse pool, network communication, concurrency safety, database debugging, dynamic routing, pagination URL] +description: "New features, improvements, and bug fixes in version v0.3 of the GoFrame framework. New features include the addition of a DES encryption/decryption package, Golang client for Kafka, and an object reuse pool. Improvements cover the refactoring of network communication packages, database query caching features, HTTP client account password settings, and enhanced server routing matching rules. Additionally, various packages and functions have been optimized to improve system performance and stability." +--- + +### New Features + +1. Added the gdes package for handling DES encryption/decryption algorithms; +2. Added the gkafka package, a Golang client for Kafka; +3. Added the gpool object reuse pool, which is more flexible and powerful than the standard library's sync.Pool, allowing customization of object caching time, creation methods, and destruction methods ([http://gf.johng.cn/686654)](http://gf.johng.cn/686654)); +4. Completed the refactoring of gtcp/gudp network communication packages with substantial improvements and added detailed documentation and example code ([http://gf.johng.cn/494382)](http://gf.johng.cn/494382)); +5. Added the gring concurrency-safe ring, a concurrency-safe version of the standard library container/ring package, with ease-of-use encapsulation ([http://gf.johng.cn/686655)](http://gf.johng.cn/686655)); +6. The gtime package now supports custom date formatting, with formatting syntax similar to PHP's date syntax ([http://gf.johng.cn/494387)](http://gf.johng.cn/494387)); +7. Added a debug mode feature to gdb, implemented via the SetDebug method, which allows detailed SQL execution records in debug mode, with detailed documentation and example code ([http://gf.johng.cn/702801)](http://gf.johng.cn/702801)); +8. Added a query caching feature to gdb, implemented via the Cache method, with detailed documentation and example code ([http://gf.johng.cn/702801)](http://gf.johng.cn/702801)); +9. The ghttp.Server routing feature now includes field matching rules, supporting dynamic routing rules like `/order/list/{page}.html` ([http://gf.johng.cn/702766)](http://gf.johng.cn/702766)); +10. Added pagination URL rule generation templates to the gpage pagination package, allowing the use of the `{.page}` variable to specify the page number position ([http://gf.johng.cn/716438)](http://gf.johng.cn/716438)); +11. Added the gmap.Map object, an alias for gmap.InterfaceInterfaceMap; + +### New Features + +1. gdb added MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime settings and the SetMaxConnLifetime method; +2. ghttp.Client added HTTP account password setting function (SetBasicAuth); +3. glog added adaptive adjustment for system newline characters (\\n\|\\r\\n); +4. Added glog console debug mode printing switch (SetDebug); +5. gcfg added the SetFileName method to set the default configuration file name; +6. gcfg/gjson/gparser packages added Int8/16/32/64, Uint8/16/32/64 methods; +7. Added encapsulation for gzip methods (Zip/Unzip); +8. gview added template variable delimiter setting method SetDelimiters; +9. ghttp.Response added Writef, Writefln methods; + +### Improvements + +1. Improved gfilepool file pointer pool design; improved gfile text content writing, with added pointer pool usage; +2. gdb package added debug mode feature, supporting executed SQL list results retrieval in debug mode; +3. Improved gproc inter-process communication mechanism, added process message grouping feature and limited queue size; +4. gdb result method processing added ToXml/ToJson methods; +5. Changed gregx package name to gregex; +6. Improved gtime.StrToTime method, added automatic conversion for common standard date-time formats, and automatic timezone recognition, with adjustments to gconv, gvalid referencing this package; +7. Added character set conversion encapsulation, with gxml package using the newly added character set conversion package for handling; +8. ghttp.Server.EnableAdmin page Restart interface supports GET parameter newExeFilePath; +9. ghttp.Server graceful restart mechanism added customizable restart executable file path, especially useful for Windows systems (as Windows does not support executable file overlay updates); +10. Improved ghttp.Server static file retrieval design, with a lookup mechanism in the main package source directory during development environments; improved gcfg/gview lookup mechanism in the main package source directory; +11. Optimized gcache design, with the LRU feature not enabled by default; optimized gtype/gcache benchmark scripts; added gregx benchmark scripts, improved design for enhanced performance; +12. gfile package added GoRootOfBuild method to retrieve the GOROOT value at compile time; improved backtrace GOROOT path filtering in the glog package; +13. Improved grpool code quality, and improved pool goroutine quantity limit design; +14. Improved gdb.Map/List and g.Map/List type definitions, using alias features to support native type inputs (map/slice), and fixed gdb.Model.Update method parameter handling issue; +15. Adjusted ghttp package example code directory structure, added ghttp.Client custom Header methods, ghttp.Cookie added Map method for retrieving all cookie values submitted by the client, returned as a map; +16. Removed getcharset method from gcharset; +17. Removed common basic data type conversion retrieval methods from gmap; +18. Improved gconv.String method, using json.Marshal for conversion if basic type string conversion is not possible; +19. gvalid.CheckObject method name changed to gvalid.CheckStruct; + +### Bug Fixes + +1. Fixed gstr.IsNumeric error; +2. Fixed error when XML encoding charset is non-UTF-8; +3. Fixed gconv package float32 to float64 precision issue; +4. Fixed gpage package pagination count issue; +5. Fixed gdb batch data Save error; +6. Removed usage of math.MAXINT64 constant in gpool to fix int64 to int type conversion error, compatible with 32-bit systems; +7. Fixed ghttp package initializing related asynchronous goroutines without using Server issue. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" new file mode 100644 index 00000000000..423c63ed388 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" @@ -0,0 +1,12 @@ +--- +slug: '/release/v0.x' +title: 'Historical Version v0.x' +sidebar_position: 999999 +hide_title: true +keywords: [Historical Version Records, GoFrame, v0.x versions, update log, software update, version release, feature improvements, performance optimization, issue fixes, framework] +description: "This section provides a detailed record of all historical updates for the GoFrame framework v0.x versions, including feature improvements, performance optimizations, and issue fixes for each version, allowing developers to quickly understand version changes and new features." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" new file mode 100644 index 00000000000..92b955a78fb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" @@ -0,0 +1,105 @@ +--- +slug: '/release/v1.0.0' +title: 'v1.0 2018-10-24' +sidebar_position: 16 +hide_title: true +keywords: [GoFrame,GoFrame framework,gf-orm,gkafka,gcron,gredis,gmlock,gaes,gproc,gfcache] +description: "GoFrame v1.0 released, including new features such as gf-orm support for SQLite, addition of gkafka module, gcron task scheduling module, and improvements on singleton operations for gredis. It also introduces new modules like the gmlock memory lock and the gaes algorithm module, as well as enhancements in log management, template engine, and database operations, thus improving user experience and performance for developers." +--- + +### New Features + +1. `gf-orm` adds support for the `sqlite` database type ([http://gf.johng.cn/database/orm/database](http://gf.johng.cn/database/orm/database)); +2. Added `gkafka` module with a client program encapsulation for kafka, supporting group consumption and specified start positions, along with a user-friendly API interface ([http://gf.johng.cn/database/gkafka/index](http://gf.johng.cn/database/gkafka/index)); +3. Added support for the latest Go language version's `go modules` feature; +4. Added `gcron` scheduling task module ([http://gf.johng.cn/os/gcron/index](http://gf.johng.cn/os/gcron/index)); +5. `WebServer` adds features to acquire/print route registration items, providing a comprehensive view of all route and callback registrations; +6. The template engine adds global variable management and several commonly used built-in functions and variables ([http://gf.johng.cn/os/gview/funcs](http://gf.johng.cn/os/gview/funcs)); +7. `gredis` improved to singleton operation mode (based on the underlying connection pool feature), eliminating the need for developers to explicitly call the `Close` method to shut down after each `redis` server operation ([http://gf.johng.cn/database/gredis/index](http://gf.johng.cn/database/gredis/index)); +8. `gf-orm` adds auto `Close` feature for database operations (based on underlying connection pool feature), so developers no longer need to `defer db.Close()`, and adds `g.DB` database object singleton alias ([http://gf.johng.cn/database/orm/linkop](http://gf.johng.cn/database/orm/linkop)); +9. Added `gvar` universal dynamic variable module ([http://gf.johng.cn/container/gvar/index](http://gf.johng.cn/container/gvar/index)); +10. Data structure container adds `concurrent safety feature enable/disable function`, improving performance in non-concurrent safety mode; +11. New `gmlock` memory lock module added ([http://gf.johng.cn/os/gmlock/index](http://gf.johng.cn/os/gmlock/index)); +12. Added `gaes` algorithm module ([http://gf.johng.cn/crypto/gaes/index](http://gf.johng.cn/crypto/gaes/index)); +13. The `gproc` module adds method to execute `shell` commands ([http://gf.johng.cn/os/gproc/index](http://gf.johng.cn/os/gproc/index)); +14. New `gfcache` module added for file content operations with automatic cache updates (documentation pending); + +### New Features + +1. `glog` adds chain operation methods, log level management control, category management, and debug management functions; +2. `g.View` adds group name setting, supporting multiple named singleton template engine objects through `g.*` object manager; +3. `glog` adds customization for file name format, supporting `gtime date format`; +4. `gconv` adds `Ints/Uints/Floats/Interfaces` conversion methods; +5. `gjson` adds `Append` method; +6. `gparser` adds `NewUnsafe/Append` method; +7. `gcache` adds `GetOrSet/GetOrSetFunc/GetOrSetFuncLock` method; +8. `gset` adds `LockFunc/RLockFunc` methods; +9. `ghttp.Response` methods enhanced with `ParseTpl/ParseTplContent/TplContent` methods, changing `Template` to `Tpl` method; +10. `ghttp.Request` adds method to obtain user's real IP judgment; +11. `Session` adds `Contains` method; +12. Enhanced `ghtml` module with several new methods; +13. `gcache` adds new `Contains/SetIfNotExist` methods; +14. `gvalid` adds `Error` object for validation error management; +15. `gvalid` module adds support for `struct tag` validation rules and custom error message binding ([http://gf.johng.cn/util/gvalid/index](http://gf.johng.cn/util/gvalid/index)); +16. `ghttp` adds input parameter and `struct` `binding mechanism`, with support for `params` tags ([http://gf.johng.cn/net/ghttp/service/handler](http://gf.johng.cn/net/ghttp/service/handler)); +17. `ghttp.Request` adds server-side `BasicAuth` functionality (documentation pending); +18. `gvalid` adds field validation alias for customizing return result fields and updates modules in WebServer usage; +19. `gf-orm` chain operation adds `ForPage` method and adjusts `Chunks` method; +20. `ghttp` object route registration adds `Init&Shut` auto-callback methods, along with duplicate route registration detection; +21. `gfsnotify` adds default recursive `Add/Remove` feature; +22. `ghttp.Response` adds `ServiceFile` method; +23. Various other new features; + +### Feature Improvements + +1. Improved `ghttp.Server` configuration management; +2. Enhanced `gcache` underlying object inheritance structure, refined some design details to improve performance; +3. Improved `gfpool` file pointer pool, fixed some errors, enhanced performance, and added benchmark test code; +4. Enhanced `gmap` series of concurrent safe map data structures with several usability methods; +5. Improved `gconv.Struct` object conversion functionality ([http://gf.johng.cn/util/gconv/index](http://gf.johng.cn/util/gconv/index)); +6. Improved `grand` random number generation rules to ensure extremely high random number generation performance, guaranteeing different random values each time a random method is called ([http://gf.johng.cn/util/grand/index](http://gf.johng.cn/util/grand/index)); +7. Improved `gfile` file content operation methods with several commonly used file content read methods; +8. Enhanced `gtime` module and added time zone conversion methods; +9. Improved `COOKIE`, removing lock mechanism; +10. Enhanced `SESSION` retrieval methods with multiple type retrievals; +11. Improved `g.DB/g.Config` singleton cache key names; +12. Enhanced `gtcp/gudp` timeout error judgment mechanism; +13. Improved `gtype` underlying operations with atomic methods; +14. Enhanced `gvalid` default value validation for `string` attributes in `structs`; +15. Improved `gvalid` non-essential validation under associated rules; +16. Enhanced `gf-orm` debug mode with automatic log output functionality; +17. `ghttp.Server/gspath` module static file retrieval improved; +18. Optimized `ghttp.ServerConfig` configuration, adding `struct/method` `name to uri` conversion rules with flexible configuration via `SetNameToUri` method ([http://gf.johng.cn/net/ghttp/service/object](http://gf.johng.cn/net/ghttp/service/object)); +19. Improved `*any/:name` route matching rules to support unnamed `*/:` route rules; +20. Changed default configuration file name from `config.yml` to `config.toml` ([http://gf.johng.cn/os/gcfg/index](http://gf.johng.cn/os/gcfg/index)); +21. Adjusted service registration `BindControllerMethod` and `BindObjectMethod` logic to bind routes to specified methods; +22. Improved `garray` binary search method with secure operations; +23. Improved `gdb.Result/Recorde` `ToXml` method with optional `rootTag` parameter; +24. Various other improvements; + +### Bug Fixes + +1. Fixed `ghttp.Server` restart failure issue on `windows`; +2. Addressed `ghttp.Server` service registration and callback registration route duplication issue; +3. Fixed deadlock issue with `garray` sortable array `Add` variadic parameter; +4. Resolved `gfsnotify` default recursive monitoring of entire directories added by `gspath.Add`; +5. Fixed `ghttp.BindParams` escaping issue for `@file` upload identifiers; +6. Resolved log path loss issue in `ghttp.Server`; +7. Addressed multi-WebServer status detection issue; +8. Fixed `gvalid` module `min/max` validation issues; +9. Resolved route binding issue on `Binded` route for controller and execution object service registration; +10. Fixed `gvalid.CheckStruct` custom error notification issue; +11. Solved `ghttp.Server` `hook` and `serve` method routing impact and added redirection method; +12. Various other fixes; + +### Other Changes + +1. Removed `gfile.IsExecutable` method; +2. Directory adjustment, moving `encryption/decryption` related packages from `encoding` to `crypto`; +3. Added debug information to `gfsnotify/gfcache`; +4. `gf-orm` permits database write of `null` if the writable key value is `nil`; +5. Unified use of `gview.Params` type as template variable type; +6. Renamed `gconv.MapToStruct` method to `gconv.Struct`; +7. Improved `ghttp.Server` terminal messages for restart and stop; +8. Enhanced `gring` module with addition of `Josephus problem` code as `gring` example program; +9. Various other changes; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" new file mode 100644 index 00000000000..4b3c6753867 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" @@ -0,0 +1,10 @@ +--- +slug: '/release/v1.1.0' +title: 'v1.1 2018-11-26' +sidebar_position: 15 +hide_title: true +keywords: [GoFrame, GoFrame framework, v1.1, version update, feature merge, v1.2, changelog, software release, technical documentation, development framework] +description: "该文档介绍了GoFrame框架的v1.1版本,该版本的功能合并到v1.2一起发布。通过这个更新日志,用户可以了解版本的改进和功能调整细节。" +--- + +The features of this version are merged and released together with `v1.2` [v1.2 2018-11-26](./v1.2%202018-11-26.md) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" new file mode 100644 index 00000000000..098054173eb --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" @@ -0,0 +1,186 @@ +--- +slug: '/release/v1.10.0' +title: 'v1.10 2019-12-05' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,WebServer,Session Storage,Log Component,JSON Data Format,guuid Module,Database Improvement,ghttp Improvement,container Improvement,gconv Performance Optimization] +description: "The GoFrame framework releases version v1.10, bringing several new features and improvements for developers. This update includes optimizations in middleware and routing implementation in WebServer, addition of file configuration management, multiple session storage implementations, log component singleton object, JSON data format support, guuid module for UUID generation, along with performance improvements in various modules such as database, network, and containers, further enhancing operational efficiency and development experience." +--- + +Dear `gfer`, the wait is over. It's been over two months since the last release, and during this period, `GoFrame` has been continually iterating and improving. There are numerous details, and here is a general overview of the `release log`. + +Additionally, `GoFrame` is participating in the 2019 Most Popular Chinese Open Source Software voting, which ends tomorrow. Feel free to vote for `GoFrame`: [https://www.oschina.net/project/top\_cn\_2019](https://www.oschina.net/project/top_cn_2019). You can vote on the webpage and through WeChat. + +### New Features + +1. `WebServer` New Features: + - Improved middleware and group routing implementation: [https://goframe.org/net/ghttp/router/middleware](https://wiki.goframe.org/net/ghttp/router/middleware) + - Added file configuration management feature: [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - Enhanced parameter acquisition: [https://goframe.org/net/ghttp/request/index](https://wiki.goframe.org/net/ghttp/request/index) + - Improved file upload: [https://goframe.org/net/ghttp/client/demo/upload](https://wiki.goframe.org/net/ghttp/client/demo/upload) +2. `Session` adds multiple built-in `Storage` implementations: + - Introduction: [https://goframe.org/os/gsession/index](https://wiki.goframe.org/os/gsession/index) + - File storage: [https://goframe.org/os/gsession/file](https://wiki.goframe.org/os/gsession/file) + - Memory storage: [https://goframe.org/os/gsession/memory](https://wiki.goframe.org/os/gsession/memory) + - `Redis` storage: [https://goframe.org/os/gsession/redis](https://wiki.goframe.org/os/gsession/redis) +3. Added log component singleton object and optimized configuration management: + - [https://goframe.org/frame/g/index](https://wiki.goframe.org/frame/g/index) + - [https://goframe.org/os/glog/config](https://wiki.goframe.org/os/glog/config) +4. Common `container` containers add `Marshal`/`UnMarshal` interface implementation for `JSON` data format: + - [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) + - [https://goframe.org/container/garray/index](https://wiki.goframe.org/container/garray/index) + - [https://goframe.org/container/gset/index](https://wiki.goframe.org/container/gset/index) + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) + - [https://goframe.org/container/gtype/index](https://wiki.goframe.org/container/gtype/index) + - [https://goframe.org/container/glist/index](https://wiki.goframe.org/container/glist/index) + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) +5. Added `guuid` module for general `UUID` generation: [https://goframe.org/util/guuid/index](https://wiki.goframe.org/util/guuid/index) + +### Feature Improvements + +#### `net` + +1. `ghttp` + - Enhanced request process handling performance; + - `Server` adds configuration for `Logger` log object; + - `Server` exposes `GetRouterMap` method, enabling developers to more conveniently implement custom permission management; + - Enhanced server configuration management; + - Major improvements on the `Client` object; + - `Client` object adds multi-file upload functionality; + - `Request` object adds `GetError` method to retrieve current processing errors; + - `Request` object adds independent view object and view variable binding, allowing independent view management for each request and the ability to switch request objects' views through middleware. This feature is disabled by default, with view parsing using the `Server` object's view; + - Refactored `CORS` functionality for the `Response` object; + - Added `Response.WriteTplDefault` method to parse and return default template content; + - Added more unit test cases; + - Other improvements; +2. `gipv4`/`gipv6` + - Some improvement work; +3. `gtcp`/`gudp` + - Some improvement work; + +#### `database` + +1. `gdb` + - Extensive details improvement work; + - Removed `sql.ErrNoRows` error return when query data is empty, retaining the error return for `Struct`/`Structs`/`Scan` methods when data is empty; + - Enhanced SQL statement output in debug mode to a complete SQL with parameters, for reference only; + - `Where` method adds support for `gmap` data type, including sequential `ListMap`/`TreeMap`, etc.; + - Cache time parameter type for query cache method `Cache` changed to `time.Duration`; + - Renamed data type conversion methods for `Record`/`Result`, marking original methods as `deprecated`; + - `Record`/`Result` query result types add `IsEmpty` method to determine if the result set is empty; + - `Record` type adds `GMap` method to convert query records to `gmap` type; + - Added `Option`/`OptionOmitEmpty` methods for input parameter filtering, including `Data` and `Where` parameters: [https://goframe.org/database/gdb/empty](https://wiki.goframe.org/database/gdb/empty) + - Added field exclusion method `FieldsEx`: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - Added logging feature: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - Improved database configuration management: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - Added extensive unit testing; +2. `gredis` + - Enhanced data type conversion: [https://github.com/gogf/gf/issues/415](https://github.com/gogf/gf/issues/415) + - Improved unit testing; + - Other enhancements; + +#### `os` + +1. `gcache` + - Important note: Cache validity period parameter adjusted from `interface{}` type to `time.Duration` type, and no longer compatible with previous `int` type to ensure better performance; +2. `gfcache` + - Due to `gcache` component's cache time parameter type change, this component's time parameter also changed to `time.Duration` type; +3. `gcfg` + - Added `Available` method to determine if configuration is valid; +4. `gfile` + - Added `Chdir` method for working directory switching; +5. `gtime` + - Added `Marshal`/`UnMarshal` interface implementation for `JSON` data format; + +#### `container` + +1. `gmap` + - Added `MapStrAny` method for common `map` type conversion; + - Added `MapCopy` method for underlying `map` data copying; + - Added `FilterEmpty` method for `map` empty value filtering; + - Added `Pop`/`Pops` methods for randomly returning (and deleting) data items in `map`; + - Added `Replace` method to cover the underlying `map` data with given `map` data; + - Improved unit testing; + - Other enhancements; +2. `garray` + - Added `Interfaces` conversion method returning `[]interface{}` type; + - For sorted arrays, added `SetComparator` method for custom comparator modification; + - Improved unit testing; + - Other enhancements; +3. `glist` + - Added `NewFrom` method to create a list based on a given `[]interface{}` variable; + - Added `Join` method to concatenate list items using a given string and return the string; + - Improved unit testing; + - Other enhancements; +4. `gset` + - Added `AddIfNotExistFunc`/`AddIfNotExistFuncLock` methods; + - Improved unit testing; + - Other enhancements; +5. `gtree` + - Added `Replace` method for updating existing tree data items; + - Other improvements; +6. `gtype` + - Some detail improvement work, not listed one by one; + - Improved benchmark testing and unit testing; +7. `gvar` + - Added `Ints`/`Uints` type conversion methods; + - Other enhancements; + +#### `crypto` + +1. `gmd5` + - Minor detail improvements; +2. `gsha1` + - Minor detail improvements; + +#### `text` + +1. `gstr` + - Improved `SplitAndTrim` method, marked `SplitAndTrimSpace` as `deprecated`; + - Added `TrimStr` method; + - Improved unit testing; + - Other enhancements; + +#### `debug` + +1. `gdebug` + - Added `CallerFileLineShort`/`FuncPath`/`FuncName` methods; + - Other improvements; + +#### `encoding` + +1. `gbase64` + - Added `EncodeToString`/`EncodeFile`/`EncodeFileToString`/`DecodeToString` methods; + - Improved unit testing; +2. `gjson` + - Improved unit testing; + +#### `frame` + +1. `g`/`gins` + - [https://goframe.org/frame/g/index](https://wiki.goframe.org/frame/g/index) + - Added `CreateVar` method; + - Improved unit testing; + - Other enhancements; + +#### `util` + +1. `gconv` + - Improved and optimized performance for some type conversion methods; + - Added `Uints`/`SliceUint` type conversion methods; + - Added high-performance type conversion methods `UnsafeStrToBytes`/`UnsafeBytesToStr`; + - Added support for `MapStrAny` interface methods for common `map` type conversion; + - Other improvements; +2. `gvalid` + - Improved ID card validation functionality for Chinese IDs; + - Added `luhn` bank card number validation functionality; +3. `grand` + - Some performance improvements; + +### Bug Fix + +1. Fixed `hijacked` error issue upon WebSocket closure: [https://github.com/gogf/gf/issues/381](https://github.com/gogf/gf/issues/381) +2. Resolved memory usage issue for large files during static file service; +3. Fixed default `Cookie` domain setting issue when behind `Nginx`; +4. Fixed `gconv.Struct` conversion failure issue when property is `[]struct` and input property parameter is empty: [https://github.com/gogf/gf/issues/405](https://github.com/gogf/gf/issues/405) +5. Other fixes; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" new file mode 100644 index 00000000000..7b674abbdaf --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" @@ -0,0 +1,148 @@ +--- +slug: '/release/v1.11.0' +title: 'v1.11 2020-01-14' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame framework,modular,high performance,production-grade,microservices,IoT,blockchain,e-commerce systems,enterprise-grade projects] +description: "GoFrame is a modular, high-performance, production-grade Go development framework that offers a rich set of core components such as caching, logging, file handling, timing, and command-line interfaces. It supports web service development with features like hot restart and multi-domain support, and is widely used in microservices, IoT, blockchain, e-commerce, and banking projects." +--- + +`GF(Go Frame)` [https://goframe.org](https://wiki.goframe.org) is a modular, high-performance, production-grade Go development framework. It provides a comprehensive infrastructure including commonly used core development components such as caching, logging, files, timing, queues, arrays, collections, strings, timers, command-line, file locks, memory locks, object pools, connection pools, resource management, data validation, data encoding, file monitoring, scheduled tasks, database ORM, TCP/UDP components, process management/communication, concurrency-safe containers, etc. It offers core components for web service development, such as Router, Cookie, Session, Middleware, service registration, configuration management, template engine, and supports features like hot restart, hot update, multi-domain, multi-port, multi-service, HTTPS, Rewrite, and more. + +`GoFrame` has a rich set of foundational modules, a complete toolchain, and detailed documentation. In the nearly two years since its open-source release, `GoFrame` has gained increasing recognition and support, evolving from obscurity to widespread application in enterprise-grade production projects such as microservices, IoT, blockchain, e-commerce systems, and banking. It has been tested in projects with hundreds of thousands to millions of users and was awarded the `GVP` Most Valuable Open-Source Project by Gitee in 2019. `GoFrame` is rapidly growing, with release iterations every 1-2 months, and an active community welcomes you to join the `GoFrame` family. + +Finally, I wish everyone a Happy New Year 2020, the Year of the Rat! + +### New Features + +1. New year, new updates, a lot of updates to official documentation: [https://goframe.org/index](https://wiki.goframe.org/index) +2. `GoFrame` toolchain updates: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) + - Added `gf run` hot compilation and run command; + - Added `gf docker` Docker image compilation command; + - Added `gf gen model` powerful model auto-generation command; + - `gf build` command now supports configuration file settings; + - Numerous command-line tool improvements; + - Added automatic proxy setting feature; +3. New features in Database `ORM`: + - Added `prefix` support for data table prefixes: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - Added `Schema` database object and improved database switching feature: [https://goframe.org/database/gdb/model/schema](https://wiki.goframe.org/database/gdb/model/schema) + - Added `WherePri` method for automatic primary key condition methods: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - Significant updates to documentation and examples, covering over 95% of functional features; + +### Feature Improvements + +#### `container` + +1. `garray` + - Added `New*ArrayRange` method for initializing arrays with specified number ranges. + - Added `Iterator*` method for callback iteration of array elements. + - Enhanced unit tests. +2. `gvar` + - Improved the implementation of `MapStrStr`, `MapStrStrDeep` methods. + +#### `net` + +1. `ghttp` + - Improved HTTP client to automatically recognize `Content-Type` for submitted parameters. + - `Request` object added `Parse` method for easy object transformation and parameter validation. + - All `Request.GetPost*` methods marked as `deprecated`, standardize client parameter submission to `QueryString`, `Form`, `Body`. + - Removed `Get`/`Post` built-in variables during `Response` template parsing, added `Query`, `Form`, `Request` built-in variables: [https://goframe.org/net/ghttp/response/template](https://wiki.goframe.org/net/ghttp/response/template) + - Improved `Response.WriteJson*` and `Response.WriteXml*` methods, supporting `string`, `[]byte` type parameters. + - `Server` added `GetRouterArray` method for exposing and obtaining the route list to the application layer. + - `Server` added `Use` method, alias of `BindMiddlewareDefault`, for global middleware registration. + - `Server` added `RouteOverWrite` option to control automatic overwriting when registering route conflicts, disabled by default with a warning. + - `Server` added `Graceful` option to control the activation/deactivation of graceful restart features in a single service scenario, enabled by default. + - Enhanced unit tests. +2. `gtcp` + - Improved data packet sending and receiving functionality under a simple protocol. + - Changed the default cache expiration time of the connection pool from `30` seconds to `10` seconds. + - Enhanced unit tests. + +#### `database` + +1. `gdb` + + - Added `As` data table alias method. + + - Improved automatic recognition and addition of safe characters for tables and fields. + + - Added `DB` method for switching database objects. + + - Added `TX` method for supporting chain operations within transactions. + + - Enhanced unit tests. + +#### `os` +2. `gcfg` + + - Added `GetMapStrStr` method. +3. `gcmd` + + - Added `strict` parameter for strict argument parsing, default is strict parsing, errors if specified parameter/option name does not exist. +4. `genv` + + - Improved `Remove` method to support deletion of multiple environment variables. +5. `gfile` + + - Improved `TempDir` method for getting temporary directory, default is `/tmp` in `*nix` systems. + - Added `ReadLines`, `ReadByteLines` methods for line-by-line callback file reading. + - Added `Copy*` methods for file/directory copying, supports recursion. + - Added `Replace*` methods for replacing file contents within directories, supports recursion. + - Improved `Scan*` methods for retrieving all files/directories under a specified directory, supports file mode specification, supports recursion. + - Enhanced unit tests. +6. `gproc` + + - Improved command-line running methods. + - Improved `Shell` command file search logic. + - Improved experimental inter-process communication design. +7. `gtime` + + - Marked timestamp methods `Second`, `Millisecond`, `Microsecond`, `Nanosecond` in package methods and `Time` objects as deprecated, replaced with `Timestamp`, `TimestampMilli`, `TimestampMicro`, `TimestampNano`. + - Note that these changes may have compatibility issues with previous versions. +8. `gview` + + - Improved parsing functionality and cache design. + - Added `encode`, `decode` HTML encoding/decoding template functions. + - Added `concat` string joining template function. + - Added `dump` template function, similar to `g.Dump` method. + - Added `AutoEncode` option for automatic HTML content encoding, useful for preventing `XSS`, disabled by default. Note that this feature does not affect the `include` built-in function: [https://goframe.org/os/gview/xss](https://wiki.goframe.org/os/gview/xss) + - Enhanced unit tests. + +#### `crypto` + +1. `gmd5` + - Added `MustEncrypt`, `MustEncryptBytes`, `MustEncryptString`, `MustEncryptFile` methods. +2. `gsha1` + - Added `MustEncryptFile` method. + +#### `encoding` + +1. `gbase64` + - Added `MustEncodeFile`, `MustEncodeFileToString`, `MustDecode`, `MustDecodeToString` methods. +2. `gjson`/`gparser` + - Added `GetMapStrStr` method. + - Added `Must*` methods to trigger `panic` error on failure to convert specified data format, instead of returning an `error` parameter. + +#### `util` + +1. `gconv` + - Improved `Convert` method to add conversion support for `[]int32`, `[]int64`, `[]uint`, `[]uint32`, `[]uint64`, `[]float32`, `[]float64` data types. + - Improved `String` method support for converting pointer parameters. + - Improved code structure and performance of `Map*` map conversion methods. + - Added `Floats`, `Float32s`, `Float64s` methods for conversion to `[]float32`, `[]float64` types. + - Added `Ints`, `Int32s`, `Int64s` methods for conversion to `[]int`, `[]int32`, `[]int64` types. + - Added `Uints`, `Uint32s`, `Uint64s` methods for conversion to `[]uint`, `[]uint32`, `[]uint64` types. + - Enhanced unit tests. + +#### `frame` + +1. `gins` + - All singleton objects trigger `panic` error when retrieval fails. + +### Bug Fix + +1. Added compatibility support for common erroneous route formats like `/user//index`. +2. Fixed issue with interval time units for data reception in `gtcp`/`gudp`. +3. Fixed issue with imprecise file existence checks in `gfile`/`gspath`/`gfsnotify` packages. +4. Fixed blocking issue with `gproc.Kill` method on `windows`. +5. Fixed array overflow issue in `gstr.TrimLeftStr`/`gstr.TrimRightStr` when the replaced string length was less than the replacement string length. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" new file mode 100644 index 00000000000..a822f0d6db7 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" @@ -0,0 +1,237 @@ +--- +slug: '/release/v1.12.0' +title: 'v1.12 2020-03-31' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame, High Performance, Modular, Basic Development Framework, Unit Testing, Cache, Logging, Data Validation, Database ORM, Web Services] +description: "GoFrame is a modular, high-performance, production-grade Golang basic development framework, offering essential modules such as caching, logging, queues, and containers, and supports core components for web service development, making it suitable for enterprise and team use. This version updates many features, including improvements in database operations, logging management, file operations, and introduces support for the Swagger tool." +--- + +Hello everyone! Thank you for waiting! + +Since the last version release, more and more friends have joined the `GoFrame` family and provided many valuable suggestions and feedback. This version addresses most of these feedbacks, including most of the improvement suggestions and some new features, which is why this release took more than two months. `GoFrame` is very focused on code quality and sustainable maintainability. This version further refines the examples, comments, and unit test cases for most modules in the framework. Currently, there are about `1991` unit test cases with a code coverage of `71%`, covering most of the main functions of all modules. + +The `GoFrame` framework provides common, high-quality basic development modules, lightweight, modular, and high-performance. We recommend reading the framework source code to learn more details. This release has a few incompatible upgrades, often requiring batch replacements. The following `change log` is quite comprehensive, and it is recommended to read it carefully before upgrading. + +This release marks the start of the next version development plan, and more friends are welcome to participate in open source contributions: [https://github.com/gogf/gf/projects/8](https://github.com/gogf/gf/projects/8) + +Thank you for your support! Enjoy your `GoFrame`! + +## GoFrame + +`GoFrame(Go Frame)` is a modular, high-performance, production-grade Golang basic development framework. It implements relatively complete infrastructure construction and development toolchain, providing commonly used basic development modules, such as: cache, log, queue, array, set, container, timer, command line, memory lock, object pool, configuration management, resource management, data validation, data encoding, scheduled tasks, database ORM, TCP/UDP components, process management/communication, etc. It also provides a series of core components for web service development, such as: Router, Cookie, Session, Middleware, service registration, template engine, etc., supporting features like hot restart, hot updates, domain binding, TLS/HTTPS, Rewrite, etc. + +### Features + +- Modular, loosely coupled design; +- Rich modules, ready to use; +- Simple and easy to use, easy to maintain; +- Active community, modest and approachable experts; +- High code quality, high unit test coverage; +- Detailed development documentation and examples; +- Complete native Chinese support; +- More suitable for enterprise and team use; + +### Address + +- Official Website: [https://goframe.org](https://wiki.goframe.org) +- Main Repository: [https://github.com/gogf/gf](https://github.com/gogf/gf) +- Gitee: [https://gitee.com/johng/gf](https://gitee.com/johng/gf) + +## Change Log + +From `GoFrame v1.12` version, the minimum `Golang` runtime version required by the framework is `v1.13`. Since new versions of `Golang` are backward compatible, it is recommended to update to use the new version of `Golang`: [https://golang.google.cn/dl/](https://golang.google.cn/dl/) + +> This version also introduces support for the `Swagger` tools and plugins, released separately. + +### `tool chain` + +1. The `gen model` command adds model generation support for `pgsql/mssql/sqlite/oracle`. +2. The `gen model` command generates a public package variable `Columns` for retrieving table field names. + +### `net` + +1. `ghttp` + - Note: Starting from this version, the `Server` has the graceful restart feature disabled by default. Developers can enable it through the appropriate configuration options. + - Improved `Client.Get` method with optional request parameters. + - Added chainable methods for `Client`: `Header`, `HeaderRaw`, `Cookie`, `ContentType`, `ContentJson`, `ContentXml`, `Timeout`, `BasicAuth`, `Ctx`: [https://goframe.org/net/ghttp/client/chain](https://wiki.goframe.org/net/ghttp/client/chain) + - Added context variable management methods `Request.GetCtx/GetCtxVar/SetCtxVar/Context` for internal request context variables: + - Custom Variables: [https://goframe.org/net/ghttp/request/custom](https://wiki.goframe.org/net/ghttp/request/custom) + - Context Variables: [https://goframe.org/net/ghttp/request/context](https://wiki.goframe.org/net/ghttp/request/context) + - Added `Request.GetUploadFile/GetUploadFiles` methods and `UploadFile` type, greatly simplifying file upload processing logic: [https://goframe.org/net/ghttp/client/demo/upload](https://wiki.goframe.org/net/ghttp/client/demo/upload) + - Added `Request.GetPage` method for convenient pagination object retrieval: + - Introduction: [https://goframe.org/util/gpage/index](https://wiki.goframe.org/util/gpage/index) + - Dynamic Pagination: [https://goframe.org/util/gpage/dynamic](https://wiki.goframe.org/util/gpage/dynamic) + - Static Pagination: [https://goframe.org/util/gpage/static](https://wiki.goframe.org/util/gpage/static) + - Ajax Pagination: [https://goframe.org/util/gpage/ajax](https://wiki.goframe.org/util/gpage/ajax) + - URL Templates: [https://goframe.org/util/gpage/template](https://wiki.goframe.org/util/gpage/template) + - Custom Pagination: [https://goframe.org/util/gpage/custom](https://wiki.goframe.org/util/gpage/custom) + - Improved `Response.Redirect*` method with a customizable HTTP status code parameter for redirection. + - Enhanced `CORS` feature, improved cross-origin handling, fully conforming to `W3C` specifications regarding the `OPTIONS` request method: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - Added `Request` built-in variable to template view objects, allowing templates to obtain request parameters. As the `GoFrame` framework's template engine uses a two-level cache design, it reduces `IO` overhead while improving execution efficiency. Even with a large number of built-in variables and methods added, extensive performance tests have shown that efficiency is 50% to 200% higher than other `WebServer` libraries under the same logic. + - Introduced an experimental `Plugin` feature for `Server`. + - Improved the underlying route storage and retrieval logic of `Server`, optimized code, and enhanced efficiency. + - Enhanced source location tracking for group route registration, allowing for precise location and prompt of duplicate route source locations when there are registration conflicts. + - Improved `Server` log handling. + - Enhanced unit testing. + +### `database` + +1. `gdb` + + - Code refactoring and improvements, introducing `Driver` interface design for custom driver implementations by developers. Added `Register` package method to allow developers to register custom database type drivers: [https://goframe.org/database/gdb/driver](https://wiki.goframe.org/database/gdb/driver) + - Added `GetArray` interface and implementation for retrieving data for specified field columns, returning an array: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - Added `InsertIgnore` interface and implementation for ignoring write conflicts during write operations, applicable only to `mysql` database type: [https://goframe.org/database/gdb/model/insert-save](https://wiki.goframe.org/database/gdb/model/insert-save) + - Added `Schema` interface and implementation for dynamically switching and getting database objects with specified names: [https://goframe.org/database/gdb/model/schema](https://wiki.goframe.org/database/gdb/model/schema) + - Added `FieldsStr/FieldsExStr` model methods for obtaining table fields and returning them as strings: [https://goframe.org/database/gdb/model/fields-retrieve](https://wiki.goframe.org/database/gdb/model/fields-retrieve) + - Added `LockUpdate/LockShared` model chainable methods for implementing pessimistic locking: [https://goframe.org/database/gdb/model/lock](https://wiki.goframe.org/database/gdb/model/lock) + - Improved support for the input method in `Where/Data` for update parameters, increasing flexibility: + - [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - [https://goframe.org/database/gdb/model/update-delete](https://wiki.goframe.org/database/gdb/model/update-delete) + - Added `Array` method to query result object `Result` for obtaining values of specified fields, returning them in an array: [https://goframe.org/database/gdb/result](https://wiki.goframe.org/database/gdb/result) + - Improved `OmitEmpty` method for filtering `Data` input parameters. If the given `Data` parameter is an empty time object, it will be filtered out. + - Added default database connection pool parameter: `MaxIdleConns=10`. + - Various other improvements. + - Enhanced unit testing. +2. `gredis` + + - Increased/modified default database connection pool parameters: `MaxIdle=10`, `IdleTimeout=10s`, `MaxConnLifetime=30s`, `Wait=true`. + - Enhanced unit testing. + +### `container` + +1. Added `UnmarshalValue(interface{}) error` interface method implementation to all container objects, used by `gconv` for creating/setting object content based on arbitrary type variables. All container objects in the `GoFrame` framework have implemented this interface. + +2. `garray` + + - Added `RemoveValue` method for searching and removing elements based on values. + - Added `FilterNil` method for traversing and removing `nil` elements from an array. + - Added `FilterEmpty` method for traversing and removing empty value elements from an array. Empty values include: `0, nil, false, "", len(slice/map/chan) == 0`. + - Improved `Set/Fill/InsertBefore/InsertAfter` methods with stricter validation, changing the original chain operation return value object to `error` return value. + - Enhanced `Get/Remove/PopRand/PopLeft/PopRight/Rand` methods with stricter validation, adding a `found` boolean return value to indicate if the method has data to return. If the array is empty or the operation is out of range, `found` returns `false`. + - Changed `Rands` method logic to ensure it returns a random array of the specified size. + - Enhanced unit testing. +3. `gpool` + + - Adjusted the expiration time parameter type of the cache pool. The old version used `int` type representing milliseconds, while the new version uses `time.Duration` type making time control more flexible. + - Internal code optimization and performance improvement. + - Enhanced unit testing. + - Improved annotations. + +### `os` + +1. `glog` + + - Introduced `Rotation` log rolling feature, with options for log splitting based on file size or expiration time, supporting the limit of split file numbers, automatic compression of split files, customizable compression levels (`1-9`), and support for cleaning up expired split files: [https://goframe.org/os/glog/rotate](https://wiki.goframe.org/os/glog/rotate) + - Added `LevelPrefixes` feature for customizing prefix names for log levels: [https://goframe.org/os/glog/level](https://wiki.goframe.org/os/glog/level) + - Added `SetLevelStr` method and feature to configure log levels using strings: + - [https://goframe.org/os/glog/level](https://wiki.goframe.org/os/glog/level) + - [https://goframe.org/os/glog/config](https://wiki.goframe.org/os/glog/config) + - Introduced `SetDefaultLogger` package method for setting the global default `Logger` object. +2. `gres` + + 1. Improved resource content encoding design with a new compression algorithm, reducing resource file size, such as the original `15MB` website static resource files (`css/js/html`, etc.) reduced to approximately `4MB` after packaging: [https://goframe.org/os/gres/index](https://wiki.goframe.org/os/gres/index) + 2. Note: This improvement is not compatible with the old version and requires re-packing of existing resource files. + 3. Enhanced unit testing. +3. `gcfg` + + - Removed atomic concurrency safety control for configuration object attributes, changed to ordinary variable types. Since configuration management is one of the most used modules, high efficiency in design and method implementation is ensured. + - Significant adjustment to singleton objects: To facilitate the use of configuration files in multi-file scenarios and improve development efficiency, when the `toml` configuration file corresponding to the given singleton name exists in the configuration directory, the default configuration file of the singleton object is automatically set to this file. For example, the singleton object obtained by `g.Cfg("redis")` will default to retrieving and setting the default configuration file as `redis.toml`. If the file does not exist, the default configuration file (`config.toml`) is used: [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - Enhanced unit testing. +4. `gview` + + - Added `concat` built-in string concatenation method: [https://goframe.org/os/gview/function/buildin](https://wiki.goframe.org/os/gview/function/buildin) + - Enhanced unit testing. +5. `gfile` + + - Improved `SelfPath` method for obtaining the current executable file path, increasing execution efficiency. + - Enhanced `Join` method for connecting file paths, preventing redundant path connection symbols. + - Improved execution efficiency of `GetContents` method, reducing memory usage. + - Added `StrToSize` method for converting size strings to byte numbers. Size strings include `10m`, `5KB`, `1.25Gib`, etc. + - Added `ReadLines/ReadByteLines` methods for reading specified file content line by line with a given read callback function. + - Enhanced unit testing. +6. `gtime` + + - Improved the implementation of `gtime.Time` object, unified string printed time format to `Y-m-d H:i:s`, such as `2020-01-01 12:00:00`. +7. `gcmd` + + - Added `strict` parameter to command-line parsing methods for determining whether the current parsing is strict. In strict parsing, if illegal options are given, parsing stops and returns an error. By default, it is non-strict parsing. +8. `genv` + + - Improved `Remove` method for deleting environment variable key-value pairs, adding the ability to delete multiple key-value pair environment variables. +9. `gfpool` + + - Improved code implementation, increasing efficiency. + - Enhanced unit testing. +10. `gfsnotify` + + - Improved package initialization method. If the default `Watcher` object fails to create due to system reasons, it `panic`s directly. +11. `gproc` + + - Improved `SearchBinaryPath` method. + - Improved `Process.Kill` method for handling different behaviors on `windows` and `*niux` platforms. + +### `encoding` + +1. `gjson` + - Code improvements, enhanced annotations, added numerous code examples. + - Documentation updates: + - Introduction: [https://goframe.org/encoding/gjson/index](https://wiki.goframe.org/encoding/gjson/index) + - Object Creation: [https://goframe.org/encoding/gjson/object](https://wiki.goframe.org/encoding/gjson/object) + - Level Access: [https://goframe.org/encoding/gjson/pattern](https://wiki.goframe.org/encoding/gjson/pattern) + - Struct Conversion: [https://goframe.org/encoding/gjson/conversion-struct](https://wiki.goframe.org/encoding/gjson/conversion-struct) + - Dynamic Creation/Modification: [https://goframe.org/encoding/gjson/dataset](https://wiki.goframe.org/encoding/gjson/dataset) + - Data Format Conversion: [https://goframe.org/encoding/gjson/conversion-format](https://wiki.goframe.org/encoding/gjson/conversion-format) + +### `frame` + +1. `g` + - Added `IsNil` method to determine if the given variable is `nil`, potentially using reflection for judgment. + - Added `IsEmpty` method to determine if the given variable is `empty`. This method primarily uses assertion but may use reflection for judgment. Empty values include: `0, nil, false, "", len(slice/map/chan) == 0`. + - Deprecated `SetServerGraceful` method, managed uniformly through `Server` configuration. +2. `gins` + - Improved code structure for easier maintenance. + - Enhanced and improved unit testing. +3. `gmvc` + - Added `M` type as an alias for `*gdb.Model` for convenience in toolchain auto-generated models containing the `M` attribute. + +### `text` + +1. `gstr` + - Added `HasPrefix/HasSuffix` methods. + - Introduced `OctStr` method for converting octal strings to corresponding `unicode` strings, such as Chinese. Commonly used in `gRPC` underlying communication encoding. + - Enhanced unit testing. + +### `debug` + +1. `gdebug` + - Improved code structure for easier maintenance. + - Added `TestDataPath` method for obtaining the absolute path of the `testdata` directory in the current package for package unit tests. + +### `util` + +1. `gconv` + + - Improved `String` conversion method with built-in support for `time.Time/*time.Time/gtime.Time` types. + - Enhanced `Map/Struct` conversion methods with additional detail handling for special scenarios. Continuous improvement based on extensive usage and feedback. + - Updated `Struct` conversion method to support `UnmarshalValue(interface{}) error` interface. + - Enhanced unit testing. +2. `grand` + + - Note: Incompatible adjustment, the original `Str` method renamed to `S` for obtaining a random string of specified length along with the `symbol` parameter, to specify whether special visible characters can be returned randomly. + - Added `Str` method for randomly retrieving a string of specified length from specified string characters, supporting `unicode` strings, such as Chinese: [https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index) + - Added `Symbols` method for randomly returning special visible characters for specified purposes: [https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index) + - Enhanced unit testing. +3. `gvalid` + + - Length validation rules now support `unicode` strings, such as Chinese. + +## Bug Fix + +1. Fixed the view object configuration issue in `Server`. +2. Fixed the issue where middleware in `Server` ignored `Middleware.Next` method control in the event of `panic` in middleware, causing authentication middleware to fail. +3. Resolved the `gudp.Server` packet loss problem when request packet size exceeds `64bytes`, and the default buffer size has been adjusted to `1024bytes`. Developers can customize buffer size. +4. Corrected `gfile.MTimeMillisecond` method to return the correct file modification millisecond timestamp. +5. Fixed `gconv.Int64` conversion support for negative numbers. +6. Various other fixes. +7. For details, see: [https://github.com/gogf/gf/issues?q=label%3Abug](https://github.com/gogf/gf/issues?q=label%3Abug) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" new file mode 100644 index 00000000000..11558656ec8 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" @@ -0,0 +1,226 @@ +--- +slug: '/release/v1.13.0' +title: 'v1.13 2020-06-10' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,Go development framework,modular design,Web service components,cache management,database ORM,TCP/UDP components,middleware support,configuration management,data validation] +description: "GoFrame is a modular, high-performance Go development framework that provides comprehensive basic and web service development components, such as cache management, logging, database ORM, TCP/UDP components, etc. The framework is designed with loose coupling, easy maintenance, and high-quality code, making it suitable for team and enterprise application development." +--- + +## GoFrame + +`GF(Go Frame)` is a modular, high-performance, production-grade Go foundational development framework. It provides comprehensive infrastructure and development toolchain, offering commonly used development modules such as caching, logging, queue, array, collection, container, timer, command line, memory lock, object pool, configuration management, resource management, data validation, data encoding, cron jobs, database ORM, TCP/UDP components, process management/communication, etc. Moreover, it includes core components for web service development, such as Router, Cookie, Session, Middleware, service registration, template engine, and it supports hot restart, hot update, domain binding, TLS/HTTPS, Rewrite, etc. + +### Features + +- Modular, loosely-coupled design; +- Rich modules, ready to use; +- Easy to use, easy to maintain; +- High code quality, high unit test coverage; +- Active community with humble and amiable experts; +- Comprehensive documentation and examples; +- Complete localization support in Chinese; +- Designed for team and enterprise use; + +### Development + +`GoFrame` originated in Beijing in 2011, from a smart IoT platform project before many current IoT standards existed, and when Go’s standard library and ecosystem were not as rich. It wasn't until 2017 that `GoFrame` released its test version, and in 2018, during the 1024 programmer’s festival, it released the official `v1.0` version, contributing to the Go ecosystem's development. Since its open-source launch, it has iterated quickly, growing widely favored by developers and enterprises, with many developers contributing. Originally designed for development teams, its development efficiency and maintainability are excellent, with high code quality and abundant unit tests and examples. `GoFrame` is currently the best Go development framework with localized Chinese documentation. + +## Change Log + +1. At the request of many developers, the minimum `Golang` runtime version required by the framework has been downgraded to `v1.11`. +2. Added new `GoFrame` video tutorial addresses: + - bilibili: [https://www.bilibili.com/video/av94410029](https://www.bilibili.com/video/av94410029) + - Xigua Video: [https://www.ixigua.com/pseries/6809291194665796100/](https://www.ixigua.com/pseries/6809291194665796100/) +3. The rarely-used `guuid` module has been moved to github.com/gogf/guuid as a community-maintained module, keeping the main `gf` repository lightweight. +4. Added `guid` module for efficient, lightweight unique string generation: [https://goframe.org/util/guid/index](https://wiki.goframe.org/util/guid/index) + +### `tool chain` + +1. Toolchain update: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) +2. Added `gf env` command for elegantly viewing current `Golang` environment variable information. +3. Added `gf mod path` command to copy the current `go modules` package to `GOPATH`, facilitating project development using the original `GOPATH` method. +4. Made improvements to existing `cli` commands, enhancing user experience; precompiled binary versions are `upx` compressed on some platforms, reducing file size for downloading. + +### `container` + +1. `garray` + - [https://goframe.org/container/garray/index](https://wiki.goframe.org/container/garray/index) + - Simplified array usage, supporting variable definitions like `var garray.Array`; + - Added `Walk` method for custom array element processing; + - Added `ContainsI` method for case-insensitive array element existence checking; + - Enhanced unit tests, code coverage at `94%`; + - Code refinement, performance improvement; + - Fixed some issues; +2. `gchan` + - Due to its limited practical significance, the package has been removed from the main framework; +3. `glist` + - [https://goframe.org/container/glist/index](https://wiki.goframe.org/container/glist/index) + - Simplified linked list usage, supporting variable definitions like `var glist.List`; + - Enhanced unit tests, code coverage at `99%`; +4. `gmap` + - [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) + - Simplified `Map` usage, supporting variable definitions like `var gmap.Map`; + - Enhanced unit tests, code coverage at `81%`; + - Code refinement, performance improvement; +5. `gset` + - [https://goframe.org/container/gset/index](https://wiki.goframe.org/container/gset/index) + - Simplified set usage, supporting variable definitions like `var gset.Set`; + - Added `Walk` method for custom set element processing; + - Enhanced unit tests, code coverage at `90%`; + - Code refinement, performance improvement; +6. `gtree` + - [https://goframe.org/container/gtree/index](https://wiki.goframe.org/container/gtree/index) + - Simplified tree usage, supporting variable definitions like `var gtree.BTree`; + - Enhanced unit tests, code coverage at `90%`; +7. `gvar` + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) + - Enhanced unit tests, code coverage at `69%`; + - Code organization structure adjustment for better maintainability; + - Code refinement, performance improvement; + +### `database` + +1. `gdb` + - Added `Transaction(f func(tx *TX) error) (err error)` interface method for transaction encapsulation using closure: [https://goframe.org/database/gdb/transaction](https://wiki.goframe.org/database/gdb/transaction) + - Removed the rarely used `From` interface method, improved `Table` and `Model` methods' parameters to variadic, supporting passing table aliases via variadic parameters: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - Added `DryRun` feature for executing queries without performing insert/update/delete operations: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - Added automatic field population features for `create_at`, `update_at` timestamp fields: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - Added `delete_at` soft delete feature: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - Added `Having` chain operation method for `having` condition queries: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - `Result` object added `Chunk` method for custom batch data processing: [https://goframe.org/database/gdb/result](https://wiki.goframe.org/database/gdb/result) + - Improved runtime database switching feature of `Schema`; + - Enhanced support for field types in `pgsql`, `mssql`, `sqlite`, `oracle` databases; + - Further improved unit tests; + - Code organization structure adjustment for better maintainability; + - Code refinement, performance improvement; +2. `gredis` + - Added default configuration for `MaxActive` connection pool parameter to `100` to limit the default number of connections; + - Improved `Conn` object's `Do` method to support automatic `json.Marshal` for `map/slice/struct` types, use `DoVar` method to retrieve data: [https://goframe.org/database/gredis/usage](https://wiki.goframe.org/database/gredis/usage) + - Enhanced unit tests, code coverage at `72%`; + +### `net` + +1. `ghttp` + + - Added `Prefix` and `Retry` client chain operation methods; + - Added client raw request printing feature: [https://goframe.org/net/ghttp/client/demo/dump](https://wiki.goframe.org/net/ghttp/client/demo/dump) + - Added `ClientMaxBodySize` server configuration to limit the client's submitted `Body` size, default is `8MB`; increase this configuration size in servers involving upload, specify the size in the configuration file, e.g., `ClientMaxBodySize="100MB"`: [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - Improved randomness of `SessionId` generation, enhancing `Session` security: [https://goframe.org/os/gsession/index](https://wiki.goframe.org/os/gsession/index) + - `ghttp.Server` now implements the standard library's `http.Handler` interface, facilitating code integration with third-party services like `Prometheus`; + - Numerous code detail improvements for performance and long-term maintainability; + - Enhanced unit tests, code coverage at `61%`; +2. `gipv4` + + - Added `GetIpArray` method for retrieving all current host IPv4 addresses; + - Added `GetMacArray` and `GetMac` methods for obtaining current host's `MAC` address information; + - Changed `IntranetIP` method name to `GetIntranetIp`, changed `IntranetIPArray` method name to `GetIntranetIpArray`; + +### `encoding` + +1. `gjson` + - Added `GetMaps` method to retrieve `JSON` internal node variable; + - Improved `NewWithTag` method to handle `map/struct`; + - Enhanced unit tests, code coverage at `77%`; +2. `gyaml` + - Upgraded the dependent third-party `yaml` parsing package, addressing the `map[interface{}]interface{}` conversion issue; + +### `error` + +1. `gerror` + - Added `NewfSkip` method for creating error objects with specified stack skip; + - Enabled stack trace printing throughout the framework to show real link call details when errors occur; + +### `os` + +1. `gcache` + + - Added `GetVar` method for obtaining "generic" variables easily convertible to other data types; + - Deprecated `Removes` method, improved `Remove` method to variadic parameters, unified usage for removing one/multiple key-value pairs; + - Enhanced unit tests, code coverage at `96%`; +2. `genv` + + - Added `GetVar` method for obtaining "generic" variables easily convertible to other data types; +3. `gfile` + + - Improved `CopyDir/CopyFile` methods; + - Added `ScanDirFunc` method for directory retrieval with custom processing callback support; + - Enhanced unit tests, code coverage at `64%`; +4. `glog` + + - Added logging feature supporting `Context` context variables: [https://goframe.org/os/glog/context](https://wiki.goframe.org/os/glog/context) +5. `gres` + + - Improved packaging feature, enhancing compression ratio of generated binary and Go files by 20%, making the compiled binaries smaller; + - Code structure improvement, enhancing execution efficiency and maintainability; +6. `gsession` + + - Improved default `SessionId` generation method using `guid.S`; + - Added `SetId` and `SetIdFunc` methods for custom `SessionId` generation methods; + +### `frame` + +1. `g` + - Added `g.Table` method for quickly creating database model operation objects; + +### `i18n` + +1. `gi18n` + - Added `GetContent` method for obtaining localized content for specified `i18n` keywords; + - Code detail improvements for increased performance and maintainability; + - Enhanced unit tests, code coverage at `74%`; + +### `test` + +1. `gtest` + - Added `AssertNQ` assertion method for strict type inequality checks; + +### `text` + +1. `gstr` + - Added `SubStrRune` method for string clipping with `unicode` support; + - Added `StrLimitRune` method for string truncation hiding with `unicode` support; + - Added `LenRune` method, replacing `RuneLen`, unifying method naming convention; + - Added `PosRune/PosIRune/PosRRune/PosRIRune` methods for left-right position searches with `unicode` support; + - Added `CompareVersionGo` method for Go-style version number comparison; + - Enhanced unit tests, code coverage at `75%`; + +### `util` + +1. `gconv` + + - Improved `Convert` method, supporting common `map` type conversion; + - Enhanced error handling during type conversion, returning via `error`; + - Various detail improvements; + - Enhanced unit tests, code coverage at `63%`; +2. `grand` + + - Added `B` method for obtaining random binary data; + - Improved underlying implementation, boosting performance of some interfaces by `50%`; + - Enhanced unit tests, code coverage at `74%`; +3. `guid` + + - Added `guid` module for efficient, lightweight unique string generation: [https://goframe.org/util/guid/index](https://wiki.goframe.org/util/guid/index) +4. `gutil` + + - Added `MapContains` method for checking if a map contains a specified key; + - Added `MapDelete` method for deleting specified keys in a map, accepting multiple key names; + - Added `MapMerge` method for merging two maps; + - Added `MapMergeCopy` method for copying multiple maps; + - Added `MapContainsPossibleKey` method for finding a specified key, ignoring cases and characters `'-'/'_'/'.'/' '`; +5. `gvalid` + + - All default error messages have been changed to English; + - Error message configuration is now implemented via `i18n` to support internationalization: [https://goframe.org/util/gvalid/message](https://wiki.goframe.org/util/gvalid/message) + - Renamed ID number rule from `id-number` to `resident-id`; + - Renamed bank card rule from `luhn` to `bank-card`; + - Enhanced unit tests, code coverage at `96%`; + +### Bug Fix + +1. Fixed multi-file `zip` compression issue in `gcompress`; +2. Fixed issue with `ghttp.Client` retrieving expired `Cookie`; +3. Fixed implementation detail of `http.File` interface in `gres.File`; +4. Fixed boundary issue in `garray.Pop*` methods; +5. Fixed issue with `gres` method `Readdir` reporting an error when parameter is `0`; +6. Other fixes: [https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug](https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" new file mode 100644 index 00000000000..45846441bc4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" @@ -0,0 +1,194 @@ +--- +slug: '/release/v1.14.0' +title: 'v1.14 2020-10-27' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,Go framework,high performance,Web service,modularity,cache,ORM,development tools,community support,localization] +description: "GoFrame is a modular, high-performance Go development framework that provides rich foundational development modules and Web service core components, including cache, logging, data validation, database ORM, etc. The framework supports hot restart, TLS/HTTPS, and Redis adapters, offering high code quality and comprehensive Chinese support, suitable for team and enterprise use." +--- + +## GoFrame + +`GoFrame` is a modular, high-performance, production-grade Go foundational development framework. It implements comprehensive infrastructure and development toolchains, offering common foundational development modules such as cache, logging, queues, arrays, collections, containers, timers, command lines, memory locks, object pools, configuration management, resource management, data validation, data encoding, scheduled tasks, database ORM, TCP/UDP components, process management/communication, etc. It also provides a series of core components for Web service development, such as `Router`, `Cookie`, `Session`, `Middleware`, service registration, template engine, etc., supporting features like hot restart, hot updates, domain binding, `TLS/HTTPS`, `Rewrite`, etc. + +### Features + +- Modular and loosely-coupled design; +- Rich modules, ready to use; +- Simple and easy to use, easy to maintain; +- High code quality, high unit test coverage; +- Active community, with humble and approachable experts; +- Detailed development documentation and examples; +- Comprehensive local Chinese support; +- Designed for team and enterprise use; + +### Support Us + +The OSC Best Open Source Project competition has begun. If you like `GoFrame`, please cast your valuable vote for `GoFrame` 🙏 [https://www.oschina.net/p/goframe](https://www.oschina.net/p/goframe) + +## Change Log + +Since `GoFrame` is designed modularly, the update log for each version is introduced in the form of modules. + +Important updates: + +1. Replaced all `json` operations within the framework from the standard library to `json-iterator/go`, improving operational efficiency. +2. Refactored the underlying design of the cache module, adding adapter design patterns, and adding memory and `Redis` adapter support. The memory adapter is provided by the default core module, while the `Redis` adapter is provided by the community module: [https://goframe.org/os/gcache/adapter](https://wiki.goframe.org/os/gcache/adapter) +3. Added customizable validation rule registration feature: [https://goframe.org/util/gvalid/customrule](https://wiki.goframe.org/util/gvalid/customrule) +4. `Web Server` added examples for all configuration items: [https://goframe.org/net/ghttp/config/example](https://wiki.goframe.org/net/ghttp/config/example) +5. `ORM` added `SQL` cache adapter based on `Redis`: [https://goframe.org/database/gdb/model/cache](https://wiki.goframe.org/database/gdb/model/cache) +6. `ORM` added experimental feature for model associations: [https://goframe.org/database/gdb/model/association](https://wiki.goframe.org/database/gdb/model/association) +7. Improved automatic update feature of time in `ORM`, adding customizable time fields: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) +8. Error handling module added `Current` and `Next` methods: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) + +### `net` + +1. `ghttp` + - `Client` + - Added `GetVar/PutVar/PostVar` requests methods, which allow sending `HTTP` requests and directly returning generic objects for convenient type conversion, making handling of returned `XML/JSON` results simpler: [https://goframe.org/net/ghttp/client/demo/index](https://wiki.goframe.org/net/ghttp/client/demo/index) + - Added `SetProxy/Proxy` methods to set client proxy, supporting `HTTP/Socket5` proxy types: [https://goframe.org/net/ghttp/client/demo/proxy](https://wiki.goframe.org/net/ghttp/client/demo/proxy) + - Added `SetRedirectLimit/RedirectLimit` methods to set page redirect limit. + - `Request` + - Added `ParseQuery`, `ParseForm` methods to parse specified parameter types and bind them to a given object. + - Added `GetHeader` method to get specified `Header` parameters. + - Added `GetRemoteIp` method to get the IP of the requesting client. When using IP whitelist restrictions, use `GetRemoteIp` instead of `GetClientIp`, as the latter can be forged through `Header`. + - Added `ReloadParam` method, often used in middleware processing when middleware modifies request parameters and calls this method to reparse them. + - Added `GetRouterMap` method to get all routing parameters returned as a `map`. + - `Response` + - Renamed `Output` method to `Flush`, used for writing buffer data to the client stream. + - `Server` + - `Server` added examples for all configuration items: [https://goframe.org/net/ghttp/config/example](https://wiki.goframe.org/net/ghttp/config/example) + - Added `SessionCookieOutput` configuration to control whether to output `SessionId` to `Cookie`, enabled by default. + - Improved route parsing to support `URI` with repeated `/` symbols. + - `Pprof` function routes support `Domain` binding. + - Other minor improvements. + - `Cookie` + - Added `SetHttpCookie` method to set `Cookie` based on a standard library `http.Cookie` object. + - Other functional improvements. + +### `database` + +1. `gdb` + + - Added experimental feature for model associations: [https://goframe.org/database/gdb/model/association](https://wiki.goframe.org/database/gdb/model/association) + - Improved automatic update feature of time, adding customizable time fields: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - Added `SQL` cache adapter based on `Redis`: [https://goframe.org/database/gdb/model/cache](https://wiki.goframe.org/database/gdb/model/cache) + - Added automatic recognition mapping feature for input parameter key names and field names: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - Added `DB.HasTable` method to determine whether the current database contains the specified table. + - Added `Model.HasField` method to determine whether the current table contains the specified field. + - Added `Model.ScanList` method to intelligently bind the current `struct`/`slice` to the specified `list` corresponding attributes. + - Added `Result.MapKeyValue` method to convert the current `Result` to `map[string]Value` type. + - Added `Result.IsEmpty/Len/Size/ScanList` methods. + - Added `ListItemValues` and `ListItemValuesUnique` methods to automatically get the values or properties with specified names in the `list`, returning them as a `slice`. + - `SQL` log content includes group name printing. + - Improved `DataToMapDeep` method. + - Other minor improvements. +2. `gredis` + + - Added `TLS` feature support and configuration file support, [https://goframe.org/database/gredis/config](https://wiki.goframe.org/database/gredis/config) + +### `container` + +1. `gvar` + - Added `Scan` and `ScanDeep` methods for automatic recognition and conversion of `struct`/`slice`. + - Added `ListItemValues` and `ListItemValuesUnique` methods to automatically get the values or properties with specified names in the `list`, returning them as a `slice`. + - Added `MapStrAny` interface implementation method. + +### `os` + +1. `gcache` + + - Added `GetVar` method to get cached data and return it as a generic object. + - Added `Update` method to only modify cache values without modifying expiration times. + - Added `UpdateExpire` method to only modify cache expiration times without modifying values. + - Refactored underlying design, adding adapter design patterns, and memory and `Redis` adapter support. The memory adapter is provided by the default core module, and the `Redis` adapter is provided by the community module: [https://goframe.org/os/gcache/adapter](https://wiki.goframe.org/os/gcache/adapter) + - Note, this module modification may have some method incompatibility, some methods added `error` parameter return, please review carefully when upgrading. Compilation will not pass. + - Other functional improvements. +2. `gfile` + + - Added `ScanDirFileFunc` method for recursive directory file traversal with a custom function handler. + - Improved `Scan*` methods with added recursion level limits, default is `100000`. +3. `gfsnotify` + + - Removed `Watcher` object creation during module initialization, adjusted to runtime on-demand creation, with added concurrency safety controls. +4. `grpool` + + - Added `AddWithRecover` method for adding asynchronous tasks with a given `recover` handling method, processing `panic` in tasks to prevent the entire process crash. + - Solves the issue that `recover` can only capture `panic` in the current `goroutine`, therefore a specific `recover` handling method can only be specified when creating asynchronous tasks. +5. `gtime` + + - Added `ParseDuration` method with support for time unit `d` representing days. + - Improved `` method with support to create `gtime.Time` objects from strings, timestamps, `time.Time` objects, [https://goframe.org/os/gtime/time](https://wiki.goframe.org/os/gtime/time) + - Improved `Add/AddStr/ToLocation/ToZone/UTCLocal/AddDate/Truncate/Round` methods, these methods now create and return new `gtime.Time` objects without altering the current object itself to ensure consistency with the standard library `time.Time` logic and avoid confusion. + - Other minor improvements. +6. `gtimer` + + - Added `Reset` method for resetting timer task counts. +7. `gfcache` + + - Removed the module, as its functionality was not particularly significant. + +### `debug` + +1. `gdebug` + - Added `GoroutineId` method to get the `goroutine id` of the current execution, for debugging purposes only. + +### `encoding` + +1. `gjson` + + - Added `GetScan/GetScanDeep` methods. + - Added `ToScan/ToScanDeep` methods. + - Added `LoadContentType` method to create `Json` operation objects based on specified content types. + - Added `IsValidDataType` method to determine if a given data type is parseable. + - Other improvements. + - Comprehensive unit testing. +2. `gcompress` + + - Added `GzipFile/UnGzipFile` methods based on `gzip` compression algorithm for file compression/decompression. + +### `i18n` + +1. `gi18n` + - Added `TranslateFormat/TranslateFormatLang/Tf/Tfl` methods: [https://goframe.org/i18n/gi18n/index](https://wiki.goframe.org/i18n/gi18n/index) + +### `text` + +1. `gstr` + - Added `SnakeFirstUpperCase` method, which adds connectors before uppercase letters and does not process numbers, e.g., `SnakeFirstUpperCase("RGBCodeMd5")` will return `rgb_code_md5`. + +### `util` + +1. `gconv` + + - Added support for basic pointer type conversion. + - Added `Scan/ScanDeep` methods for automatic recognition and conversion of `Struct/[]Struct`. + - Improved recursive layer handling in `MapDeep` method. + - Other minor and performance improvements. +2. `gutil` + + - Added `ListItemValues` and `ListItemValuesUnique` methods to automatically get values or properties with specified names in the `list`, returning them as a `slice`. + - Added `ItemValue` method to get key/property values of specified `map/*map/struct/*struct` types. + - Added `MapOmitEmpty` method to filter empty values in a `map`. + - Added `SliceDelete` method for array item deletion. + - Added `Try` method, which executes a given method through a closure and returns `error` if the method `panic`, otherwise returns `nil`. + - Improved `TryCatch(try func(), catch ...func(exception interface{}))` to `TryCatch(try func(), catch ...func(exception error))` +3. `gvalid` + + - Added custom rule feature, allowing developers to register custom validation rules: [https://goframe.org/util/gvalid/customrule](https://wiki.goframe.org/util/gvalid/customrule) + - Other functional improvements. + +### `error` + +1. `gerror` + - Added `Current` method to get the `error` interface object of the current error level. + - Added `Next` method to get the next level error `error` interface object. If the next level does not exist, returns `nil`. + - Documentation update: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) + +### Bug Fix + +1. Fixed `Unique` method issue in `garray` module. +2. Fixed `goroutine` leak issue in `glog` during lazy initialization of the timer. +3. Fixed naming `Case` conversion issues in `gstr` when names contain numbers and special characters. +4. Fixed `CORS` cross-domain setting `Header` detail issue in `ghttp` module. +5. Other BUG fixes: [https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed](https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed) \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" new file mode 100644 index 00000000000..bf6b48e753e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" @@ -0,0 +1,145 @@ +--- +slug: '/release/v1.15.0' +title: 'v1.15 2020-12-31' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame, Golang, enterprise development framework, web development, high-performance framework, modular design, active community, open source project, unit testing, database component] +description: "GoFrame v1.15 version offers a series of new features and improvements, providing efficient and modular framework support for enterprise-level Golang development. This update covers various aspects such as HTTP client improvements, database ORM feature enhancements, and error code support. With high code quality, comprehensive development documentation, and active community support, GoFrame provides a convenient project development environment for developers and is widely used in various internet companies." +--- + +Hello everyone! From late autumn to deep winter, we haven't been idle since the last release. We've brought you a heartwarming `GoFrame v1.15` version this time. In addition, there are two other things: + +- `GoFrame` was selected by `OSC` Open Source China as one of the `TOP30` open source projects of `2020`: [https://www.oschina.net/question/2918182_2320114](https://www.oschina.net/question/2918182_2320114). Thank you for your recognition and support! Meanwhile, `GoFrame` is also a `Gitee GVP` [Most Valuable Project](https://gitee.com/johng/gf). +- The official website of `GoFrame` has been redesigned. You only need to experience it for a few minutes, and you'll be like me, loving this framework: [https://goframe.org](https://wiki.goframe.org). Thanks to [Atlassian](https://www.atlassian.com/) for the sponsorship, providing full product line license codes! + +After years of dedicated development and stable growth, `GoFrame` has gradually grown into an enterprise-level `Golang` basic development framework, offering project development standards, development toolchains, complete basic modules, rich development documentation, high code quality, and an active community. To ensure the quality of the framework, we've conducted extensive unit testing across components to ensure logical correctness (`2534` test units, `9097` test assertions), while maintaining high-quality documentation. To date, many large, medium, and small internet companies have used `GoFrame` in production environments. + +Open source is not easy, and with your understanding and support, happiness is abundant! Thank you to all the partners involved in the project development, love you all! `GoFrame, YES!` + +## `GoFrame` + +`GF(Go Frame)` is a modular, high-performance, enterprise-level `Go` basic development framework. It provides a complete infrastructure and development toolchain, offering commonly used basic development modules such as caching, logging, queues, arrays, sets, containers, timers, command lines, memory locks, configuration management, resource management, data validation, scheduled tasks, database `ORM`, `TCP/UDP` components, process management/communication, etc. It also provides a series of core components for `Web` service development, such as `Router`, `Cookie`, `Session`, `Middleware`, service registration, template engine, and supports hot restart, hot update, domain binding, `TLS/HTTPS`, `Rewrite`, and other features. + +> If you are new to the `Go` language, you can compare `GoFrame` to `Laravel` in `PHP`, `SpringBoot` in `Java`, or `Django` in `Python`. + +![](https://wiki.goframe.org/download/attachments/1114119/arch.png?version=1&modificationDate=1608537397031&api=v2) + +## Features + +- Modular, loosely coupled design; +- Rich modules, ready to use; +- Simple and easy to use, easy to maintain; +- High code quality, high unit test coverage; +- Active community, humble and low-key experts; +- Detailed development documentation and examples; +- Comprehensive local Chinese support; +- Designed for team and enterprise use; + +## Change Logs + +1. `ghttp` + - Improved `HTTPClient` `GET` request methods, automatically constructing parameters as `QueryString` instead of `Body` to ensure compatibility with other servers. + - Added default value setting feature to `Request` objects: [Request - Default Value](../../docs/WEB服务开发/请求输入/请求输入-默认值绑定.md) + - Added `Request.SetCtx` method for custom context variables, commonly used in middleware/interceptors: [Request - Context](../../docs/WEB服务开发/请求输入/请求输入-Context.md) + - Added `Request` variable in template parsing to get client request parameters regardless of `QueryString/Form` type: [Response - Template Parsing](../../docs/WEB服务开发/数据返回/数据返回-模板解析.md) + - Improved `Cookie` functionality. For setting `Cookies` with the same expiration as `Session`, refer to: [Cookie#Cookie Session Expiry](https://wiki.goframe.org/display/gf/Cookie#Cookie-Cookie%E4%BC%9A%E8%AF%9D%E8%BF%87%E6%9C%9F) + - Added `ALLMap` method in group route registration for bulk route registration: [Group Route#Bulk Registration](https://wiki.goframe.org/pages/viewpage.action?pageId=1114517#id-%E5%88%86%E7%BB%84%E8%B7%AF%E7%94%B1-%E6%89%B9%E9%87%8F%E6%B3%A8%E5%86%8C) + - Introduced `CRSF` plugin documentation: [CSRF](../../docs/WEB服务开发/高级特性/CSRF防御设置.md) + - Other feature and detail improvements. + +2. `gdb` + - Added `Ctx` method for asynchronous `IO` control or custom context information transmission, especially for tracing: [ORM - Context](../../docs/核心组件/数据库ORM/ORM上下文变量.md) + - Added `Raw` type for embedding raw `SQL` statements which will directly submit to the underlying database driver without any processing: [Writing#RawSQL Statement Embedding](https://wiki.goframe.org/pages/viewpage.action?pageId=1114344#id-%E5%86%99%E5%85%A5%E4%BF%9D%E5%AD%98-RawSQL%E8%AF%AD%E5%8F%A5%E5%B5%8C%E5%85%A5), [Updating#RawSQL Statement Embedding](https://wiki.goframe.org/pages/viewpage.action?pageId=1114238#id-%E6%9B%B4%E6%96%B0%E5%88%A0%E9%99%A4-RawSQL%E8%AF%AD%E5%8F%A5%E5%B5%8C%E5%85%A5) + - Improved `Fields/Fields/Data` methods with automatic mapping detection and filtering for input `map/struct` parameters to database fields: [ORM - Senior Features](../../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性.md) + - Configurable `InsertedAt/UpdatedAt/DeletedAt` field names and `TimeMaintainDisabled` setting added to disable time filling and soft delete features: [ORM Model - Time Fields](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-时间维护/ORM链式操作-时间维护.md) + - Added `Counter` update feature for incrementing/decrementing field values: [Updating#Counter Update Feature](https://wiki.goframe.org/pages/viewpage.action?pageId=1114238#id-%E6%9B%B4%E6%96%B0%E5%88%A0%E9%99%A4-Counter%E6%9B%B4%E6%96%B0%E7%89%B9%E6%80%A7) + - Improved `ORM` timezone processing; see: [ORM - Timezone](../../docs/核心组件/数据库ORM/ORM时区处理.md) + - Other performance and usability detail improvements. + - Improved some detail issues. + - Enhanced unit tests. + +3. `gerror` + - Added `Newf/NewSkipf` methods for creating error objects: [Error Handling](../../docs/核心组件/错误处理/错误处理.md) + - Added support for error code features: [Error Code - Example](../../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) + - Enhanced unit tests. + +4. `gvalid` + - Added `phone-loose` loose mobile number verification rule, allowing any `11`-digit number starting with `13/14/15/16/17/18/19`. + - Validation errors implement the `gerror` `Current() error` interface, allowing the use of `gerror.Current` to get the current first validation error: [Data Validation - Result](../../docs/核心组件/数据校验/数据校验-校验结果.md) + - Other detail improvements. + - Enhanced unit tests. + +5. `gvar` + - Added `IsNil/IsEmpty` methods to check if data is `nil/empty`. + - Added `IsInt/IsUint/IsFloat/IsSlice/IsMap/IsStruct` common type checking methods. + - Deprecated `StructDeep/StructDeep` methods, recommending to use `Struct/Structs` directly. + - Enhanced unit tests. + +6. `ghtml` + - Added `SpecialCharsMapOrStruct` method for automatically converting `HTML` code in `map/struct` keys/attributes to prevent `XSS`. + +7. `gjson` + - Deprecated `To*` conversion methods, recommending `Struct` method instead of `ToStruct`. + - Some detail improvements. + - Enhanced unit tests. + +8. `internal` + - Improved and refined `internal/empty` package's null value judgment. + - Due to performance issues [https://github.com/gogf/gf/issues/1004](https://github.com/gogf/gf/issues/1004), temporarily removed the dependency of the `internal/json` package on the third-party package [github.com/json-iterator/go](http://github.com/json-iterator/go), reverting to using the standard library `encoding/json`. + - Improved `internal/structs` package by removing the dependency on the third-party package [github.com/gqcn/structs](http://github.com/gqcn/structs), simplifying the reflection processing logic, improving performance and usability, and enhancing long-term maintainability. + - Added `RemoveSymbols` method in the `internal/utils` package for removing special characters in a string. Improved `EqualFoldWithoutChars` method by removing regex filtering of substrings, greatly enhancing method performance. These small functions contribute to improving the performance of other complex type conversion modules in the framework. Although the `internal` package does not expose methods directly, it impacts the performance of some core components within the framework. + +9. `gcfg` + - Improved singleton instance configuration object retrieval with auto-detect file type feature: [Configuration Management - Singleton Object#Auto-Retrieval Feature](https://wiki.goframe.org/pages/viewpage.action?pageId=1114194#id-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86%E5%8D%95%E4%BE%8B%E5%AF%B9%E8%B1%A1-%E8%87%AA%E5%8A%A8%E6%A3%80%E7%B4%A2%E7%89%B9%E6%80%A7) + - Other detail improvements. + +10. `gcmd` + - Improved default parameter parsing retrieval method: [Command](../../docs/组件列表/系统相关/命令管理-gcmd.md) + - Added `GetWithEnv` method to retrieve from environment variables if a specified parameter is absent in the command line: [Command](../../docs/组件列表/系统相关/命令管理-gcmd.md) + +11. `genv` + - Added `SetMap` method for batch setting environment variables. + - Added `GetWithCmd` method to retrieve from command line parameters if a specified parameter is absent in environment variables: [Environment](../../docs/组件列表/系统相关/环境变量-genv.md) + +12. `gfile` + - Deprecated `ReadByteLines` method; added `ReadLinesBytes` method. + - Adjusted callback function definition of `ReadLines/ReadLinesBytes` methods to include `error` return. + +13. `glog` + - Improved rolling update functionality. + - Other detail improvements. + +14. `gsession` + - Added `SetMap` method for batch setting key-value pair data. + +15. `gtimer` + - Improved constant naming to use PascalCase style. + +16. `gview` + - Added built-in template function `map` to convert parameters to `map[string]interface{}` type. + - Added built-in template function `maps` to convert parameters to `[]map[string]interface{}` type. + - Added built-in template function `json` to convert parameters to `JSON` string type. + - Documented updates: [Template Funcs - Built-In](../../docs/核心组件/模板引擎/模板引擎-模板函数/模板函数-内置函数.md) + - Other detail improvements. + +17. `gconv` + - Performance improvements. + - Functionality improvements (many small detail improvements). + - More robust code. + - Enhanced unit tests. + +18. `gutil` + - Added `Keys` method to get keys/attribute names of `map/struct` and return as an array. + - Added `Values` method to get values/attributes of `map/struct` and return as an array. + - Added `MapToSlice` method, for example: `{"K1": "v1", "K2": "v2"} => ["K1", "v1", "K2", "v2"]` + - Added `StructToSlice` method, for example: `{"K1": "v1", "K2": "v2"} => ["K1", "v1", "K2", "v2"]` + - Added `SliceToMap` method, for example: `["K1", "v1", "K2", "v2"] => {"K1": "v1", "K2": "v2"}` + - Enhanced unit tests. + - Other detail improvements. + +## Bug Fix + +1. Fixed concurrency safety issue in `Clone` method of `garray/gmap/gtree`. +2. Fixed issue where `gpool` did not automatically invoke expiration method for expired elements when expiration method was set. +3. Fixed duplication issue in `gfile.ReadLines/ReadLineBytes` when reading large amounts of data. +4. Other bug fixes. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" new file mode 100644 index 00000000000..d47128a6b17 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" @@ -0,0 +1,105 @@ +--- +slug: '/release/v1.16.0' +title: 'v1.16 2021-06-01' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame framework,Golang,Full tracing,Database ORM,HTTP client,Data validation,Timer component,Error stack,Development toolchain] +description: "GoFrame v1.16 version is newly released, introducing full tracing features, with multiple improvements such as database ORM model association, nested transactions, subqueries, and more. The HTTP client has a middleware interceptor added, the data validation component supports enhanced Context and I18N, the timer component is newly restructured, and there are many code optimizations." +--- + +`Hello`, everyone! You've been waiting! It's been just six months since the last release, and many things have happened in this half-year. Schrödinger's cat determines different outcomes for the kitten by different means of observation, and similarly, the way we view the world shapes how the world appears to us. This time we bring you the latest `GoFrame v1.16` version! `GoFrame` is a modular, high-performance, enterprise-grade `Go` foundational development framework: [https://goframe.org](https://wiki.goframe.org), a low-key, pragmatic enterprise-grade `Golang` development framework in the true sense! This update includes numerous new features and enhancements, especially full tracing, `ORM` model association/nested transactions/subqueries/dozens of new methods, `HTTP` client interceptor, data validation and `I18N` enhancements, refactored timer, and more. There are a lot of updates this time, below are the major updates, hope you like them! `Enjoy!` + +![](/markdown/86fedaae17d9c3ed7be8d93a1f31d5bd.png) + +This documentation has been extensively updated, with the total development documentation now containing over 200,000 words. It is recommended to read according to the official directory structure. + +## Important Features + +1. The framework introduces **full tracing**, adopting the `OpenTelemetry` standard, currently integrating `HTTP Client&Server/GRPC Client&Server/ORM/Redis/Logging` components. For detailed introduction, please refer to the sections: + 1. [Tracing - Basic Example](../../docs/服务可观测性/服务链路跟踪/链路跟踪-基本示例.md) + 2. [Tracing - HTTP Example](../../docs/服务可观测性/服务链路跟踪/链路跟踪-HTTP示例/链路跟踪-HTTP示例.md) + 3. [Tracing - GRPC Example](../../docs/服务可观测性/服务链路跟踪/链路跟踪-GRPC示例.md) +2. The core database components have added the following features: + 1. **Model Association**: + 1. [Model Association - ScanList](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-动态关联-ScanList.md) + 2. [Model Association - With](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) + 2. **Nested Transactions**: [ORM - Transaction](../../docs/核心组件/数据库ORM/ORM事务处理/ORM事务处理.md) + 3. **Subquery** feature: [Model Query - Subquery](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-子查询特性.md) + 4. Added **dozens** of ORM model operation methods (referencing `PHP Laravel`), it is highly recommended for those using `goframe` to take a look: + 1. [ORM - Model 🔥](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作.md) + 2. [ORM Model - Insert/Save](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) + 3. [ORM Model - Update/Delete](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-更新删除.md) + 4. [ORM Model - Query](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM链式操作-数据查询.md) +3. HTTP **client adds middleware interceptor** functionality, for details, please refer to the section: [HTTPClient - Middleware](../../docs/WEB服务开发/HTTPClient/HTTPClient-拦截器中间件.md) +4. Extensive improvements to the data validation component: + 1. Added chain operation verification objects: [Data Validation - Object](../../docs/核心组件/数据校验/数据校验-校验对象.md) + 2. Added support for `Context`, and enhanced support for powerful `I18N` international error information management. For details, please refer to the sections: + 1. [I18N](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. [Data Validation - Error Msg](../../docs/核心组件/数据校验/数据校验-自定义错误.md) + 3. Custom validation rules have been improved, with added features for local validation rule registration and full data validation: [Data Validation - Custom](../../docs/核心组件/数据校验/数据校验-自定义规则/数据校验-自定义规则.md) +5. The timer component `gtimer` is fully restructured, removing the `TimingWheel` implementation and adopting a more robust `PriorityQueue` improved implementation. For details, refer to the section: [Timer](../../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +6. The framework's core foundational components have added a full error stack feature, allowing complete retrieval of error stack information for related components in the event of an error. This feature is only available in a well-designed, comprehensive foundational component framework. + +## Functionality Improvements + +1. `ORM` + 1. Added full tracing context `Context` parameter passing: [ORM - Context](../../docs/核心组件/数据库ORM/ORM上下文变量.md) During tracing, it will record `SQL` and database connection information (excluding sensitive configurations), and the component tracing information can be configured to be turned off. + 2. Further improved the `ORM` component logging, which is only valid in debug mode. For detailed introduction, refer to the section: [ORM - Senior Features](../../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性.md) + 3. Added dozens of `ORM` model operation methods referencing `PHP Laravel`, such as `InsertAndGetId`, `Min/Max/Avg/Sum`, `Increment/Decrement`, `WhereBetween/WhereLike/WhereIn/WhereNull`, `OrderAsc/OrderDesc/OrderRandom`, etc. It is highly recommended for those using `goframe` to take a look, for details, refer to the section: + 1. [ORM - Model 🔥](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作.md) + 2. [ORM Model - Insert/Save](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) + 3. [ORM Model - Update/Delete](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-更新删除.md) + 4. [ORM Model - Query](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM链式操作-数据查询.md) + 4. In the new version, the database `ORM` chain operation has enabled field filtering by default. Parameters that cannot be intelligently matched with table fields will be automatically filtered. For details, refer to the section: [ORM Model - Fields Filtering](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) + 5. Improved the conversion of `pgsql` database type `int8` to `Golang` type, from `int` type adjusted to `int64`. + 6. Extensive refactoring and improvement work have been done, too detailed to describe each. The key result is richer component functionality, more rigorous code, simpler design, and easier use. +2. `HTTP` + 1. `HTTP Client` adds middleware interceptor functionality: [HTTPClient - Middleware](../../docs/WEB服务开发/HTTPClient/HTTPClient-拦截器中间件.md) + 2. `HTTP Client&Server` adds tracing support: [Tracing - HTTP Example](../../docs/服务可观测性/服务链路跟踪/链路跟踪-HTTP示例/链路跟踪-HTTP示例.md) + 3. The client request method in the `ghttp` package is marked deprecated, and will be unified to use the `HTTP Client` object in the future. + 4. Improved the data validation of `Request.Parse` method, directly validating submitted parameters instead of the `struct` object converted from data: [Request - Validation](../../docs/WEB服务开发/请求输入/请求输入-请求校验.md) + 5. Added `WrapF/WrapH` methods to convert standard library `http.HandlerFunc/http.Handler` to service method types supported by `ghttp.Server`. + 6. Numerous other improvements, with many details. The key outcome is richer component functionality, more rigorous code, and easier use. +3. `gvalid` + 1. The `Check` method name is changed to `CheckValue`, for details, refer to the section: [Data Type - Value](../../docs/核心组件/数据校验/数据校验-参数类型/数据校验-单数据校验.md) + 2. Added `CheckStructWithData` method to validate the `struct` object of specified parameters: [Struct Validation - Example](../../docs/核心组件/数据校验/数据校验-参数类型/数据校验-Struct校验/Struct校验-基本使用.md) + 3. Added `Validator` validation object for convenient chain operation, allowing further expansion later: [Data Validation - Object](../../docs/核心组件/数据校验/数据校验-校验对象.md) + 4. Custom rule method definitions now include `Context` variables and added `RuleFunc` and `RuleFuncMap` local validation rule registration methods: [Data Validation - Custom](../../docs/核心组件/数据校验/数据校验-自定义规则/数据校验-自定义规则.md) + 5. Validation methods have added `Context` parameter support, and improved to support powerful `I18N` international error information management capabilities: + 1. [I18N](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. [Data Validation - Error Msg](../../docs/核心组件/数据校验/数据校验-自定义错误.md) + 6. `Error` object is modified to interface implementation, note that previous usage with `*Error` pointer will result in errors. + 7. Some other detail improvements. +4. `gcache` + 1. Added `Ctx` chain operation method for context parameter input, for details, refer to the section: [Caching](../../docs/核心组件/缓存管理/缓存管理.md) + 2. The `Adapter` interface adds `Context` parameter input, facilitating context parameter passing, and improved adapters in built-in implementation supporting context parameter passing: [Caching - Interface](../../docs/核心组件/缓存管理/缓存管理-接口设计.md) +5. `gredis` + 1. Added support for tracing, for details, refer to the section: Redis-Context +6. `gjson` + 1. The `Option` type name was changed to `Options`, this is a breaking change. + 2. The `NewWithOption` method name was changed to `NewWithOptions`. +7. `gcmd` + 1. Added `GetOptWithEnv` method and marked `GetWithEnv` method as deprecated. +8. `glog` + 1. Added tracing support based on the `OpenTelemetry` standard: [Logging - Context](../../docs/核心组件/日志组件/日志组件-Context.md) +9. `gproc` + 1. Added unified signal registration listening callback feature: [Process - Signal](../../docs/组件列表/系统相关/进程管理-gproc/进程管理-信号监听.md) +10. `gres` + 1. Best practices for resource management can be found here: [Resource - Best Practices](../../docs/核心组件/资源管理/资源管理-最佳实践.md) +11. `gtimer` + 1. Timer component `gtimer` is fully restructured, removing the `TimingWheel` implementation, and adopting a more robust `PriorityQueue` improved implementation. Details can be found here: [Timer](../../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +12. `gview` + 1. All template parsing methods added a `Context` parameter input: [Template Engine](../../docs/核心组件/模板引擎/模板引擎.md) +13. `gconv` + 1. Improved `Scan` method by adding automatic conversion support for `Map/Maps` parameter types: [Type Conversion - Scan](../../docs/核心组件/类型转换/类型转换-Scan转换.md) +14. `gi18n` + 1. The `I18N` internationalization component adds support for `Context`, for details, refer to the section: [I18N](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. Note that all methods now have a `ctx` parameter input, and some methods have removed the `language` parameter, instead controlled by `ctx` to input `language`, enhancing extensibility: [I18N - Example](../../docs/核心组件/I18N国际化/I18N国际化-使用介绍.md) + 3. Removed `TranslateFormatLang` and `Tfl` methods. +15. `gmeta` + 1. Newly added `gmeta` metadata package, for details, refer to the section: [Metadata](../../docs/组件列表/实用工具/元数据-gmeta.md) +16. Other detailed improvements in various components, not necessary to specifically detail in the release documentation. + +## Development Toolchain + +`CLI` tools have been updated, mainly simplifying `dao` model code generation, eliminating redundant method generation, and removing methods that directly return model objects. The `dao` object is modified to directly inherit the `GoFrame ORM` component's `Model` object, accepting query data through the specified model, hence the ways of using some methods may require adjustments. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" new file mode 100644 index 00000000000..6d7a2902232 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" @@ -0,0 +1,50 @@ +--- +slug: '/release/v1.2.0' +title: 'v1.2 2018-11-26' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame framework, SQLServer support, Oracle support, gvalid validation, ghttp improvement, template engine functions, gconv improvement, gform configuration, gfsnotify, WebSocket cross-origin] +description: "New features, new functionalities, and improvements in GoFrame framework version v1.2. This version adds support for SQLServer and Oracle databases, enhances the sequential features of the gvalid module, and improves the handling mechanism of ghttp.Request. The template engine introduces multiple built-in functions, and various upgrades and optimizations in modules such as gvar, gform, and gfsnotify." +--- + +### New Features + +1. `ORM` adds support for `SQLServer` and `Oracle` ([https://goframe.org/database/orm/database](https://wiki.goframe.org/database/orm/database)); +2. Completes the sequential feature of validation results in the `gvalid` module ([https://goframe.org/util/gvalid/checkmap](https://wiki.goframe.org/util/gvalid/checkmap)); +3. Improves `ghttp.Request.Exit` to enable immediate business execution exit when called, sparing developers from using `return` after the `Exit` method call ([https://goframe.org/net/ghttp/service/object](https://wiki.goframe.org/net/ghttp/service/object)); +4. Adds several built-in functions to the template engine: `text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ([https://goframe.org/os/gview/funcs](https://wiki.goframe.org/os/gview/funcs)); +5. Adds built-in variable `Config` to the template engine ([https://goframe.org/os/gview/vars](https://wiki.goframe.org/os/gview/vars)); +6. Improves the default conversion rules of `gconv.Struct`, supporting case-insensitive matching of key names and attribute names; +7. `gform` configuration files support `linkinfo` custom database connection fields ([https://goframe.org/database/orm/config](https://wiki.goframe.org/database/orm/config)); +8. The `gfsnotify` module adds the ability to unregister specific callbacks ([https://goframe.org/os/gfsnotify/index](https://wiki.goframe.org/os/gfsnotify/index)); + +### New Functionalities + +1. Improves `ghttp.Request` by adding methods `SetParam/GetParam` for setting/getting custom variables in the request process, allowing variable sharing in the callback functions during the request process ([https://goframe.org/net/ghttp/request](https://wiki.goframe.org/net/ghttp/request)); +2. Improves `ghttp.Response` by adding the `ServeFileDownload` method for guiding web server to client-side file downloads ([https://goframe.org/net/ghttp/response](https://wiki.goframe.org/net/ghttp/response)); +3. The `gvar` module introduces the `gvar.VarRead` read-only interface to control the exposure of data read functionality; +4. Adds `g.Throw` for exception throwing and `g.TryCatch` for exception catching methods; +5. Improves the `gcron` module with the addition of a custom Cron management object and adds `New/Start/Stop` methods; + +### Functional Improvements + +1. WebServer adds the `RouterCacheExpire` configuration parameter for setting the expiration time of route search cache; +2. WebServer allows the same `HOOK` event to be registered multiple times, with higher priority given to callbacks registered earlier ([https://goframe.org/net/ghttp/service/hook](https://wiki.goframe.org/net/ghttp/service/hook)); +3. When the current working directory is a system temporary directory, it does not add the directory to the search path by default in `gcfg`/`gview`/`ghttp` modules; +4. Improves the default support for cross-origin requests in `WebSocket` ([https://goframe.org/net/ghttp/websocket](https://wiki.goframe.org/net/ghttp/websocket)); +5. Enhances `gtime.Format` to support Chinese; +6. Enhances `gfsnotify` to handle hot update issues when editors perform non-standard edits (RENAME+CHMOD) to files; +7. Improves the `gtype.Set` method by adding an atomic operation to return the old variable value; +8. `gfile.ScanDir` adds support for `pattern` multiple file mode matching, using `,` to separate multiple match modes; +9. The `gcfg` module adds the capability to get configuration variables as `*gvar.Var`; +10. The `gstr` module adds a method for Chinese string truncation; +11. Improves `gtime.StrToTime` to match common time format patterns and adds the `gtime.ParseTimeFromContent` method; +12. Changes the environment variable names for configuration management, template engine, and debug modes to uppercase underscore standard format; +13. Improves the random number generation design in the `grand` module, implementing high-speed generation using `crypto/rand` + buffer ([https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index)); + +### Bug Fixes + +1. Fixes the search failure issue in the `gspath` module on `Windows`; +2. Fixes the retrieval issue with indexFiles during `Search` in the `gspath` module; +3. Bug fix INZS1 ([https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1)); +4. Fixes the execution issue of `gproc.ShellRun` on Windows. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" new file mode 100644 index 00000000000..f3e86e3e4f5 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" @@ -0,0 +1,43 @@ +--- +slug: '/release/v1.3.0' +title: 'v1.3 2018-12-26' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame,GoFrame Framework,v1.3,gform,WebServer,gcache,gredis,Travis CI,gview,Bug Fix] +description: "The GoFrame framework v1.3 release covers multiple new features, including the refactoring of gform, WebServer group routing, and rewrite routing feature, and improves the framework's automatic recognition in the development environment. In addition, it integrates Travis CI for automated building and testing, optimizes the WebServer static file service, enhances gcache performance, fixes several functional bugs, and improves overall stability and scalability." +--- + +### New Features + +1. Completed the refactoring of `gform` to improve scalability, and fixed some detail issues, and improved unit test cases ( [https://goframe.org/database/orm/index](https://wiki.goframe.org/database/orm/index)); +2. Added group routing feature for `WebServer` route registration ( [https://goframe.org/net/ghttp/group](https://wiki.goframe.org/net/ghttp/group)); +3. Added `Rewrite` routing feature to `WebServer` ( [https://goframe.org/net/ghttp/static](https://wiki.goframe.org/net/ghttp/static)); +4. Added automatic recognition of the development environment at runtime; +5. Added `Travis CI` for automated building/testing; + +### New Functions + +1. Improved `WebServer` static file service functionality, added `SetStaticPath`/`AddStaticPath` methods ( [https://goframe.org/net/ghttp/static](https://wiki.goframe.org/net/ghttp/static)); +2. Added `Filter` chaining method to `gform` for filtering non-table field key-value pairs in parameters ( [https://goframe.org/database/orm/linkop](https://wiki.goframe.org/database/orm/linkop)); +3. Added `Data` method to `gcache` for obtaining all cache data items; +4. Added `GetConn` method to `gredis` for obtaining native Redis connection objects; + +### Function Improvements + +1. Improved the `Where` method of `gform` to support `slice` type parameters and more conveniently support `in` operation queries ( [https://goframe.org/database/orm/linkop](https://wiki.goframe.org/database/orm/linkop)); +2. Improved `gproc` inter-process communication data structure, expanded `pid` field from `16bit` to `24bit`; +3. Improved `gconv`/`gmap`/`garray`, added several operation methods; +4. Improved the `date` built-in function in the `gview` template engine, printing the current system time when the given timestamp is empty; +5. Improved the `gview` template engine to display empty when the printed variable does not exist (default standard library shows ``); +6. Improved `WebServer` by removing `HANGUP` signal monitoring to avoid abnormal exit issues when running through `nohup`; +7. Enhanced `gcache` performance and improved benchmark tests; + +### Bug Fixes + +1. Fixed cache resource contention issue when `gcache` is closed with non-LRU features, and fixed return value issue of `doSetWithLockCheck` internal method; +2. Fixed random number bit overflow issue in `grand.intn` internal method on the `x86` architecture; +3. Fixed byte length overflow issue caused by automatic matching of `Int` method in `gbinary` targeting `[]byte` parameter length; +4. Fixed Go variable encoding issue in `gjson` due to official standard library `json` not supporting `map[interface{}]*` types; +5. Fixed data race issue in some methods of `garray`, and fixed binary search sorting issue; +6. Fixed parameter retrieval issue in `ghttp.Request.GetVar` method; +7. Resolved `gform` database connection pool not working problem; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" new file mode 100644 index 00000000000..874db7825f4 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" @@ -0,0 +1,39 @@ +--- +slug: '/release/v1.4.0' +title: 'v1.4 2019-01-24' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame, GoFrame framework, v1.4 update, gtimer timer, gcron refactor, gconv type conversion, gform automatic recognition, Travis CI support, ghttp flow control, gvalid phone number validation] +description: "This GoFrame framework v1.4 release brings several new features and improvements. A new high-performance task timer module gtimer has been added, along with a refactor and optimization of the gcron module. The gconv module now supports conversion of struct pointer properties, and gform has added automatic recognition of database types. Additionally, multiple modules have been improved for performance and concurrency safety, and some known issues have been fixed." +--- + +### New Features + +1. Added a concurrency-safe high-performance task timer module `gtimer`, similar to Java's `Timer`, but more powerful. It is implemented with a flexible and efficient hierarchical timing wheel design and can manage millions of scheduled tasks. `gtimer` is one of the core modules of the `GoFrame` framework, with unit test coverage of `93.6%`: [https://goframe.org/os/gtimer/index](https://wiki.goframe.org/os/gtimer/index) +2. Refactored the `gcron` scheduled task module using the task timer `gtimer`, removing reliance on the third-party package `github.com/robfig/cron`. `gcron` now supports singleton pattern scheduled tasks: [https://goframe.org/os/gcron/index#](https://wiki.goframe.org/os/gcron/index); +3. The `gconv` type conversion module now supports conversion of **pointer properties** within `struct` structures: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct); +4. `gform` has added a feature to automatically recognize database types, which is very useful when the query results need to be returned in `json` format: [https://goframe.org/database/orm/index](https://wiki.goframe.org/database/orm/index) +5. `Travis CI` has added support for automated testing on the `386` architecture (currently supports both `386` and `amd64`); + +### New Functions + +1. The `ghttp` module adds `Exit`, `ExitAll`, and `ExitHook` methods for HTTP request flow control: [https://goframe.org/net/ghttp/service/object](https://wiki.goframe.org/net/ghttp/service/object); +2. The `grand` module adds `Meet/MeetProb` methods for random probability-based judgments and adds alias methods `N/Str/Digits/Letters`; +3. The `gvalid` data/form validation module adds support for validating `16X` and `19X` phone numbers; + +### Function Improvements + +1. `gform` sets the default database connection pool `CONN_MAX_LIFE` parameter value to `30` seconds; +2. Improved the `glist` module, boosting performance by about `20%` and adding several linked list operation methods; +3. Enhanced the `gqueue` module, increasing performance by about `50%` and adding support for `select` syntax with the use of `Queue.C`: [https://goframe.org/container/gqueue/index](https://wiki.goframe.org/container/gqueue/index); +4. Improved the `gmlock` memory lock module and completed unit test cases: [https://goframe.org/os/gmlock/index](https://wiki.goframe.org/os/gmlock/index); +5. Improved all concurrency-safe container modules, changing the non-required parameter `safe...bool` to `unsafe...bool` for concurrency safety control; +6. Enhanced the `gpool` object reuse module to support concurrency safety; +7. Updated third-party dependencies for the `gkafka` module; +8. Completed unit test cases for the `ghttp` module; + +### Bug Fix + +1. Fixed the issue where the file pointer was not closed when operating on files in the `gmd5` module; +2. Fixed the issue where expired cache items were not deleted in the `gcache` module; +3. Other fixes; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" new file mode 100644 index 00000000000..6016286e96e --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" @@ -0,0 +1,42 @@ +--- +slug: '/release/v1.5.0' +title: 'v1.5 2019-02-28' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame, GoFrame Framework, v1.5 Update, Module Improvements, Container Modules, New Features, Bug Fixes, ghttp, gvalid, greuseport] +description: "GoFrame framework v1.5 version released, main repository migrated to GitHub with multiple module improvements including garray, gset, gmap, gstr container modules, and parameter support for gform. Added greuseport module and improved multiple features in ghttp. Also, fixed known issues like gvalid's validation rules and gcron's timer issues. For more details, please visit the GoFrame official website." +--- + +### New Features + +1. The main repository has migrated from `gitee` to `github` ( [https://github.com/gogf/gf](https://github.com/gogf/gf) ), with `gitee` serving as a mirror site for domestic code contributions and ISSUE submissions. Migration details are available at: [https://goframe.org/upgradeto150](https://wiki.goframe.org/upgradeto150) +2. Significant improvements/enhancements have been made to the commonly used `container` array module: `garray`, including the addition of many commonly used methods, improved unit test cases, and method annotations. See API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) +3. Significant improvements/enhancements have been made to the commonly used `container` set module: `gset`, including the addition of many commonly used methods, improved unit test cases, and method annotations. See API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) +4. Significant improvements/enhancements have been made to the commonly used `container` MAP module: `gmap`, including the addition of many commonly used methods, improved unit test cases, and method annotations. See API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) +5. Significant improvements/enhancements have been made to the commonly used string module: `gstr`, including the addition of many commonly used methods, improved unit test cases, and method annotations. See API documentation: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +6. Improved support for `struct`/ `*struct` parameters in `gform`, adjusting parameters of `*Insert/*Save/*Replace/*Update/Where/Data` methods to `interface{}` type, supporting parameter passing of any type: `string/map/slice/struct/*struct`. Refer to: [https://goframe.org/database/orm/chaining](https://wiki.goframe.org/database/orm/chaining) +7. Added/improved test cases for several modules, including: `gvalid`/ `gregex`/ `garray`/ `gset`/ `gmap`/ `gstr`/ `gconv`/ `ghttp`/ `gdb`; +8. As the `gkafka` module is relatively heavy and not a core module of the framework, it has been moved to a new repository for independent management, and the related dependency package has been removed: [https://github.com/gogf/gkafka](https://github.com/gogf/gkafka) +9. Added `greuseport` module to implement TCP `REUSEPORT` feature: [https://pkg.go.dev/github.com/gogf/gf/v2/net/greuseport](https://pkg.go.dev/github.com/gogf/gf/v2/net/greuseport) + +### New Features/Improvements + +1. Removed memory overhead caused by automatic initialization of `session` object in template engine's built-in variables; +2. Improved `ghttp.Client`, adding several methods. See: [https://goframe.org/net/ghttp/client](https://wiki.goframe.org/net/ghttp/client) +3. Added `COMMON` method to `ghttp` group routing for registering commonly used `HTTP METHOD` (`GET/PUT/POST/DELETE`) routes; +4. Updated framework dependencies for the `golang.org/x/sys` module; +5. Improved `gform` batch operation return result object, allowing accurate retrieval of affected record rows through the result object; +6. Moved `gstr`/ `gregex` modules from `util` category to `text` category directory; +7. Moved `gtest` module from `util` category to `test` category directory; +8. Improved `glog` method annotations; + +### Bug Fixes + +1. Fixed issue where emails with dots were not successfully matched using `gvalid.Check` with the " `email`" rule; +2. Fixed failure issue with `gvalid.Check` under `regex` rule; +3. Fixed issue in the `gcron` module timing rules where days and weeks did not allow `?` symbol; +4. Fixed issue where `ghttp.Server` returned `200` status code in some exceptional cases; +5. Fixed "memory leak" issue caused by atomic operations in the `gfpool` module under high concurrency; +6. Fixed issue where the method `Index` routing could only be accessed through `/xxx/index` when registering group route/controls; +7. Fixed error issue when using the template engine without a `config.toml` (even if not used) configuration file; +8. Other fixes; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" new file mode 100644 index 00000000000..7e16e2c1bf1 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" @@ -0,0 +1,54 @@ +--- +slug: '/release/v1.6.0' +title: 'v1.6 2019-04-09' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame framework,gcron,gredis,gcfg,gview,ghttp,gdb,gconv,gvalid] +description: "Version 1.6 brings several features and improvements, including the operation log record for the gcron scheduled task module, global group configuration for the gredis module, optimization of default configuration file retrieval paths for the gcfg and gview modules, CORS support for the ghttp module, TLSConfig configuration, and the new chaining operation methods for the gdb module. Several issues regarding resource competition and conversion failures in various modules have been fixed, significantly enhancing the system's stability and security." +--- + +### New Features/Improvements + +1. `gcron` scheduled task module adds operation log recording feature: [https://goframe.org/os/gcron/index](https://wiki.goframe.org/os/gcron/index) +2. `gredis` adds global group configuration features along with additional configuration options `maxIdle/maxActive/idleTimeout/maxConnLifetime`: [https://goframe.org/database/gredis/index](https://wiki.goframe.org/database/gredis/index) +3. `gcfg` module adds more default configuration file retrieval paths, introduces global group configuration features, and provides the `Instance` singleton method: [https://goframe.org/os/gcfg/index](https://wiki.goframe.org/os/gcfg/index) +4. `gview` module adds more default configuration file retrieval paths and provides the `Instance` singleton method: [https://goframe.org/os/gview/index](https://wiki.goframe.org/os/gview/index) +5. `ghttp` module new features and improvements: + - Adds `CORS` HTTP(S) cross-origin request features: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - Adds `TLSConfig` configuration feature; + - Removes the `error` return value from routing registration methods, and prints errors directly to the terminal/log file when registration errors occur; + - Adds `Set-Cookie` support during `HTTP Code 302` redirections; + - Adds security checks for `SESSION ID`; + - Adds support for `HTTPS` based `WebSocket` (`WSS`): [https://goframe.org/net/ghttp/websocket/index](https://wiki.goframe.org/net/ghttp/websocket/index) + - `Request` object adds `Error` method for logging custom error messages to the `WebServer` error log; + - Other improvements; +6. `gdb` module new features and improvements: + - Adds `Instance` singleton management method; + - Adds `Structs/Scan` chaining operation methods, `gdb.DB/TX` adds `GetStructs/GetScan` methods for result set `struct/slice` mapping conversion: [https://goframe.org/database/gdb/chaining](https://wiki.goframe.org/database/gdb/chaining) + - Adds `Safe` chaining operation method (not concurrent-safe by default) for chaining safety control: [https://goframe.org/database/gdb/chaining](https://wiki.goframe.org/database/gdb/chaining) + - Improvements to `Where` chaining operation method: + - Supports any `string/map/slice/struct/*struct` types; + - Adjusted logic to automatically convert multiple `Where` method calls in chaining operations to `And` conditions; + - Supports `slice` condition parameters, commonly used in `SELECT IN` queries, e.g., `Where("uid IN(?)", g.Slice{1,2,3})`; + - Supports embedding conditions in `key` of `map` type condition parameters, e.g., `Where(g.Map{"uid>?", uid})`; +7. `gconv` and `gvalid` modules improve and remove conversion/validation for private `struct` method attributes; +8. `gconv.Map` conversion method adds support for `json tag`: `-`, `omitempty`: [https://goframe.org/util/gconv/map](https://wiki.goframe.org/util/gconv/map) +9. `gstr` module adds case-insensitive replace methods `ReplaceI/ReplaceIByArray/ReplaceIByMap`; +10. `gutil` module adds `IsEmpty` method to determine if the given variable is empty (0 for integer, false for boolean, 0 length for slice/map, nil for others), along with a shortcut method `g.IsEmpty`; +11. `gutil` module adds `Export` method to export and return formatted output of variable contents as a string, along with a shortcut method `g.Export`; +12. `gspath` adds cache and non-cache retrieval methods `Search`/`SearchWithCache`; +13. `gjson` module adds default `UseNumber` feature support; +14. `gmap` adds `SetIfNotExistFunc/SetIfNotExistFuncLock` methods; +15. Migrates `greuseport` module to a new repository: [https://github.com/gogf/greuseport](https://github.com/gogf/greuseport) +16. Numerous unit test improvements; + +### Bug Fix + +1. Fixed resource competition issue in `gqueue` module; +2. Fixed conversion failure issue in `gconv.GTime`; +3. Fixed byte overflow issue in `gconv.String` when converting `int` parameters; +4. Fixed `HTTP Basic Auth` verification issue in `ghttp.Request`; +5. Fixed concurrency safety issue in `gxml` for non-`UTF-8` encoded content conversion; +6. Fixed formatting issues in `gtime` for some `Format` (`G` & `j`) specifications; +7. Fixed method issue in `gudp.Conn` for obtaining client connection address `RemoteAddr`; +8. Fixed `GetOrSetFuncLock` method in `gmap/gcache` module by adding `nil` return value check for callbacks, saving only non-nil return values. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" new file mode 100644 index 00000000000..09376fe82a6 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" @@ -0,0 +1,69 @@ +--- +slug: '/release/v1.7.0' +title: 'v1.7 2019-06-10' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame framework,glog,gmap,gtime,gdb,gtcp,gconv,ghttp,gtree] +description: "The GoFrame framework v1.7 version was released on June 10, 2019. It includes multiple module reconstructions and improvements, such as enhancing the performance of the glog logging module with asynchronous output, gmap supports various data structures, gtime adds more PHP time formats, gdb adds the GetLastSql method, gtcp supports TLS communication, gconv adds recursive conversion functionality, and several bugs are fixed for improved framework stability and performance." +--- + +### New Features/Improvements + +1. Refactored and improved `glog` module: + - Removed all locks in the log module, redesigned as lock-free for improved execution performance + - Added asynchronous output feature for log content: [https://goframe.org/os/glog/async](https://wiki.goframe.org/os/glog/async) + - Added support for `Json` format in log output content: [https://goframe.org/os/glog/json](https://wiki.goframe.org/os/glog/json) + - Added `Flags` feature support including file line number printing, custom time format, and asynchronous output control: [https://goframe.org/os/glog/flags](https://wiki.goframe.org/os/glog/flags) + - Added `Writer` interface support for developers to customize log functionality extensions or integrate with third-party services/modules: [https://goframe.org/os/glog/writer](https://wiki.goframe.org/os/glog/writer) + - Changed method name `SetStdPrint` to `SetStdoutPrint` + - Changed chain method name `StdPrint` to `Stdout` + - Deprecated `*fln` log output methods, `*f` methods now support automatic line breaks + - Added more chain method supports: [https://goframe.org/os/glog/chain](https://wiki.goframe.org/os/glog/chain) +2. Refactored and improved `gmap` module: + - Added support for more data formats: `HashMap`/`ListMap`/`TreeMap` + - Simplified type names, e.g., `gmap.StringInterfaceMap` simplified to `gmap.StrAnyMap` + - Improved `Map/Keys/Values` methods for better performance + - Renamed `BatchSet`/`BatchRemove` to `Sets`/`Removes` + - Added more functional method supports: [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) +3. Improved `gtime` time module: + - Added and improved more class `PHP` time format support + - Added more functional methods, such as `FormatTo`/`LayoutTo`, etc. + - See development documentation: [https://goframe.org/os/gtime/index](https://wiki.goframe.org/os/gtime/index) +4. Improved `gdb` database module: + - Added support for data conversion for inherited structures: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - Added `GetLastSql` method to get the most recent executed SQL statement in debug mode + - Other detailed improvements +5. Improved `gtcp` communication module: + - Enhanced detail handling to improve communication performance; + - Added `TLS` server/client communication support: [https://goframe.org/net/gtcp/tls](https://wiki.goframe.org/net/gtcp/tls) + - Added simple protocol support for developers to encapsulate/decapsulate, solving packet sticking/fragmentation issues: [https://goframe.org/net/gtcp/conn/pkg](https://wiki.goframe.org/net/gtcp/conn/pkg) + - Added `Close` method for TCP server + - For more details, see the development documentation: [https://goframe.org/net/gtcp/index](https://wiki.goframe.org/net/gtcp/index) +6. Improved `gconv` type conversion module + - Renamed `gconv.TimeDuration` conversion method to `gconv.Duration` + - Added `gconv.StructDeep` and `gconv.MapDeep` methods, supporting recursive conversion + - See development documentation: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct) +7. Improved `ghttp` module: + - Added `http/https` field to the log output: [https://goframe.org/net/ghttp/logs](https://wiki.goframe.org/net/ghttp/logs) + - Added `ghttp.Server.SetKeepAlive` setting method to enable/disable `KeepAlive` feature + - Added `ghttp.Request.GetUrl` method to get the current full URL request address + - `ghttp.Client` client supports the developer's custom `Transport` attribute, `ghttp.Client.Post` method supports `browser mode`: [https://goframe.org/net/ghttp/client](https://wiki.goframe.org/net/ghttp/client) +8. Added `gtree` tree data structure container support: [https://goframe.org/container/gtree/index](https://wiki.goframe.org/container/gtree/index) +9. Improved `gudp` communication module, please refer to the development documentation for details: [https://goframe.org/net/gudp/index](https://wiki.goframe.org/net/gudp/index) +10. Improved `gcfg` configuration management module, all `Get*` methods add default value support: [https://goframe.org/os/gcfg/index](https://wiki.goframe.org/os/gcfg/index) +11. `gredis` module added `DoVar`/`ReceiveVar` methods to facilitate developer's flexible data format conversion of execution results: [https://goframe.org/database/gredis/index](https://wiki.goframe.org/database/gredis/index) +12. `gcache` module `BatchSet`/`BatchRemove` method names changed to `Sets`/`Removes` +13. Improved `gjson`/`gparser` modules, adding more methods: [https://goframe.org/encoding/gjson/index](https://wiki.goframe.org/encoding/gjson/index) +14. Improved `gfile.MainPkgPath` method to support different platform development environments; +15. Improved `grpool` coroutine pool module for enhanced execution performance: [https://goframe.org/os/grpool/index](https://wiki.goframe.org/os/grpool/index) +16. Improved `TryCatch` method, by default suppressing and ignoring errors when `Catch` parameter is not provided +17. Improved `gmlock` module, added `TryLockFunc`/`TryRLockFunc` methods, and added `TryLockFunc`/`TryRLockFunc` methods for `gmlock.Mutex` advanced mutex lock object +18. Removed `gvar.VarRead` interface type support + +### Bug Fixes + +1. Resolved conflicts when using `gdb` module with other third-party `ORM` modules concurrently; +2. Fixed detailed logic issues in the `gcron.AddOnce` method; +3. Fixed incorrect empty property validation in the internal `empty` module `IsEmpty` method; +4. Fixed concurrency safety issue in `gview` template engine; +5. Fixed SESSION initialization expiration time issue in `ghttp.Server`; \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" new file mode 100644 index 00000000000..e61013e8f15 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" @@ -0,0 +1,78 @@ +--- +slug: '/release/v1.8.0' +title: 'v1.8 2019-07-15' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,gerror,gcharset,gmutex,glog,gdb,gconv,ghttp,gvalid,gtcp,gproc] +description: "新版GoFrame框架发布,带来多项改进与新功能。包括新增错误处理模块、字符编码转换支持、日志模块异步输出与自定义格式、数据库ORM错误处理与链式操作,以及数据转换与通信模块的增强。此更新还解决了多个模块的已知问题,提升框架整体性能与稳定性。" +--- + +### New Features and Improvements + +1. The framework currently has `69` development modules (excluding internal modules), with native code of `65302` lines (excluding third-party dependency packages), and unit test coverage reaching `77%`; +2. Added `gerror` error handling module: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) +3. Improved `gcharset` character encoding conversion module, supporting more character sets: [https://goframe.org/encoding/gcharset/index](https://wiki.goframe.org/encoding/gcharset/index) +4. Added `gmutex` module, an advanced mutex module based on `channel`, supporting more rich mutex features: [https://goframe.org/os/gmutex/index](https://wiki.goframe.org/os/gmutex/index) +5. Improved `glog` logging module: + - Added asynchronous logging output feature: [https://goframe.org/os/glog/async](https://wiki.goframe.org/os/glog/async) + - Added `Flags` extra feature: [https://goframe.org/os/glog/flags](https://wiki.goframe.org/os/glog/flags) + - Added `Json` data format output: [https://goframe.org/os/glog/json](https://wiki.goframe.org/os/glog/json) + - Added custom `Writer` interface feature: [https://goframe.org/os/glog/writer](https://wiki.goframe.org/os/glog/writer) + - **Renamed `Backtrace` to `Stack` and improved call stack output format;** + - Added `Expose` method to expose the internal default `Logger` object; +6. Improved `gdb` database ORM module: + - **Improved error handling, returning `sql.ErrNoRows` when no data is found in a database operation**: [https://goframe.org/database/gdb/error](https://wiki.goframe.org/database/gdb/error) + - Improved `Update`/`Delete` methods to support `Order BY` and `LIMIT` features; + - In database chain operations, the parameter supports `slice` parameters: [https://goframe.org/database/gdb/model/model](https://wiki.goframe.org/database/gdb/model/model) + - **Renamed `Priority` weight configuration to `Weight`;** + - Added `Debug` configuration to enable/disable debugging feature: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - Added `Offset` method, an optional chain operation method, with `pgsql` database directly recognizing the second parameter of `Limit` method as `Offset` syntax; + - Improved database dynamic switching feature to switch between different database types; + - Simplified configuration file structure: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) +7. Improved `gconv` data conversion module: + - Supported more tags during struct object conversion: `gconv/c/json`; + - Supported `*struct/[]struct/[]*struct` automatic initialization and creation: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct) + - Added `Strusts/StrctsDeep` methods for recursive conversion of struct arrays; + - Added `StructDeep` method for recursive conversion of struct objects; + - Added `MapDeep` method for recursive conversion of struct properties; +8. Improved `ghttp` module: + - Improved the group routing function of `ghttp` module with more robust logic handling; + - Improved `ghttp.Request.Get*ToStruct` methods to support `params/param/p` tags and recursive struct conversion, with automatic initialization for `**struct` parameters; + - Adjusted `ghttp.CORSDefault` cross-origin settings, with `AllowOrigin` set to `*`; +9. Improved `gvalid` data validation module: + - Added support for validation tags `gvalid/valid/v`; + - Improved `CheckStruct` to support recursive validation of struct objects: [https://goframe.org/util/gvalid/checkstruct](https://wiki.goframe.org/util/gvalid/checkstruct) +10. Improved `gtcp` TCP communication module: + - Improved communication packet protocol design for more lightweight and efficient packages: [https://goframe.org/net/gtcp/conn/pkg](https://wiki.goframe.org/net/gtcp/conn/pkg) + - Added `TLS` support to `TCP Server`: [https://goframe.org/net/gtcp/tls](https://wiki.goframe.org/net/gtcp/tls) + - Added `Server.Cloce` server closing method; +11. Improved `gproc` module communication data structure, using `gtcp` lightweight packet protocol to optimize message sending logic; +12. Improved `gqueue` module with data synchronization buffer mechanism to address memory usage and latency issues under large data volumes; +13. Improved `gmlock` module, replacing internal mutexes using `gmutex`, and added more operation methods; +14. Improved `gaes` encryption module by adding `CBC` encryption/decryption methods; +15. Improved `garray.Range/SubSlice` methods for better design and performance; +16. Improved `gjson`/`gparser` modules to implement `MarshalJSON` interface for custom `JSON` data format conversion; +17. **Added `error` variables to return method results in `crypto` classification to ensure more rigorous interface design style;** +18. **Changed input/output types in `gbase64` module, adding several related methods;** +19. Changed `UnLock` to `Unlock` in `gflock`, and added `IsRLocked` method; +20. Added `gfile.CopyFile/CopyDir` methods for file and directory copying; +21. Added more type conversion methods to `gjson/gparser/gvar/gcfg` modules; +22. Improved `gcache` module, with expiration time parameter supporting `time.Duration` type; +23. Added `internal/structs`, a powerful and convenient struct parser, optimizing all modules involving struct reflection processing; +24. Improved `gbinary`, adding support for `BigEndian` with encapsulated methods; + +### Bug Fixes + +1. Fixed `garray.Search` return value issue; +2. Fixed logic issues in `garray.Contains`, `garray.New*ArrayFromCopy` methods; +3. Fixed `gjson.Remove` slice parameter issue; +4. Fixed `gtree.AVLTree.Remove` method return value issue; +5. Fixed inaccurate size issue in `gqueue.Size`; +6. Fixed `queue.Close` issue; +7. Fixed deadlock issue in `gcache.GetOrSetLockFunc` when callback function returns `nil` result; +8. Fixed default recursive monitoring addition issue in `gfsnotify.Add` method; +9. Fixed issues with `gdb.Model.Scan` for certain parameter types; + +### Notes + +Please pay attention to the bold parts above, as you may encounter incompatibilities when upgrading. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" new file mode 100644 index 00000000000..143e54543cc --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" @@ -0,0 +1,95 @@ +--- +slug: '/release/v1.9.0' +title: 'v1.9 2019-09-24' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame Framework,gf Command Line,gres Resource Manager,gsession Module,gi18n Localization,gini Module,gcmd Parsing,gdebug Module,WebServer Middleware] +description: "GoFrame framework released v1.9 version, including important new features and improvements. Added gf command line tool, gres resource manager, and gsession module, and enabled a more convenient WebServer routing registration method. Improved ghttp and gdb modules, adjusted container classification modules, and fixed some known issues." +--- + +This version is actually the major release of `v2.0`. To avoid the strict requirement of `go module` mechanism for `v2` and above versions needing to modify `import` and add `v2` suffix, it was released using `v1.9`. + +### New Features + +1. Added `gf` command line development assistant tool: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) +2. Added `gres` resource manager module: [https://goframe.org/os/gres/index](https://wiki.goframe.org/os/gres/index) +3. Refactored `Session` functionality, added `gsession` module, and `WebServer` defaults to file storage `Session`: [https://goframe.org/net/ghttp/session](https://wiki.goframe.org/net/ghttp/session) +4. `WebServer` added middleware feature, retaining the original HOOK design, both can achieve request interception, pre-processing, etc.: [https://goframe.org/net/ghttp/router/middleware](https://wiki.goframe.org/net/ghttp/router/middleware) +5. Added `gi18n` localization management module: [https://goframe.org/i18n/gi18n/index](https://wiki.goframe.org/i18n/gi18n/index) +6. Added `gini` module: [https://goframe.org/encoding/gini/index](https://wiki.goframe.org/encoding/gini/index) +7. `WebServer` added more convenient hierarchical routing registration method: [https://goframe.org/net/ghttp/group/level](https://wiki.goframe.org/net/ghttp/group/level) +8. `gcmd` command line parameter parsing module refactored, added `Parser` parsing object: [https://goframe.org/os/gcmd/index](https://wiki.goframe.org/os/gcmd/index) +9. Added `gdebug` module for stack information retrieval/printing: [https://goframe.org/debug/gdebug/index](https://wiki.goframe.org/debug/gdebug/index) + +### Major Adjustments + +1. Removed methods marked as `deprecated` in `1.x` versions; +2. Adjusted container classification modules to default to non-concurrent safety; +3. Directory adjustments: + - Removed `third` directory, unified management of package dependencies with `go module`; + - Moved original modules in `g` directory to framework main directory, original `g` module moved to `frame/g` directory; + - Changed original `geg` sample code directory name to `.example`; + +### Functional Improvements + +1. `ghttp` + - Improved `Request` parameter parsing method: [https://goframe.org/net/ghttp/request](https://wiki.goframe.org/net/ghttp/request) + - Improved cross-domain request functionality, added `Origin` setting and verification feature: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - Changed `TTL` configuration data type of `Cookie` and `Session` to `time.Duration`; + - Added simultaneous transmission of `SessionId` through `Header/Cookie`; + - Added `ConfigFromMap/SetConfigWithMap` methods to configure WebServer with `map` parameter; + - Improved default `CORS` configuration with default support for common `Header` parameters; + - Added `IsExitError` method for developers to customize `recover` error handling, filtering non-exception errors defined by the framework; + - Added `SetSessionStorage` configuration method for custom `Session` storage; + - `ghttp.Request` added more parameter retrieval methods; +2. `gdb` + - Added automatic escape (`Quote`) feature for some SQL fields; + - Added support for `slice` parameters in method operations and chain operations; + - Added `SetLogger` method for custom database log printing; + - Added `Master/Slave` method for selecting master-slave nodes for database operations; + - Added unit tests for `mssql/pgsql/oracle`; + - Supported full integration parameterized SQL statement debugging printing in `debug` mode; + - Added more functional methods; +3. `glog` + - Added `Default` method to get the default `Logger` object; + - Added `StackWithFilter` method for custom stack printing filtering; + - Added more functional methods; +4. `gfile` + - Renamed some methods: `Get/PutBinContents` changed to `Get/PutBytes`; + - Added `ScanDirFile` method for retrieving file directories only, supporting recursive search; + - Added more functional methods; +5. `gview` + - Added `SetI18n` method for setting custom `gi18n` localization objects for view objects; + - Added built-in support for `gres` resource manager; +6. `gcompress` + - Added file/directory compression/decompression methods for `zip` algorithm; + - Compression parameters support multiple paths for file/directory; +7. `gconv` + - Improved support for `[]byte` data type parameters; + - Added `Unsafe` conversion method for increased conversion efficiency in specific scenarios; + - Added `MapDeep/StructDeep/StructsDeep` methods for recursive `struct` conversion; +8. `gjson/gparser` + - Improved automatic type recognition functionality; + - Added `LoadJson/LoadXml/LoadToml/LoadYaml/LoadIni` methods for custom data type content loading; + - Added more functional methods; +9. `gerror` + - Improved error stack retrieval logic; + - Added more functional methods; +10. `gmap/garray/gset/glist/gvar` + - Improved concurrent safety benchmarking scripts; + - Changed `garray.StringArray` to `garray.StrArray`; + - Added more functional methods; +11. `gdes` + - Standardized method name changes; +12. `gstr` + - Added `Camel/Snake` naming conversion methods; + - Added more functional methods; +13. `genv` + - Added more functional methods; + +### Bug Fix + +1. Fixed issue with `gvalid` customization `tag` errors not working when validating `struct`; +2. Fixed automatic content type recognition failure in `gcfg` configuration management module under specific conditions; +3. Fixed concurrent safety issue in `gqueue` when the user actively closes the queue; +4. Fixed integer overflow issue in `session` when developer sets excessively large `TTL`. \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" new file mode 100644 index 00000000000..05b6b316e01 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" @@ -0,0 +1,12 @@ +--- +slug: '/release/v1.x' +title: 'Historical Version v1.x' +sidebar_position: 10000 +hide_title: true +keywords: [Historical Version, Version Records, GoFrame Framework, Software Update, Feature Improvement, Bug Fixes, v1.x, Version Release, Upgrade Notes, Technical Documentation] +description: "Learn about the historical changes of the GoFrame framework V1.x versions, covering feature improvements and bug fixes across multiple versions. Detailed records of each version's release content help developers quickly understand the changes and potential impacts brought by new versions." +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..e81520f8a05 --- /dev/null +++ "b/i18n/en/docusaurus-plugin-content-docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" @@ -0,0 +1,73 @@ +--- +slug: '/release/note' +title: 'Version Release Notes' +sidebar_position: -10000 +hide_title: true +keywords: [GoFrame,GoFrame Framework,Version Naming Rules,Version Upgrade,Compatibility Guarantee,GNU Style,Major Version,Minor Version,Revision Version,go modules] +description: "GoFrame framework's version release notes, including the GNU-style version naming rules, how to ensure compatibility between versions, and recommended upgrade methods. Additionally, it introduces the usage notes and operation methods of the master and develop branches to ensure developers can efficiently manage and upgrade the framework versions." +--- + +## Version Naming + +`GoFrame` adopts the `GNU` style version naming rule: + +`MajorVersion.MinorVersion.Revision` + +That is: + +`MajorVersion.MinorVersion.Revision` + +For example: + +`v0.0.1`, `v1.1.0`, `1.7.1` + +Version explanation: +- `MajorVersion`: The major version number of `GoFrame` starts from `0`, from `alpha` to `beta` to the current official version. The major version indicates a completely new framework version, such as major refactoring, major feature changes, or significant incompatible changes. +- `MinorVersion`: The minor version number is increased when there are large new feature releases, significant refactoring or module changes, or incompatibility changes. A minor version release comes with a complete change log and is considered a significant release. It usually occurs every few months. +- `Revision`: The revision number typically involves bug fixes, or adding minor features, minor improvements, or module changes. It increases the revision number while ensuring full backward compatibility. Revision releases are made irregularly and frequently. +- When the major version number increases, the minor and revision numbers are reset to `0`. +- When the minor version number increases, the revision number is reset to `0`. + +## Compatibility Guarantee + +`GoFrame` promises that every revision release ensures complete backward compatibility for all modules, allowing for easy upgrades. + +Due to the rapid development of the `GoFrame` framework, with constant additions of new features and improvements, minor version releases may not guarantee full backward compatibility for all modules. However, every minor version release will provide a complete change log to announce the release. If there are compatibility adjustments for some modules, they will be accompanied by relevant keynotes and often include upgrade operation guidance. + +## Version Upgrade Method + +For `Golang` project development, it is not recommended to use the `vendor` method directly, nor is it recommended to use the framework's `master` branch directly. It is recommended to use the `go modules` package management method, that is, to manage package versions using `go.mod`. + +Before planning any version upgrade, check the latest version number in the repository: [https://github.com/gogf/gf/releases](https://github.com/gogf/gf/releases). Choose a version number for upgrading, modify `go.mod`, save it, and the `Goland IDE` will automatically download the corresponding framework version. + +If upgrading to the latest framework version, you can also perform a complete upgrade by executing `gf up -a` in the project's root directory using the `cli` tool. + +## Using Specific Versions + +### `master` Branch + +The `master` branch is a public testing branch. All unreleased code is first merged into the `master` branch, and after a certain period of testing, it is tagged for formal release. If some issues are fixed and developers are eager to use the latest version, they can try the `master` branch. The update method is as follows: + +```bash +go get -u github.com/gogf/gf@master +``` + +You can also update to a specific `git reversion`: + +```bash +go get -u github.com/gogf/gf@4d3273379022a9518c1dc20ebada612cddeed764 +``` + +### `develop` Branch + +The `develop` branch is the development branch. All development feature branches are unified and merged into the `develop` branch for joint testing and debugging. Once confirmed, they are merged into the `master` branch. Note that the `develop` branch cannot be used in a production environment. Contributor PRs are also merged into the `develop` branch. The update method is as follows: + +```bash +go get -u github.com/gogf/gf@develop +``` + +You can also update to a specific `git reversion`: + +```bash +go get -u github.com/gogf/gf@0e58b6e95ba211fcde27954a68cbf4acadbb6bc9 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" new file mode 100644 index 00000000000..72ac9644a39 Binary files /dev/null and "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/img.png" differ diff --git "a/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" new file mode 100644 index 00000000000..2d588ce2fb2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\344\273\243\347\240\201\346\226\207\346\241\243\350\264\241\347\214\256.md" @@ -0,0 +1,55 @@ +--- +slug: '/supportus/pr' +title: '代码文档贡献' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,开源,代码贡献,文档贡献,GitHub,gitee,开源项目,PR流程,框架开发] +description: 'GoFrame 是一个开源、免费的框架,欢迎所有开发者为其代码和文档贡献力量。通过GitHub主库,您可以参与代码贡献,加入我们开发的史册。此外,您还能通过完善官网文档、撰写博客或录制视频的方式帮助推广GoFrame框架。' +--- + +`GoFrame` 是开源的、免费的软件,这意味着任何人都可以为其开发和进步贡献力量。 `GoFrame` 的项目源代码目前同时托管在 `github`(主库)和 `gitee`(国内)平台上,两个平台的仓库保持即时的同步,代码贡献统一使用 `github` 主库。我们非常欢迎有更多的朋友加入到 `GoFrame` 框架的开发中来,您为 `GoFrame` 所做出的任何贡献都将会被记录到 `GoFrame` 的史册中。 + +贡献内容大概可以分为两类: **代码贡献** 和 **文档贡献**。 + +## 一、代码贡献 + +### 1、哪里找到任务 + +在主库 `issue` 中有很多打上 `help wanted` 标签的 `issue`,这些都期望得到社区小伙伴们的贡献: [地址戳这里](https://github.com/gogf/gf/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22) + +![](/markdown/3138f105d604376eec1a9ec583359ec3.png) + +### 2、代码贡献流程 + +1. 首先 `fork` 一份仓库代码到自己的版本库中; +2. 在自己的版本库中新建开发分支并对代码做修改,随后提交修改到自己的版本库; +3. 在自己的版本库中创建一个 `pull request`,源分支选择自己的开发分支,目标分支选择主库的 `master` 分支: [https://help.github.com/en/articles/creating-a-pull-request](https://help.github.com/en/articles/creating-a-pull-request) +4. 提交 `pull request` 请求,随后等待由项目的开发作者对提交内容做 `code review`。如果 `pull request` 长时间没有被 `code review`,可以主动跟进、找到团队成员提出 `code review` 要求。审核通过之后你将成为 `GoFrame` 框架的成员之一。 +5. 恭喜你,你的名字将永久地载入到 `GoFrame` 框架源代码的贡献列表中; + +### 3、代码协作约定 + +1. 所有源码文件、类型、方法都要有详尽的注释; +2. 如果逻辑复杂的程序部分需要阐述实现思路; +3. 所有的注释都使用英文阐述,不再使用中文; +4. 新增的功能/模块必须要有单元测试,并且覆盖率达到 `80%` 以上; + +## 二、文档贡献 + +### 1、官网完善 + +`GoFrame` 框架的文档主要集中在 `gf-site` 仓库中,地址: [https://github.com/gogf/gf-site](https://github.com/gogf/gf-site) + +大家在阅读文档的过程中,如果不太完善的地方,可以点击编辑页面帮忙完善。特别是组件、功能的使用示例,往往都可以补充上去。 + +### 2、博客及视频教程 + +社区的小伙伴可以编写文章或者录制视频分享,方便社区其他小伙伴通过搜索引擎查阅到你的分享,便于相互学习,以及开源项目推广。 + +在文章中请保持统一的框架名称关键字: `GoFrame`,请勿采用缩写名称。 + +## 三、其他帮助 + +任何帮助可联系微信 `389961817`。我将为你提供必要的指导和帮助。 + +![img.png](img.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" new file mode 100644 index 00000000000..6e4c796c1f4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\345\212\240\345\205\245\347\244\276\345\214\272\345\233\242\351\230\237.md" @@ -0,0 +1,75 @@ +--- +slug: '/supportus/team' +title: '加入社区团队' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,开源项目,社区团队,技术分享,项目开发,开源参与,职场竞争力,技术成长,技术交流] +description: '随着GoFrame框架的不断壮大,我们致力于进一步改进,加强版发布吸引更多关注。加入社区团队能解决工作实际问题,掌握最新发展动态,参与内部设计和技术讨论,享受不定期分享带来的成就感和技术提升。此外,还能通过与志同道合的技术大佬开拓交际,增强简历竞争力。积极参与项目开发和推广,共同处理社区问题,分享经验。严格条件确保成员质量,欢迎成熟企业贡献力量。' +--- + +## 一、为什么要向社区招募呢? + +- 随着`GoFrame`开源项目的更加强大和完善,我们想更进一步做得更好、更完善、更强大!我们需要更多的力量! +- 随着全新`v2`版本的发布,`GoFrame`迈上了一个全新台阶,我们想要更多人知道我们不懈努力所创造的价值! +- 推动`GoFrame`发展成为一个依靠社区驱动的项目! + +## 二、成为社区团队成员能获得什么呢? +### 1、能解决自己工作当中的实际问题 + +使用`GoFrame`并成为其团队一员,您将能比别人接触其更多的原理和细节,能够帮助您解决棘手的项目实际问题。 +### 2、掌握开源项目的最新发展动态 + +我们未来将会把项目的第一手资料发放到社区团队中,由社区团队作为`GoFrame`对外推广宣传布道的中坚力量。 +### 3、参与项目内部设计、技术讨论 + +我们期望项目的成果中有更多思想和经验的融合,我们共同制定项目未来发展的方向。 +### 4、不定期参与内部技术分享 + +我们期望团队的每位成员都能不仅对内、也能对外分享自己的学习心得、研究成果。 +### 5、成就感、认同感 + +多年的倾注以及社区的认可,让我们认为这样的坚持是一件有价值的事情。 +### 6、技术成长与提升 + +增强自己对框架设计和底层原理的理解。您也会接触到各种各样的业务需求场景痛点、行业中领先得或切实可行的解决方案。 +### 7、简历上的润色 + +增加自己在职场中的竞争力。技术领袖欣赏开源参与者,但更多关注您在社区中担任的角色、付出的努力与沉淀的结果。 +### 8、开拓技术交际 + +认识更多志趣相投的大佬。 + +## 三、我们之间也有君子约定哦! +### 1、积极参与项目开发 + +任何的`feature、enhancement、bug fix`都是非常欢迎的,在`issue`中有来自于社区源源不断的需求,希望您能积极参与贡献。 +### 2、积极推广宣传布道本项目 + +无论是视频、文章还是交流讨论,作为社区的中坚力量,我们期望您能在各平台积极推广宣传`GoFrame`社区的优秀内容、设计和思想。 +### 3、积极参与项目`issue`处理、社区问题解答 + +积极参与解决大家的疑问,建立和维护良好的`GoFrame`社区形象。同时,您不是一个人在战斗,当您遇到困难或者棘手的`issue`时,可以与团队小伙伴一起交流、共同解决难题。 +### 4、积极参与团队内部交流、技术分享 + +无论是在社区、职场还是在生活中,我们都期望您是一位积极主动的人、乐于分享的人。我们也能在您的分享中与您积极交流,共同学习提高。 + +## 四、成为社区团队成员的条件 + +为了保障团队和项目质量,我们谨慎地设置了一些基本的加入条件。 + +- 首先,已经在实际项目中使用过`GoFrame`,并且想要进一步加深了解 +- 其次,感兴趣在业余的时间和社区小伙伴一起参与`GoFrame`社区建设 +- 最后,能认识到开源是一件有价值的事情,但也如生活那般充满了苦辣酸甜 + +:::tip +此外,来源社区、反哺社区,我们也非常欢迎来自于企业的贡献,一起共建社区,形成企业与社区的良性合作循环。欢迎感兴趣的企业积极与我们联系,我们将为您预留席位。 +::: + +## 五、如何成为社区团队成员呢 + +如果有感兴趣并且满足条件的社区小伙伴,请微信联系项目发起人,并发送关于自己的一句话介绍。 + +如果您是开源大佬,请直接发送自己的`github`页面链接即可。 + + +![img.png](img.png) diff --git "a/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" new file mode 100644 index 00000000000..a6e2df749e4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\224\257\346\214\201\346\210\221\344\273\254.md" @@ -0,0 +1,11 @@ +--- +slug: '/supportus' +title: '支持我们' +sidebar_position: 99 +hide_title: true +--- + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\235\245\346\235\257\345\222\226\345\225\241.md" "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\235\245\346\235\257\345\222\226\345\225\241.md" new file mode 100644 index 00000000000..6db580385c4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\346\224\257\346\214\201\346\210\221\344\273\254/\346\235\245\346\235\257\345\222\226\345\225\241.md" @@ -0,0 +1,407 @@ +--- +slug: '/supportus/donate' +title: 'Warm Coffee☕️' +sidebar_position: 0 +hide_title: true +--- + +We currently accept donation by `Wechat`/ `Alipay`/ `Gitee`, please note your `github`/ `gitee` account in your payment bill. + +* * * + +我们当前接受来自于 **微信**、 **支付宝** 或者 **[码云](https://gitee.com/johng/gf)** 的捐赠,请在捐赠时备注自己的 `github` 或者 `gitee` 账号名称。 + +## Alipay + + + +## Wechat + + + +## Donates + +Latest updated at/最后更新日志:2024-12-04 21:40:00 + +| Name | Channel | Amount | Time | Comment | +| --- | --- | --- | --- | --- | +| 上海金保证网络科技 | `bank` | `¥2000.00` | | | +| 蔡蔡 | `wechat` | `¥1332.00` | | `gf` 越来越强。 `gf` 真强大,让项目省心 | +| [Han Wang](https://gitee.com/hwang7799) | `gitee` | `¥1000.00` | | 感谢您的开源项目!感谢您详细的说明文档!祝GoFrame越来越好 | +| \*\*源(che\*\*\*@qq.com) | `alipay` | `¥888.88` | | | +| soidea666 | `wechat+qq` | `¥800.00` | | | +| 孙庆鹏 | `wechat` | `¥666.00` | `2024-12-05 09:24:24` | gf加油,兄弟加油 | +| [闫沧](https://github.com/ireadx) | `alipay` | `¥589.00` | - | 写 `golang` 以来 `gf` 一直都在 | +| 大海 | `wechat` | `¥588.00` | | 精品认可 | +| Tom | `wechat` | `¥500.00` | | 一点心意 希望 `GF` 越来越好 | +| [沙果云科技](https://iotdoc.sagoo.cn/) | `wechat` | `¥500.00` | | | +| [fly的狐狸](https://github.com/zcool321) | `wechat/alipay` | `¥350.00` | | | +| \*\*欢 | `alipay` | `¥308.00` | | 很好用!愿 `gf` 像 `spring` 框架一样强大 | +| 未来为我而来 | `wechat` | `¥300.00` | | 祝 `goframe` 越来越完善,尽快支持 `mqtt` | +| [佳雨](https://github.com/xinjiayu) | `wechat` | `¥300.00` | - | 加油! | +| [liusuxian](https://github.com/liusuxian) | `alipay` | `¥300.00` | | | +| 慧橞云汤姆 | `alipay` | `¥288.00` | | 慧橞云汤姆。感谢付出。 | +| [lingcoder](https://github.com/lingcoder) | `alipay` | `¥268.88` | | | +| [王中阳](https://www.bilibili.com/video/BV1Ng41167fW/) | `wechat` | `¥266.66` | | 我会多多分享 `GoFrame` 实战教程,和社区一起越做越好! | +| 扶程星云 | `wechat` | `¥266.66` | | `goframe` 框架真不错,学习资料也很全👍 | +| 侯哥 | `wechat` | `¥210.00` | | | +| [arden](https://github.com/arden) | `wechat/alipay` | `¥210.00` | `2022-03-11 21:39:44` | | +| 普帆(上海)企业管理咨询有限公司 | `alipay` | `¥202.20` | | | +| [Dockercore](https://github.com/dockercore) | `wechat` | `¥200.00` | | 非常喜欢!简洁好用!文档超级全! | +| 北京京纬互动科技 | `alipay` | `¥200.00` | | | +| 丑的别致丶 | `wechat` | `¥200.00` | | 希望框架越来越好 | +| Ciel | `wechat` | `¥200.00` | | 大佬的框架真好用,聊表谢意😊 | +| LFFWL | `wechat` | `¥200.00` | | 第一个外包,gf做的,给强哥来杯咖啡,加油 | +| 曹柏华 | `wechat` | `¥200.00` | | 支持大佬 | +| L. | `wechat` | `¥200.00` | `2022-11-24 21:13:14` | 加油! | +| 陈诚 | `wechat` | `¥200.00` | `2022-11-25 09:25:26` | 加油!成都伟客互联科技有限公司 | +| [刘星杰](https://github.com/stardemo) | `wechat` | `¥200.00` | `2023-07-13 22:24:06` | | +| | `wechat` | `¥200.00` | `2023-08-18 09:04:19` | 感谢强哥 受益良多 | +| lunacy\_zeus | `alipay` | `¥188.00` | | | +| K\*e | `wechat` | `¥168.00` | | 感谢老大 | +| [米司特包](https://github.com/misitebao) | `wechat` | `¥166.66` | | 大佬加油! | +| 陈诚 | `wechat` | `¥166.66` | | 感谢分享!感谢开源! | +| Chao | `wechat` | `¥166.00` | | | +| 陈小刚 | `wechat` | `¥150.00` | | 学习了 | +| 小陈 | `alipay` | `¥150.00` | | 祝 `gf` 越做越好
    希望 `gf` 越来越好 | +| \*\*航 | `alipay` | `¥128.00` | | 感谢您的努力!感谢开源! | +| [李超](https://github.com/effortlee) | `wechat` | `¥124.00` | | | +| | `wechat` | `¥102.40` | `2023-01-16 14:29:18` | 2022.1.16 新年快乐 (2/3) | +| pniYlt | `wechat` | `¥101.00` | | 祝gf越来越强 | +| \*\*阳 | `alipay` | `¥100.01` | | | +| lah | `wechat` | `¥100.00` | | | +| 潘兄 | `wechat` | `¥100.00` | | | +| 全 | `alipay` | `¥100.00` | | | +| 东东 | `wechat` | `¥100.00` | | | +| 陆昱天 | `alipay` | `¥100.00` | | | +| 金毛 | `alipay` | `¥100.00` | | | +| 1\*1x | `wechat` | `¥100.00` | | | +| 如果🍋 | `alipay` | `¥100.00` | | 错过的奶茶^\_^ | +| jack | `wechat` | `¥100.00` | | | +| sbilly | `wechat` | `¥100.00` | | 祝好! | +| [wenzi1](https://github.com/wenzi1) | `alipay` | `¥100.00` | | | +| 金毛 | `wechat` | `¥100.00` | | | +| 莫失莫忘 | `wechat` | `¥100.00` | | | +| 阿康 | `wechat` | `¥100.00` | | | +| yu | `wechat` | `¥100.00` | | 感谢开源,加油!我是QQ群里的lah | +| [Thunur](https://gitee.com/thunur) | `wechat` | `¥100.00` | | | +| \*\*栋 | `alipay` | `¥100.00` | | | +| \*\*浩 | `alipay` | `¥100.00` | | | +| Steve | `wechat` | `¥100.00` | | | +| [deravo](https://github.com/deravo) | `alipay` | `¥100.00` | | 加油 | +| [zhuhuan12](https://gitee.com/zhuhuan12) | `gitee` | `¥100.00` | | | +| struggle | `wechat` | `¥100.00` | | 北京辰安科技有限公司武汉分公司 | +| 挖坑人 | `wechat` | `¥100.00` | | 多喝几杯咖啡,哈哈 | +| 易感 | `wechat` | `¥100.00` | | 来杯咖啡,给强哥润润嗓子 | +| \*强(872\*\*\*@qq.com) | `alipay` | `¥100.00` | | 辛苦各位大佬了 | +| Y崽 | `wechat` | `¥100.00` | | | +| 西红柿炒番茄 | `wechat` | `¥100.00` | | 感谢贡献,想这个项目能一直走下去 | +| \*栋 | `alipay` | `¥100.00` | | | +| 霍莹辉 | `wechat` | `¥100.00` | `2022-03-11 15:00:58` | 支持2.0! | +| 无涯 | `wechat` | `¥100.00` | `2022-03-11 15:08:40` | 感谢分享,等我项目上线了,再回来。 | +| \*\*前 | `alipay` | `¥100.00` | | | +| 王全 | `wechat` | `¥100.00` | `2022-07-06 23:39:38` | 感谢 `gf`,请强哥喝茶 | +| 刘杨😃 | `wechat` | `¥100.00` | `2022-11-24 21:03:32` | 用起来真的爽给个多租户的标准更好 | +| harden | `wechat` | `¥100.00` | `2023-02-28 09:59:06` | github zzehao1990#gmail.com | +| 陈暖 | `wechat` | `¥100.00` | `2023-05-15 13:51:03` | 希望gf越来越好 | +| KR | `wechat` | `¥100.00` | `2023-07-05 19:12:23` | 好用,前端易上手,多多推广呀 | +| | `wechat` | `¥100.00` | `2023-07-11 17:39:46` | 感谢 加油 | +| \*\*华 | `alipay` | `¥100.00` | | | +| Leon | `wechat` | `¥100.00` | `2023-08-18 09:23:33` | gf 很棒 | +| 霍莹辉 | `wechat` | `¥100.00` | `2023-08-18 12:29:01` | gf v3 | +| 陈暖 | `wechat` | `¥100.00` | `2023-10-24 14:30:49` | 祝愿gf越做越好 | +| 乐道 | `wechat` | `¥100.00` | `2024-04-26 10:40:51` | 感谢log的ser prefix功能 | +| [lingcoder](https://github.com/lingcoder) | `wechat` | `¥100.00` | `2024-05-11 12:08:04` | 加油!感谢goframe | +| [lingcoder](https://github.com/lingcoder) | `wechat` | `¥100.00` | `2024-07-09 08:29:04` | 请强哥喝咖啡 | +| [lingcoder](https://github.com/lingcoder) | `wechat` | `¥100.00` | `2024-09-25 14:31:15` | 来杯咖啡提提神! | +| 義薄雲天💋🍃🌺💋 | `wechat` | `¥100.00` | `2024-09-26 10:43:06` | | +| [jiatower](https://github.com/jiatower)| `alipay` | `¥100.00` | `2024-09-27 10:28:17` | | +| [ibryang](https://github.com/ibryang) | `wechat` | `¥100.00` | `2024-09-30 13:32:13` | | +| 喜子 | `wechat` | `¥100.00` | `2024-09-30 14:39:07` | 万事如意 | +| [lingcoder](https://github.com/lingcoder) | `alipay` | `¥100.00` | `2024-12-04 19:10:29` | 加油GF!给强哥点杯咖啡 | +| 杨延庆 | `wechat` | `¥100.00` | `2024-11-06 11:27:43` | | +| 严宇轩 | `alipay` | `¥99.99` | | | +| [米司特包](https://github.com/misitebao) | `wechat` | `¥99.99` | | | +| [seny0929](https://gitee.com/seny0929) | `wechat` | `¥99.90` | | | +| [sbilly](https://github.com/sbilly) | `wechat` | `¥99.00` | | | +| 六七 · | `wechat` | `¥88.88` | | `gf` 越来越好 | +| 喜东东他大哥🖖 | `wechat` | `¥88.88` | | 加油 `gf` | +| 简单 | `wechat` | `¥88.88` | | | +| Jiäng Tāo | `wechat` | `¥88.88` | | 祝越来越好! | +| lobtao | `wechat` | `¥88.88` | | 祝 `goframe` 越来越棒 | +| 海创微联 | `alipay` | `¥88.88` | | | +| qinyuguang | `alipay` | `¥88.88` | | 推荐,很赞 | +| 东成西就 | `wechat` | `¥88.88` | | | +| 张伟 | `wechat` | `¥88.88` | `2023-08-18 08:46:35` | 努力必有回响!愿GF越来越好! | +| [sanfenzui](https://gitee.com/sanfenzui) | `alipay` | `¥88.00` | | | +| 丑的别致丶 | `wechat` | `¥88.00` | `2022-05-20 10:40:36` | 越来越好 | +| [sanrentai](https://github.com/sanrentai) | `alipay` | `¥88.00` | | | +| 紫焰 | `wechat` | `¥88.00` | `2023-01-18 16:15:21` | 强哥一年辛苦了!用GF赚钱了,请你喝杯奶茶,明年再接再厉✌️ | +| 冰冰 | `wechat` | `¥88.00` | | | +| | `wechat` | `¥88.00` | `2022-07-06 22:40:01` | 祝GF越来越好 | +| 飞龙左 | `wechat` | `¥88.00` | `2024-09-30 12:57:10` | 希望goframe越来越好,持续发展100年 | +| AD | `wechat` | `¥88.00` | `2024-09-30 13:11:40` | 争取成为国产TOP 1 | +| 郭亚浩 | `wechat` | `¥80.00` | | | +| Hades | `alipay` | `¥66.66` | | | +| C\*e | `wechat` | `¥66.66` | | `GF` 越来越好,棒👍! | +| [ywanbing](https://github.com/ywanbing) | `wechat` | `¥66.66` | | | +| [米司特包](https://github.com/misitebao) | `wechat` | `¥66.66` | | | +| 秋叶、 | `wechat` | `¥66.66` | | `GF` 带我一起玩 | +| 💥聪จุ๊บ🇨🇳 | `wechat` | `¥66.66` | | 请大佬喝茶 | +| [hyuant](https://github.com/hyuant) | `alipay` | `¥66.66` | | | +| LSJ | `wechat` | `¥66.66` | | 我想我是海:祝 `gf` 越来越好,统治后端 | +| 北漂生活 | `wechat` | `¥66.66` | | `gf` 大展鸿图 | +| 三石 | `wechat` | `¥66.66` | | 给大佬喝杯咖啡 | +| 豆芽菜 | `wechat` | `¥66.66` | | `GF` 加油,太棒了,良心 | +| 小宝 | `wechat` | `¥66.66` | | | +| Tank | `wechat` | `¥66.66` | | | +| 梓荣 | `wechat` | `¥66.66` | | 感谢开源 | +| X\_w | `wechat` | `¥66.66` | | 感谢创作团队, `gf` 极大提升效率!支持支持 | +| 数字化砖块移动工程师 | `wechat` | `¥66.66` | | 祝GF越来越溜 | +| Chowing | `wechat` | `¥66.66` | | 顺德人民发来贺电!! | +| 紫焰 | `wechat` | `¥66.66` | | 强哥666 | +| 张三 | `wechat` | `¥66.66` | | 开源不易。我以前维护过一段 `go-micro` `1.5` 到 `1.8` 版本 坚持太难了,希望你坚持住啊,加油👍! | +| 奇讯科技唐鹏前 | `wechat` | `¥66.66` | | | +| 11ms.cn?wx | `wechat` | `¥66.66` | | 祝优秀的 `gf` 越来越好 | +| 朔 | `wechat` | `¥66.66` | | 来杯咖啡 | +| 谁说不是呢 | `wechat` | `¥66.66` | `2021-12-13 16:49:14` | `GF` 做大做强再创辉煌 | +| \*\*通 | `alipay` | `¥66.66` | | 感谢提供这么好的框架 | +| [2exd](https://github.com/2exd) | `alipay` | `¥66.66` | | 感谢大佬分享!!! | +| 土豆相公 | `alipay` | `¥66.60` | | | +| Tank | `wechat` | `¥66.00` | | 支持 `goframe` | +| 甘明杨 | `wechat` | `¥66.00` | `2022-01-05 14:36:57` | 油来了 | +| 樱吹雪 | `wechat` | `¥66.00` | `2022-01-07 18:28:22` | | +| 自由如风 | `wechat` | `¥66.00` | `2022-01-11 21:33:36` | 希望尽快完善V2文档,麻烦了 | +| newborn.X | `wechat` | `¥66.00` | `2022-01-12 22:00:31` | 感谢强哥,v2设计的很实用 | +| 畅路信息 | `wechat` | `¥66.00` | `2022-05-11 17:22:55` | | +| 疯狂的萝卜 | `wechat` | `¥66.00` | `2022-07-21 11:28:33` | 希望 `gf` 能越做越好,成为 `go` 开发的首选框架 | +| 小见(我只是程序猿) | `wechat` | `¥66.00` | `2022-11-24 21:04:07` | 👍 | +| 邵欣 | `wechat` | `¥66.00` | `2023-03-15 14:17:26` | 强哥威武, `goframe` 越来越好 | +| | `wechat` | `¥66.00` | `2023-08-29 10:23:44` | 感谢大佬开源,加油 | +| yinfeng | `wechat` | `¥66.00` | `2023-09-15 16:47:43` | 老同事,加油 | +| 霍莹辉 | `wechat` | `¥66.00` | `2024-03-13 13:53:21` | 期望一年一个版本 | +| 饶友 | `wechat` | `¥66.00` | `2024-05-08 09:04:18` | 支持 | +| 邱利茂 | `wechat` | `¥66.00` | `2024-09-30 12:52:40` | 👍👍 | +| 【追寻】 | `wechat` | `¥52.10` | `2022-05-20 11:45:27` | 2022.05.20http + grpc. 愿越来越好 | +| 粟\*e | `wechat` | `¥50.00` | | | +| [katydid酱](https://gitee.com/katydid2005) | `gitee` | `¥50.00` | | 感谢您的开源项目!框架给予了很大的帮助!谢谢大佬! | +| [zhuhuan12](https://gitee.com/zhuhuan12) | `wechat` | `¥50.00` | | | +| 李勇 | `wechat` | `¥50.00` | | | +| [szzxing](https://github.com/szzxing) | `wechat` | `¥50.00` | | | +| [mingzaily](https://github.com/mingzaily) | `alipay` | `¥50.00` | | 请大佬喝茶 | +| 智慧人生 | `wechat` | `¥50.00` | | 智慧人生 | +| 辰 | `wechat` | `¥50.00` | | | +| RAGGA-TIME | `alipay` | `¥50.00` | | | +| [ChArmy](https://gitee.com/charmy) | `alipay` | `¥50.00` | | | +| [莫莫大船长](https://github.com/zhwei820) | `wechat` | `¥50.00` | | 感谢强哥,感谢 `gf` 🙏 | +| 一棵松 | `wechat` | `¥50.00` | | | +| \*\*尧 | `alipay` | `¥50.00` | | 捐赠goframe大佬 | +| 会飞的猫 | `wechat` | `¥50.00` | | | +| \*帅 | `alipay` | `¥50.00` | | | +| 霍莹辉 | `wechat` | `¥50.00` | | | +| xuwl | `wechat` | `¥50.00` | | | +| 陈善文 | `wechat` | `¥50.00` | | | +| | `wechat` | `¥50.00` | | 2021-12-03 16:43:42 | +| \*\*富 | `alipay` | `¥50.00` | | 咖啡钱 | +| 胡伟通 | `wechat` | `¥50.00` | | 谢谢支持🤝 | +| \*亮 | `alipay` | `¥50.00` | | 谢谢强哥 `gf` 给强哥来杯咖啡 | +| [ming871](https://github.com/ming871) | `alipay` | `¥50.00` | | | +| \*\*科 | `alipay` | `¥50.00` | | goframe 启动 | +| | `alipay` | `¥50.00` | `2024-03-12 22:08:41` | 捐赠名不发布 | +| \*\*强 | `alipay` | `¥36.00` | | gf更快更高更强,感谢强哥 | +| [go-zend](https://github.com/go-zned) | `alipay` | `¥33.00` | | 拿铁多加糖 | +| 刘桂成 | `wechat` | `¥32.00` | `2022-02-10 18:03:57` | 请强哥喝一杯冰美提神,感谢您! | +| [tiangenglan](https://gitee.com/tiangenglan) | `gitee` | `¥30.00` | | | +| 刘宇 | `wechat` | `¥30.00` | | 请你喝咖啡 | +| 安全专题 | `wechat` | `¥30.00` | | [aqzt.com](http://aqzt.com/) 来支持, `gf` 加油 | +| \*\*波 | `alipay` | `¥29.00` | | | +| [mijjjj](https://github.com/mijjjj) | `wechat` | `¥29.00` | `2024-06-06 12:12:19` | 祝gf越来越好 | +| \*\*虎 | `alipay` | `¥28.00` | | | +| Ray | `wechat` | `¥28.00` | `2022-11-24 21:04:50` | 愿 `goframe` 越来越好! | +| \*\*亮 | `alipay` | `¥20.24` | `2024-08-30 17:15:11` | 2024开始go,和gf一起高飞 | +| [hailaz](https://gitee.com/hailaz) | `gitee` | `¥20.00` | | | +| x\*z | `wechat` | `¥20.00` | | | +| [foxhack](https://github.com/foxhack) | `wechat` | `¥20.00` | | | +| M\*e | `wechat` | `¥20.00` | | | +| [王飞](https://gitee.com/wang_2018) | `gitee` | `¥20.00` | | 感谢您的开源项目! | +| [Zeroing-ZY](https://gitee.com/yunjieg) | `gitee` | `¥20.00` | | 感谢您的开源项目! | +| \*秦 | `wechat` | `¥20.00` | | 给群主献上一杯咖啡... | +| \*. | `wechat` | `¥20.00` | | 学生一个微薄之力,冲 | +| | `wechat` | `¥20.00` | | | +| yiran | `wechat` | `¥20.00` | | 赏给大佬喝茶🍵 | +| 向回走的闹钟 | `wechat` | `¥20.00` | | 越来越好 | +| \*\*航 | `alipay` | `¥20.00` | | | +| 雁字回时月满楼 | `wechat` | `¥20.00` | | 感谢 `gf` | +| Panda | `wechat` | `¥20.00` | | 支持一下! `gf` 很棒👍 | +| 秋葵 | `wechat` | `¥20.00` | | 之前强哥 | +| 老兵 | `wechat` | `¥20.00` | | 初学 `golang`,绵薄之力 | +| 。。。 | `wechat` | `¥20.00` | | 🤭 | +| 星空 | `wechat` | `¥20.00` | | 感谢奉上 | +| eric chan | `wechat` | `¥20.00` | | 吃个早餐 | +| 汤sir | `wechat` | `¥20.00` | | 强哥,来杯咖啡☕️ | +| 三石 | `wechat` | `¥20.00` | | 喝杯奶茶放放松 | +| 王先生 | `wechat` | `¥20.00` | | `gitee` 王先生 | +| [joyoes](https://github.com/joyoes) | `wechat` | `¥20.00` | | | +| \*翀 | `alipay` | `¥20.00` | | | +| \*\*杰 | `alipay` | `¥20.00` | | 超赞的框架,努力学习中 | +| [reake](https://gitee.com/reake) | `gitee` | `¥20.00` | | | +| 张隆 延吉-天网报警 | `wechat` | `¥20.00` | | 感谢开源无私奉献 | +| [oaheiw](https://gitee.com/oaheiw) | `wechat` | `¥20.00` | | | +| 伟子哥 | `wechat` | `¥20.00` | | | +| i | `wechat` | `¥20.00` | | | +| 大咖 | `wechat` | `¥20.00` | | | +| 梦回不流年° | `wechat` | `¥20.00` | | | +| | `wechat` | `¥20.00` | | 2021-12-03 16:44:25 | +| 起风了 | `wechat` | `¥20.00` | `2022-01-03 19:46:07` | 祝GoFrame越来越好 | +| 阿高 | `wechat` | `¥20.00` | `2022-02-25 11:36:34` | 感谢开源 | +| ReviveKwan🌲💻🐯 | `wechat` | `¥20.00` | `2022-05-20 11:49:44` | 老大喝咖啡☕️ | +| @我 | `wechat` | `¥20.00` | | | +| zjx | `wechat` | `¥20.00` | `2022-05-20 14:29:14` | | +| Harris | `wechat` | `¥20.00` | `2022-05-20 15:44:24` | | +| \*\*辉 | `alipay` | `¥20.00` | | 感谢分享,来杯奶茶 | +| \*翀 | `alipay` | `¥20.00` | | 请喝咖啡 | +| Jungle | `wechat` | `¥20.00` | | | +| 红色石头 | `wechat` | `¥20.00` | `2023-03-14 11:57:49` | 来杯咖啡 | +| 水雕歌 | `wechat` | `¥20.00` | `2023-08-18 08:33:51` | 强哥,加油。 | +| | `wechat` | `¥20.00` | `2023-08-18 10:21:32` | 祝gf越来越强 | +| | `wechat` | `¥20.00` | `2023-08-25 13:27:14` | 愿gf越来越好 | +| 灯火消逝的码头 | `wechat` | `¥20.00` | `2024-01-30 15:59:33` | | +| 卡卡 | `wechat` | `¥20.00` | `2024-09-30 13:15:56` | 继续打磨 | +| ℳঞ💕 | `wechat` | `¥20.00` | `2024-09-30 13:56:08` | gf越来越强大 | +| 黄石头 | `wechat` | `¥20.00` | `2024-09-30 14:50:23` | | +| 马健鸿 | `wechat` | `¥20.00` | `2024-09-30 20:30:45` | 强哥,加油👏 | +| 郭淑芸 | `wechat` | `¥19.90` | `2022-07-06 22:56:15` | 默默水群的见习猎人 感谢强哥包容 | +| R\*s | `wechat` | `¥18.88` | | 谢谢 `GF`!辛苦了! | +| [Mr.奇淼](https://www.gin-vue-admin.com/) | `wechat` | `¥18.88` | | 强哥无敌,奇淼爱你 | +| [yodfz](https://github.com/yodfz) | `wechat` | `¥18.88` | | 支持下国人开发者 | +| \*\*青 | `alipay` | `¥18.88` | | | +| struggler | `alipay` | `¥18.80` | | | +| 程凤明 | `wechat` | `¥18.80` | | | +| xu | `wechat` | `¥18.80` | | 感谢大神,验证器,格式转换等工具类写得非常精妙 | +| SliverHorn | `wechat` | `¥17.77` | | 强哥无敌,SliverHorn爱你 | +| sbilly | `wechat` | `¥16.66` | | | +| 大广 | `wechat` | `¥16.66` | `2023-07-13 21:11:27` | 强哥明天喝厚乳拿铁 | +| yinfeng | `wechat` | `¥16.66` | `2024-06-04 18:06:12` | 加油加油,你是最棒的 | +| 夕景 | `alipay+qq` | `¥13.53` | | | +| 巨石糖果山X³ | `wechat` | `¥12.00` | `2022-05-20 10:33:48` | 学完我就牛批了 | +| sailsea | `wechat` | `¥11.00` | | | +| \*\*均 | `alipay` | `¥10.24` | | | +| 赵大大 | `wechat` | `¥10.24` | `2022-05-20 10:28:00` | 祝6 | +| 万思国 | `wechat` | `¥10.24` | `2022-09-10 17:09:14` | 2022.9.9 学习储备(1/3) | +| 何发惠 | `wechat` | `¥10.24` | `2024-02-26 11:00:46` | 祝gf越来越好!感谢gf | +| [tokmn](https://github.com/tokmn) | `wechat` | `¥10.10` | | bravo | +| [mg91](https://gitee.com/mg91) | `gitee` | `¥10.00` | | | +| [pibigstar](https://github.com/pibigstar) | `alipay` | `¥10.00` | | | +| [wxkj](https://gitee.com/wxkj) | `wechat` | `¥10.00` | | | +| [zfan\_codes](https://gitee.com/zfan_codes) | `gitee` | `¥10.00` | | | +| \*络 | `wechat` | `¥10.00` | | | +| \*G | `wechat` | `¥10.00` | | | +| E\*\_ | `wechat` | `¥10.00` | | | +| \*洁 | `wechat` | `¥10.00` | | 赞助你肥宅快乐水 | +| [李海峰](https://gitee.com/dlhf) | `gitee` | `¥10.00` | | 希望 `GF` 越来越好,框架很牛逼! | +| a\*l | `wechat` | `¥10.00` | | `gf` | +| [wxkj](https://gitee.com/wxkj) | `wechat` | `¥10.00` | | | +| [Playhi](https://github.com/Playhi) | `alipay` | `¥10.00` | | | +| [侯哥](http://www.macnie.com/) | `wechat` | `¥10.00` | | | +| \*天 | `wechat` | `¥10.00` | | | +| \*\*亮 | `alipay` | `¥10.00` | | | +| 汤sir | `wechat` | `¥10.00` | | 强哥,喝杯咖啡☕️ | +| 徒行 | `wechat` | `¥10.00` | | 目前项目在转用这个框架,鼓励一下大佬吧👍👍👍 | +| 产品设计.软件开发 | `wechat` | `¥10.00` | | 祝愿走向更成功! | +| [charlieccGuo](https://github.com/charlieccGuo) | `wechat` | `¥10.00` | | | +| Even\_ | `wechat` | `¥10.00` | | 日照市民发来贺电! | +| 一滴水 | `wechat` | `¥10.00` | | `goframe` 越来越强大 | +| \*\*君 | `alipay` | `¥10.00` | | 加油 | +| Tzp | `wechat` | `¥10.00` | | | +| [hkxiaoyu118](https://github.com/hkxiaoyu118) | `wechat` | `¥10.00` | | | +| YJ | `wechat` | `¥10.00` | | YangJ-Eric祝愿越来越好 | +| 牛肉面不要面 | `wechat` | `¥10.00` | | 加个蛋➕ | +| [七米](https://github.com/Qimi) | `wechat` | `¥10.00` | | | +| ´・ᴗ・\` 非礼勿视 | `wechat` | `¥10.00` | | 目前在找机会应用到公司项目中,辛苦了。 | +| [orchie](https://github.com/orchie) | `alipay` | `¥10.00` | | | +| [ezreal\_rao](https://gitee.com/ezreal_rao) | `gitee` | `¥10.00` | | | +| [一路繁华的夏](https://gitee.com/dlhf) | `gitee` | `¥10.00` | | 望GF越来越好,框架还是很牛逼的 | +| KING | `wechat` | `¥10.00` | | 希望越做越好 | +| 魔怔中年人 | `wechat` | `¥10.00` | | 中华脊梁! | +| Believe.God | `wechat` | `¥10.00` | | | +| Byte | `wechat` | `¥10.00` | | 感谢大佬~ | +| Rebellion | `wechat` | `¥10.00` | | 强哥牛逼 不说了 | +| krugle | `wechat` | `¥10.00` | | | +| 李白 | `wechat` | `¥10.00` | | 希望gf越来越美 | +| 岳超栋 | `wechat` | `¥10.00` | | 支持支持 | +| \*\*俊 | `alipay` | `¥10.00` | | | +| | `wechat` | `¥10.00` | | | +| 阳光下的温暖° | `wechat` | `¥10.00` | | | +| 罗 | `wechat` | `¥10.00` | | 捐,用得很爽 | +| flymon | `wechat` | `¥10.00` | | | +| 刘志铭 | `wechat` | `¥10.00` | | 喝杯瑞幸吧 | +| 低调 | `wechat` | `¥10.00` | | 强哥加油 | +| 赵煜 | `wechat` | `¥10.00` | | | +| 刘生 | `wechat` | `¥10.00` | | 辛苦,喝杯咖啡吧 | +| 飞 | `wechat` | `¥10.00` | | 强哥辛苦 | +| 阳泽 | `wechat` | `¥10.00` | | | +| hans | `wechat` | `¥10.00` | | 大佬辛苦了 加油 | +| [vjianfeng](https://github.com/lvjianfeng) | `wechat` | `¥10.00` | | | +| 天真冷 | `wechat` | `¥10.00` | | 强总辛苦了。咖啡奉上。 | +| Lucipher | `wechat` | `¥10.00` | | 开源不易,强哥加油 | +| 肖唔歌 | `wechat` | `¥10.00` | | 喝杯奶茶解解渴 | +| windy | `wechat` | `¥10.00` | | 请强神喝可乐 | +| 喜气洋洋 | `wechat` | `¥10.00` | | | +| Jary | `wechat` | `¥10.00` | | 喝杯奶茶 | +| Lin | `wechat` | `¥10.00` | | 拿去嫖 | +| Yong | `wechat` | `¥10.00` | `2021-12-04 02:33:52` | 支持一下国产 | +| | `wechat` | `¥10.00` | `2021-12-06 13:34:50` | | +| Goodday | `wechat` | `¥10.00` | `2021-12-31 18:14:03` | 加油 | +| Rain🍉 | `wechat` | `¥10.00` | `2022-01-03 21:40:35` | 感谢作者为GO生态做出的贡献 | +| 茫然的高山 | `wechat` | `¥10.00` | `2022-01-07 14:35:28` | thinks | +| 厦秋垒 Lein Xia | `wechat` | `¥10.00` | `2022-01-11 01:14:16` | 感谢,加油 | +| \*民 | `alipay` | `¥10.00` | | 感谢强哥的辛苦付出 | +| \*\*光 | `alipay` | `¥10.00` | | | +| 云先生 | `wechat` | `¥10.00` | `2022-03-22 22:05:58` | 辛苦 望越来越好 | +| Dean | `wechat` | `¥10.00` | `2022-05-20 10:29:43` | 开始转型学习,感谢老版 | +| sany | `wechat` | `¥10.00` | `2022-05-20 10:30:59` | 加油 | +| hans | `wechat` | `¥10.00` | `2022-05-20 10:40:58` | 加油 | +| 长兴波波 | `wechat` | `¥10.00` | `2022-05-20 10:41:17` | | +| 小陈 | `wechat` | `¥10.00` | `2022-05-20 11:47:46` | 新人,谢谢大佬 | +| 🍀 | `wechat` | `¥10.00` | `2022-05-20 11:58:01` | 新手上路 | +| SHEN-LL | `wechat` | `¥10.00` | `2022-05-20 12:06:19` | 小小支持一下,感谢强哥 | +| 征 | `wechat` | `¥10.00` | `2022-05-20 12:13:34` | | +| 卡卡 | `wechat` | `¥10.00` | `2022-05-20 13:26:09` | | +| 请叫我八点起床 | `wechat` | `¥10.00` | `2022-05-20 13:54:02` | 能不能把redis client暴露出来 | +| 清凨 | `wechat` | `¥10.00` | `2022-07-02 10:01:30` | 咖啡钱😊 | +| \*\*领 | `alipay` | `¥10.00` | | | +| \*\*科 | `alipay` | `¥10.00` | | | +| \*\*杰 | `alipay` | `¥10.00` | | 吃个辣条 | +| 赵渊 | `wechat` | `¥10.00` | `2022-07-07 08:12:47` | 我强哥威武 | +| JCJC错误别字检测-田春峰 | `wechat` | `¥10.00` | `2022-09-02 18:39:12` | 感谢GoFrame社区的努力 | +| A阿牛哥-python&go&layui&vue3 | `wechat` | `¥10.00` | `2022-11-24 21:33:18` | `gf` 越来越强,月薪过万就靠强哥了^ω^ | +| | `wechat` | `¥10.00` | `2023-01-08 11:40:21` | 加油 | +| | `wechat` | `¥10.00` | `2023-02-14 18:25:02` | 大哥辛苦了 希望 `GF` 越来越好 | +| | `wechat` | `¥10.00` | `2023-04-27 08:17:15` | 从 `gf` 中学到了很多,希望越来越好,为授人以渔干杯 | +| ℳঞ别太肖张💕 | `wechat` | `¥10.00` | `2023-06-01 11:29:06` | 非常好用👍🏻 | +| fly bird | `wechat` | `¥10.00` | `2023-08-20 18:47:46` | 框架确实不错,准备拿来用也。 | +| | `wechat` | `¥10.00` | `2023-09-30 09:31:42` | 愿越来越好,使用框架赚到了点辛苦钱,感谢有你 GoFrame | +| lin文锐 | `wechat` | `¥10.00` | `2023-11-23 11:48:58` | 谢谢,gf很强 | +| 广州大智汇 | `wechat` | `¥10.00` | `2023-11-29 17:37:58` | | +| Sumail | `wechat` | `¥10.00` | `2024-09-30 12:51:19` | | +| [tutuge](https://github.com/tutuge) | `wechat` | `¥10.00` | `2024-09-30 13:03:52` | | +| lobtao | `wechat` | `¥10.00` | `2024-09-30 13:08:37` | 😂 | +| | `wechat` | `¥10.00` | `2024-09-30 13:11:20` | 一杯奶茶 | +| HUGO | `wechat` | `¥10.00` | `2024-10-01 01:48:12` | | +| [米司特包](https://github.com/misitebao) | `wechat` | `¥9.99` | | | +| faddei | `qq` | `¥9.99` | | | +| \*庆 | `alipay` | `¥9.99` | | 支持一下, `gf` 越来越好 | +| [seny0929](https://gitee.com/seny0929) | `alipay` | `¥9.90` | | | +| Glowworm | `wechat` | `¥8.88` | | 感谢大佬 | +| 🚶 | `wechat` | `¥6.88` | | 喝杯冰阔落 | +| [王哈哈](https://gitee.com/develop1024) | `wechat` | `¥6.66` | | 希望 `gf` 越来越好 | +| ---P คิดถึง | `wechat` | `¥6.66` | | | +| 重庆宝尔威科技 | `wechat` | `¥6.66` | | | +| 琦玉-QPT | `wechat` | `¥6.66` | | | +| \*华 | `wechat` | `¥6.66` | | 感谢郭强的热心 | +| 黄布斯 | `wechat` | `¥6.66` | | 感谢你们的付出,让我们一起进步 | +| xride | `wechat` | `¥6.60` | | 越来越强呀 | +| \*栈 | `wechat` | `¥5.00` | | | +| zxc | `wechat` | `¥5.00` | | 感谢 | +| lester | `wechat` | `¥5.00` | | 微信就剩5元了 | +| book云 | `wechat` | `¥5.00` | | 请强哥喝可乐 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" new file mode 100644 index 00000000000..ce3e9ae2c2f --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\344\272\244\346\265\201\347\276\244.md" @@ -0,0 +1,34 @@ +--- +slug: '/share/group' +title: '技术交流群' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,技术交流群,QQ群,微信群,实战群,编程社区,软件开发,开发者交流,GoFrame公众号] +description: '加入GoFrame框架的技术交流群,参与GoFrame实战群和微信群,获取最新的框架发展动态和技术交流。我们提供多个QQ群和微信群,供爱好者们分享经验和交流开发心得,并持续关注GoFrame的最新资讯。' +--- + +## 加入QQ群 + +扫描或群号搜索添加。 + +### GoFrame实战1群 +已满 + +### GoFrame实战2群 +已满 + +### GoFrame实战3群 + + +点击链接加入群聊【GoFrame实战3群】: [https://qm.qq.com/q/K7UMKSrVq8](https://qm.qq.com/q/K7UMKSrVq8) + +## 加入微信群 +扫描或微信添加 `389961817` 备注 加群 + + + + +## 微信公众号 +关注 `GoFrame` 的发展动态 + + diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" new file mode 100644 index 00000000000..a07d0b41e7c --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/0-2021-03-12 \345\210\235\346\254\241\350\247\201\351\235\242.md" @@ -0,0 +1,66 @@ +--- +slug: '/share/2021-03-12' +title: '2021-03-12 初次见面' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,技术分享,线上见面会,框架介绍,框架设计,模块化设计,统一框架设计,代码分层设计,社区驱动建设] +description: '本次GoFrame技术分享介绍其框架设计理念及模块化设计,包含统一框架设计和代码分层设计等,不涉及具体技巧。活动有开发者与作者交流环节并使用腾讯会议接入,欢迎大家准备问题并加入交流。' +--- + +大家好啊! + +为了兑现对社区的承诺,我们计划开展第一次 `GoFrame` 技术分享(线上见面会)!本次分享的主题是: **GoFrame框架介绍及设计**。 + +同时,我们也有开发者与作者面对面交流环节,并且预留了比较充足的交流时间,大家可以准备好需要提问的问题。 + +为保证交流体验,提问环节使用腾讯会议接入,请需要提问的同学请提前加入会议。 + +## 一、内容大纲 + +本次主要分享一些框架的设计理念,不会讲具体使用技巧性的内容。 + +1. [框架介绍(最新版本)](https://goframe.org) +2. 项目初心 +3. [框架设计](../../../docs/框架设计/框架设计.md) + - [模块化设计](../../../docs/框架设计/模块化设计.md) + - [统一框架设计](../../../docs/框架设计/统一框架设计.md) + - [代码分层设计](../../../docs/框架设计/工程开发设计/代码分层设计.md) +4. 其他杂项 +5. 社区驱动建设的计划 +6. 交流环节 + +## 二、加入方式 + +### B站直播 + +[http://live.bilibili.com/22901270](https://live.bilibili.com/22901270) + +### 腾讯会议 + +郭强 邀请您参加腾讯会议 + +会议主题:GoFrame初次见面 + +会议时间:2021/03/12 19:00-20:00 (GMT+08:00) 中国标准时间 - 北京 + +点击链接入会,或添加至会议列表: + +[https://meeting.tencent.com/s/yNMOX0LDlY8N](https://meeting.tencent.com/s/yNMOX0LDlY8N) + +会议 ID: **135 616 293** + +手机一键拨号入会 + ++8675536550000,,135616293# (中国大陆) + ++85230018898,,,2,135616293# (中国香港) + +根据您的位置拨号 + ++8675536550000 (中国大陆) + ++85230018898 (中国香港) + +PS: + +大家也可以把需要提问的问题评论到本文,以便我们可以事先准备,届时提高交流效率。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" new file mode 100644 index 00000000000..8f5f1381a74 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/1-2021-04-17 GoCN Gopher Meetup.md" @@ -0,0 +1,34 @@ +--- +slug: '/share/2021-04-17' +title: '2021-04-17 GoCN Gopher Meetup' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,Golang,技术分享,框架设计,微服务开发,活动报名,GoCN,Tap4Fun,成都] +description: '2021年4月17日在成都由GoCN和Tap4Fun举办的Golang领域的技术分享活动,主要围绕GoFrame框架设计及微服务开发展开介绍。作为讲师之一,我参与了这次成功的活动,活动内容详实,礼品丰富。更多关于GoFrame设计的介绍可参见相关文档章节。' +--- + +大家好啊,新的一轮 `GoFrame` 框架技术分享来啦! + +这次呢,是由 `GoCN` 和 `Tap4Fun` 在成都举办的一次 `Golang` 领域的技术分享,很荣幸作为其中一位讲师受邀参加。 + +本次技术分享主题仍旧以框架设计介绍为主,以下是参考目录: + +![](/markdown/0940acf2b1d7c695310da3d8316e88fc.png)![](/markdown/ec7498b17b29822f11262708edfba681.png) + +更多关于 `GoFrame` 设计相关的介绍,可参考《 [框架设计](../../../docs/框架设计/框架设计.md)》章节。 + +由于时间有限,针对于大家比较感兴趣的微服务开发方面的介绍,会在后续活动中再进行分享交流。 + +活动报名地址: [https://www.bagevent.com/event/7198261](https://www.bagevent.com/event/7198261?code=001hWnFa1BenRA0Q3lGa1V6Sci0hWnFn&state=STATE) + +* * * + +华丽的分割线 + +* * * + +感谢本次 `GoCN` 及 `Tap4Fun` 举办的 `Golang` 技术分享活动,整个活动举办得很成功,主办方也很用心,礼品也很不错❤️。 + +这里是本次 `GoFrame` 框架的技术分享的PPT稿件,欢迎各位按需取用。 + +[gocn-GoFrame框架介绍及设计.pdf](/attachments/gocn-GoFrame-intro.pdf) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..ec1f63b358a --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/2-2021-06-24 GoFrame ORM\347\273\204\344\273\266\350\256\276\350\256\241.md" @@ -0,0 +1,22 @@ +--- +slug: '/share/2021-06-24' +title: '2021-06-24 GoFrame ORM组件设计' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM组件,组件设计,GOCN开源说,核心特性,开发文档,直播,项目源码,PPT下载] +description: '本次分享主题《GoFrame ORM组件设计》主要介绍GoFrame框架中核心组件ORM的设计及其相关特性。通过这次分享,了解GoFrame的ORM组件功能及其突出特性,更多细节可参考开发文档与项目源码。本次活动还包括相关PPT文件下载和录播视频观看。' +--- + +Hi,大家好啊,本周四晚8点GOCN开源说分享主题《GoFrame ORM组件设计》,主要介绍GoFrame框架中核心组件ORM的设计及其相关特性。 + +GoFrame的ORM组件功能相当强大,由于准备及分享时间有限,本次分享主要对其一些突出的特性进行介绍, +更多的细节欢迎大家参阅 [开发文档](../../../docs/核心组件/核心组件.md) +以及 [项目源码](https://github.com/gogf/gf/tree/master/database/gdb)。 + +直播地址: [https://live.bilibili.com/21878276](https://live.bilibili.com/21878276) + +![](/markdown/2a110b53cb90f2b0ea1af07ed17c4925.png)![](/markdown/823bc3dcb047efeda12e132de48456e4.png) + +PPT文件下载: [GoFrame ORM组件设计.pptx](/attachments/GoFrameORM-design.pptx) + +录播视频观看: [https://www.bilibili.com/video/BV1yw411o7NE](https://www.bilibili.com/video/BV1yw411o7NE) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" new file mode 100644 index 00000000000..d79b47992c5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/3-2022-01-27 Let's GoFrame.md" @@ -0,0 +1,22 @@ +--- +slug: '/share/2022-01-27' +title: "2022-01-27 Let's GoFrame" +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,Golang,开源,高性能,企业级,开发框架,开箱即用,OpenTelemetry,错误堆栈] +description: 'GoFrame框架的核心设计、功能特性以及实战应用。GoFrame是一款模块化、高性能的企业级Go基础开发框架,适用于各种规模的业务型项目。通过丰富的基础组件、开发工具和实战化规范,开发者可以集中精力于业务逻辑,提高开发和维护效率,创造更大价值。' +--- + +Hello 朋友们,早出晚归忙活了一年,好久没有来点技术分享啦! + +![](/markdown/4690d7d19d1de647c100d48a4389dedf.png)![](/markdown/806eac487471281a18dc07dd07d9f000.png) + +本周四晚8点 `GOCN` 开源说分享主题《 `Let's GoFrame`》,主要介绍 `GoFrame` 框架中的一些核心设计、功能特性以及实战演示。 + +`GoFrame` 是一款模块化、高性能、企业级的 `Go` 基础开发框架。如果您想使用 `Golang` 开发一个业务型项目,无论是小型还是中大型项目, `GoFrame` 是您的不二之选。如果您想开发一个 `Golang` 组件库, `GoFrame` 提供开箱即用、丰富强大的基础组件库也能助您的工作事半功倍。 + +`GoFrame` 提供了统一的、实战化工程开发规范及配套落地的开发工具、自动化的数据模型及数据库操作代码生成、自动化的 `OpenAPIv3` 接口文档生成、支持 `OpenTelemetry` 可观测性标准、全错误堆栈特性、支持错误码特性、核心组件全接口化设计等等功能特性。使用 `GoFrame` 开箱即用的基础组件、丰富的开发文档、实战化的工具和规范能帮助我们将精力聚焦到业务本身,编写更加规范、安全、可观测的代码,提高项目开发维护效率,团队及个人能够创造更多的价值。 + +PPT文件下载: [gocn-Let's GoFrame.pptx](/attachments/gocn-LetsGoFrame.pptx) + +`bilibili` 视频地址: [https://www.bilibili.com/video/BV1U34y117w7/](https://www.bilibili.com/video/BV1U34y117w7/) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" new file mode 100644 index 00000000000..d97c65d674e --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/4-2022-05-17 GoFrame\350\264\241\347\214\256\346\214\207\345\215\227&\344\275\277\347\224\250\347\255\224\347\226\221.md" @@ -0,0 +1,50 @@ +--- +slug: '/share/2022-05-17' +title: '2022-05-17 GoFrame贡献指南&使用答疑' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,贡献指南,开源项目,社区贡献,开源摘星活动,使用答疑,GitHub,开发者,腾讯会议] +description: 'GoFrame框架的贡献指南及使用答疑,分享如何为GoFrame开源项目做出贡献,并讲解了腾源会组织的开源摘星活动,旨在通过社区的共同努力提升项目的稳定性和活力,并为参与者提供积分奖励。活动将于2022年5月17日举行,通过腾讯会议参与。' +--- + +## 基本介绍 + +Hallo,社区小伙伴们好啊! + +随着GoFrame框架的主体功能越趋于完善和稳定,未来GoFrame的主要精力会集中于推动社区贡献。 + +截止2022-05-17,目前github上,开源项目使用GoFrame框架的开源项目有1K+,为GoFrame项目提交贡献的小伙伴达到了89人。部分的小伙伴成为了持续的贡献者,随着贡献者队伍的壮大,咱们的开源项目越来越强大、稳健、开放、活力四射。 + +![](/markdown/09b9ca341360819fe4a46e1fb6e62fe0.png) + +## 分享主题 + +### GoFrame贡献指南 + +首先,咱们这次的分享,主要介绍一下如何给GoFrame提交贡献,GoFrame需要什么。 + +### 开源摘星活动介绍 + +其次,这次也会介绍腾源会组织的 **开源摘星活动**,介绍大家如何在一边给GoFrame提供急需的贡献时,一边赚取开源摘星活动的积分奖励。 + +### 使用答疑环节 + +最后,我们也预留充足的答疑时间,大家在使用的过程中的疑问可在会议上进行提问。 + +## 会议链接 + +郭强 邀请您参加腾讯会议 + +会议主题:GoFrame贡献指南&使用答疑 + +会议时间:2022/05/17 20:00-22:00 (GMT+08:00) 中国标准时间 - 北京 + +点击链接入会,或添加至会议列表: + +[https://meeting.tencent.com/dm/k7o72Mh8TN0G](https://meeting.tencent.com/dm/k7o72Mh8TN0G) + +#腾讯会议:718-825-328 + +复制该信息,打开手机腾讯会议即可参与 + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" new file mode 100644 index 00000000000..1766e75f2b3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/5-2022-06-22 GoFrame v2.1\345\212\237\350\203\275\347\211\271\346\200\247&\344\275\277\347\224\250\347\255\224\347\226\221.md" @@ -0,0 +1,38 @@ +--- +slug: '/share/2022-06-22' +title: '2022-06-22 GoFrame v2.1功能特性&使用答疑' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,v2.1功能特性,使用答疑,在线提问,会议链接,腾讯会议,视频回顾,郭强,开发者分享] +description: '2022年6月22日举办的GoFrame v2.1功能特性分享会,在线介绍新版特性及使用答疑。会议由郭强主持,使用腾讯会议进行。参与者可在线提问,解决在使用GoFrame框架过程中的疑惑,并提供视频回顾链接,帮助用户更好地掌握GoFrame v2.1版本的更新与应用。' +--- + +## 分享主题 + +### `v2.1` 功能特性介绍 + +在线给大家介绍本次功能特性的主要内容以及注意事项。 + +### 使用答疑环节 + +大家在使用的过程中的疑问可在会议上进行在线提问,我们将力所能及帮助大家。 + +## 会议链接 + +郭强 邀请您参加腾讯会议 + +会议主题:GoFrame v2.1功能特性&使用答疑 + +会议时间:2022/06/22 20:00-20:30 (GMT+08:00) 中国标准时间 - 北京 + +点击链接入会,或添加至会议列表: + +[https://meeting.tencent.com/dm/hYdbmxIBHt6l](https://meeting.tencent.com/dm/hYdbmxIBHt6l) + +#腾讯会议:329-799-787 + +复制该信息,打开手机腾讯会议即可参与 + +## 视频回顾 + +[https://www.bilibili.com/video/BV1YS4y1v7BT/](https://www.bilibili.com/video/BV1YS4y1v7BT/) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..376882bddad --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\346\212\200\346\234\257\345\210\206\344\272\253\344\272\244\346\265\201/6-2022-09-14 ORM\351\251\261\345\212\250\345\274\200\345\217\221.md" @@ -0,0 +1,42 @@ +--- +slug: '/share/2022-09-14' +title: '2022-09-14 ORM驱动开发' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM驱动开发,ORM接口,技术分享,单元测试,框架使用,社区贡献,接口开发示例,开发注意事项] +description: '有关GoFrame框架的ORM驱动开发的详细内容,包括接口设计、接口介绍及其关系、接口开发的实际示例;此外,还涵盖了开发注意事项、单元测试编写以及如何将驱动贡献给社区的过程,并就框架使用的常见问题进行了解答。读者可以通过本文档来提升对GoFrame ORM 驱动开发的理解。' +--- + +大家好,近期搜集到社区有许多小伙伴们对ORM驱动开发比较感兴趣,因此准备一次技术分享,主要介绍GoFrame ORM驱动开发的一些细节。感兴趣的小伙伴可以准时参加。 + +## 会议信息 + +郭强 邀请您参加腾讯会议 + +会议主题:GoFrame ORM驱动开发 + +会议时间:2022/09/14 19:30-20:00 (GMT+08:00) 中国标准时间 - 北京 + +点击链接入会,或添加至会议列表: + +[https://meeting.tencent.com/dm/lBhnKKnhik0g](https://meeting.tencent.com/dm/lBhnKKnhik0g) + +#腾讯会议:413-297-318 + +复制该信息,打开手机腾讯会议即可参与 + +## 视频回放 + +[https://www.bilibili.com/video/BV1rT411M7Sp/](https://www.bilibili.com/video/BV1rT411M7Sp/) + +## 主要内容 + +- GoFrame ORM介绍 +- ORM接口设计 +- ORM接口介绍 +- ORM接口关系 +- 接口开发示例 +- 开发注意事项 +- 单元测试编写 +- 将驱动贡献给社区 +- 框架使用答疑 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" new file mode 100644 index 00000000000..46b0c9dfdb8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\344\272\244\346\265\201/\347\244\276\345\214\272\344\272\244\346\265\201.md" @@ -0,0 +1,11 @@ +--- +slug: '/share' +title: '社区交流' +sidebar_position: 0 +hide_title: true +--- + + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" new file mode 100644 index 00000000000..1ebcb21eeb2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/Golang\346\241\206\346\236\266\351\200\211\345\236\213\346\257\224\350\276\203_goframe_beego_iris\345\222\214gin.md" @@ -0,0 +1,588 @@ +--- +slug: '/articles/framework-comparison-goframe-beego-iris-gin' +title: 'Golang框架选型比较: goframe, beego, iris和gin' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame, Beego, Iris, Gin, Golang框架对比, Web框架, 框架选型, 技术选型, 模块化设计, 工程化, 易用性, 框架性能, 开发效率] +description: '深入对比GoFrame、Beego、Iris和Gin四大Golang Web框架的特性、优势和实践经验,从模块化设计、易用性、文档完善度、工程化等多个维度进行分析,为团队技术选型提供参考。包含实际项目经验分享及框架迁移案例。' +--- + + + +迁移自旧版官网社区贡献文章:https://wiki.goframe.org/pages/viewpage.action?pageId=3673375 + + + +![Solve problems? Bring problems?](golang-framework-choose.jpeg) +Solve problems? Bring problems? + +由于工作需要,这些年来也接触了不少的开发框架,Golang的开发框架比较多,不过基本都是Web"框架"为主。这里稍微打了个引号,因为大部分"框架"从设计和功能定位上来讲,充其量都只能算是一个组件,需要项目使用的话得自己四处再去找找其他的组件,或者自己造轮子。如果用于Web开发,这些"框架"的Web开发能力均已完备,无太大差别,且均是自标准库net/http.Server的二次封装。由于框架众多,这里笔者只选择了几个曾做过技术选型评估、较为熟悉,且目前比较流行和典型的Golang"框架",从适用于业务项目开发框架的角度,做一个简单的横向比较,以便大家在项目框架选型时做个参考。 + +评估指标 +==== + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    指标说明
    基本介绍来源各自官网。
    模块化设计是否支持模块化插拔设计、模块之间低耦合设计,是否可以独立使用其中某部分组件。
    模块完善度框架提供的功能模块是否丰富。模块能否能覆盖日常普遍的开发需求。
    使用易用性易用性不仅仅是值框架好不好用,更多是团队能否在低成本下快速接入,长期来看能否低成本维护。
    文档完善性参考官网提供的介绍资料,包括但不限于:文档、视频、示例、案例资料。同时,本地中文文档支持也是参考项。
    工程化完备是否能够快速接入项目开发,是否提供项目接入规范、设计模式、开发工具链,文档是否完善、源码是否易读、是否便于长期维护。
    开发模式框架适用的开发模式,或者官方推荐的开发模式。
    工程规范项目接入时的开发规范,如目录规范、设计规范、编码规范、命名规范等。
    社区活跃官方与社区沟通是否便捷,问题是否能够快速解答,BUG是否能够快速响应处理。
    开发工具链项目开发时使用到的CLI开发工具,如初始化项目、交叉编译、代码生成、swagger、热编译能力等等。
    Web: 性能测试 +

    来源第三方评测 https://github.com/the-benchmarker/web-frameworks

    +
    Web: 路由冲突处理存在路由注册冲突时有无良好的解决方案,在业务项目开发中比较常见。
    Web: 域名支持Web路由是否支持域名绑定,甚至多域名的绑定。
    Web: 文件服务Web服务是否提供静态资源的访问能力。
    Web: 优雅重启/关闭Web服务在重启时不会影响请求执行,关闭时会等待正在执行的请求处理完,新请求不再接入。
    ORM框架是否自带ORM组件,ORM组件是业务项目的核心组件。无论是自研还是通过第三方组件引入。
    Session框架是否提供会话管理组件,无论是通用型Session组件,还是仅针对于Web服务的Session组件。
    I18N支持国际化组件支持(常用但非核心组件)。
    配置管理配置管理也是框架需要完备的核心组件能力。
    日志组件日志组件也是框架需要完备的核心组件能力。
    数据校验数据校验也是框架需要完备的核心组件能力。
    缓存管理缓存管理也是框架需要完备的核心组件能力。无论是内存还是Redis,无论是自研还是通过第三方组件引入。
    资源打包支持将依赖的文件资源例如静态资源、配置文件等固定文件编译到可执行文件中。框架组件自动支持资源检索。
    链路跟踪框架是否具备分布式链路跟踪能力,分布式跟踪在微服务架构中是必不可少的能力。
    测试框架框架是否支持单元测试接入,提供单元测试接入规范。无论是使用标准库还是第三方测试框架。
    突出优点比较明显的几点优点。
    突出缺点比较明显的几点缺点。
    + +横向比较 +==== + +* 以下部分对比参数涉及评分的部分,满分总共按照10分为标准。 +* 如果标记为"-"的部分,表示不支持或者需要引入第三方插件支持。 +* 以下特性如果官网提供文档则直接提供文档地址,找不到文档但是笔者知道有就会简单标注。 +* 各个"框架"功能特性实现不同,在文档、功能、易用性上存在较大差异,各位朋友可自行查阅链接。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

    GoFrameBeegoIrisGin
    比较版本v1.15.2v1.12.3v12.0.2v1.6.3
    项目类型开源(国内)开源(国内)开源(海外)开源(海外)
    开源协议MITApache-2BSD-3-ClauseMIT
    框架类型模块化框架Web框架Web"框架"Web"框架"
    +

    基本介绍

    +
    工程完备、简单易用,模块化、高质量、高性能、企业级开发框架。最简单易用的企业级Go应用开发框架。目前发展最快的Go Web框架。提供完整的MVC功能并且面向未来。一个Go语言写的HTTP Web框架。它提供了Martini风格的API并有更好的性能。
    项目地址 +

    github.com/gogf/gf

    +
    github.com/beego/beegojackfan.us.kg/kataras/irisjackfan.us.kg/gin-gonic/gin
    官网地址goframe.org beego.meiris-go.comgin-gonic.com
    +

    模块化设计

    +
    ---
    模块完善度10642
    +

    使用易用性

    +
    99910
    +

    文档完善度

    +
    10864
    工程化完备10851
    社区活跃910910
    开发模式模块引入三层架构、MVCMVCMVC-
    工程规范分层设计对象设计项目结构--
    开发工具链gf工具链bee工具链--
    Web: 性能测试8889
    Web: HTTPSHTTPS & TLS支持CustomHttpConfiguration支持
    Web: HTTP2--支持支持
    Web: WebSocketWebSocket服务-
    Web: 分组路由路由注册-分组路由NamespaceGroupingRoutes
    Web: 路由冲突处理--
    Web: 域名支持域名绑定---
    Web: 文件服务静态文件服务静态文件处理ServingStaticFiles
    Web: 多端口/实例多端口监听多实例监听-RunMultipleServiceUsingIris-
    Web: 优雅重启/关闭平滑重启特性热升级GracefulShutdownOrRestartGracefulRestartOrStop
    ORMORM文档ORM文档--
    SessionSessionSession-
    I18N支持I18NI18NLocalization-
    模板引擎模板引擎View设计TemplateRenderingHtmlRendering
    配置管理配置管理参数配置-CustomHttpConfig
    日志组件日志组件Logging--
    数据校验数据校验表单数据验证-CustomValidators
    缓存管理缓存管理Cache--
    资源打包资源管理bee工具bale命令--
    链路跟踪服务链路跟踪---
    测试框架单元测试-TestingTesting
    突出优点 + goframe主要以工程化和企业级方向为主,特别是模块化设计和工程化设计思想非常棒。针对业务项目而言,提供了开发规范、项目规范、命名规范、设计模式、开发工具链、丰富的模块、高质量代码和文档,社区活跃。作者也是资深的PHP开发者,PHP转Go的小伙伴会倍感亲切。 + + beego开源的比较早,最早的一款功能比较全面的Golang开发框架,一直在Golang领域有着比较大的影响力,作者谢大多年组织着国内影响力比较大GopherCN活动。beego有着比较丰富的开发模块、开箱即用,提供了基于MVC设计模式的项目结构、开发工具链,主要定位为Web开发,当然也可以用于非Web项目开发。 + iris主要侧重于Web开发,提供了Web开发的一系列功能组件,基于MVC开发模式。iris这一年发展比较快,从一个Web + Server的组件,也慢慢朝着beego的设计方向努力。gin专注于轻量级的Web + Server,比较简单,易于理解,路由和中间件设计不错,可以看做替代标准库net/http.Server的路由加强版web server。献给爱造轮子的朋友们。
    突出缺点开源时间较晚,推广过于佛系,目前主要面向国内用户,未推广海外。起步较早,自谢大创业后,近几年发展较慢。非模块化设计,对第三方重量级模块依赖较多。 +

    号称性能最强,结果平平。非模块化设计。最近两年开始朝beego方向发展,但整体框架能力还不完备,需要加油。

    +
    功能简单易用,既是优点,也是缺点。
    + +经验分享 +==== + +不同的需求场景,存在不同的选择。选择适合的工具,解决适合的问题。 + +开源不存在孰好孰坏之分,开源作者能够本着开源精神给大家分享技术成果用以学习和使用,这本身就是一件非常不易并且值得称道的事情。 + +最后,笔者在这里跟大家分享一下自己所在团队的情况,以及在`Golang`技术栈转型过程中所走的弯路,希望能在框架选型这一环节,能给大家作一定参考。 + +团队最初痛点 +------ + +团队转型`Golang`技术栈的一些背景。主要几点: + +1. 团队后端最初的主要技术栈为`PHP`,由于业务发展需要,进行微服务改造。第一版微服务采用了`PHP`+`JsonRpc`的通信方式。 +2. 随着项目增多,公司也组件了自己的`DevOps`团队,底层部署转向了`Docker`+`Kubernetes`容器架构,并且引入了`Golang`技术栈。 +3. 由于一些痛点,通过一段时间对`PHP`和`Golang`的比较,团队决定快速转型`Golang`技术栈,主要痛点如下: + 1. `PHP`项目在业务复杂后、项目中后期的开发和维护成本整体偏高。主要原因还是其过高的灵活性,非结构化的变量设计,参差不齐的开发人员素质。 + 2. 上云容器化部署后,`PHP`的`DevOps`效率太低。复杂的`Composer`版本管理,超大的`Docker`镜像大小,都影响着`DevOps`的效率。相比较而言,`Golang`效率极其高效。 + 3. `JsonRpc`通信协议设计下,接口的扩展性和灵活性很高,但服务之间很难快速确定接口的输入与输出定义,只能根据文档和示例进行对接和维护。由于代码和文档分离,大部分场景下接口文档维护往往滞后于接口变化。随着服务的不断增加,非结构化的通信协议管理使得服务接口的开发和维护成本进一步提高。 + 4. `JsonRpc`的通信协议本质基于`HTTP1.x`+`Json`,执行效率过低,算不上真正的微服务通信协议,很难对接上主流的服务治理框架。相比较基于`HTTP2.x`的`gRPC`协议有着成熟微服务开发框架和服务治理解决方案。 + 5. 业务梳理的考量,`PHP`到`Golang`技术栈的迁移,其实也是一次技术重构的契机,在技术重构的过程中也重新梳理业务系统设计,偿还技术债务。 + +进一步的痛点 +------ + +`Golang`确实足够简单,相比较其他的解释类开发语言,没有过多的语法糖和语言特性,因此团队上手很快,并快速完成了一部分业务系统的技术重构。但随之而来的是更加严重的痛点。主要几点: + +1. **轮子过多:**`Golang`实在太简单了,以至于我们的团队成员爆发了压抑许久的闷骚劲,充分发挥"造后不管"的造轮精神,开发/封装了许多大大小小的轮子。这些轮子均能满足最基本的功能,例如:日志、配置、缓存等等。但轮子并不是实现一个基础功能的半成品就了事,需要保证功能性、稳定性、扩展性和可维护性,要能结合更多生产实践验证,更需要能够长期维护、持续进行迭代改进。否则,就是一堆大小不一的成人玩具。造轮一时爽,维护火葬场。直到现在,我们还在为分散在`100`多个`Golang`项目中的数十个成人玩具做大统一的事情痛苦不已。当然,这个问题也跟组织架构和团队管理也有很大关系。 +2. **不成体系:** + 1. 我们坚信一个`package`只做一件事情,并且特地使用**单仓库包**的形式进行包管理,相当于每个`package`都是独立维护的`git`仓库。其实**单仓库包**和`package`设计并不存在必要性,反而独立的单仓库包提高了组件和框架的维护成本。 + 2. 这种单仓库包设计难以形成技术体系,在团队技术管理上,难以形成统一的技术框架。单仓包显得很孤立,而一个技术体系的建立除了需要制定规范和标准,更需要技术框架来准确落地。一个成体系的、统一的技术框架,至少涉及到数十个基础技术组件,不可能完全孤立设计。每一个`package`的基础功能实现都很简单,但是如何能够统一组织在一起却不是一件简单的事情,这需要团队的技术管理者需要有一定的技术底蕴、格局和前瞻性,而不是和普通开发者那样眼界只能局限于`package`本身。 + 3. 这种孤立的单仓库包设计,对于业务项目的规范化约束不强,每一个组件都可以独立替换,也至于痛点1的问题越发严重(连日志组件都好几套,虽然都满足基本的日志规范设计)。最终,我们最初引以为傲的单仓库包设计,最终变成了一堆散沙。例如,就连需要增加标准化的链路跟踪功能,由于单仓库包过于散乱和不统一,使得推进改进成本极其高昂。 + 4. 除了使得技术体系难以建立,技术规范难以准确落地之外,每个组件的易用性也设计得较差。举个简单例子,我们的日志组件、缓存组件、数据库组件、HTTP/gRPC Server组件都需要对接配置管理功能,单仓包需要保证低耦合设计,因此开发者在使用的时候需要先手动读取配置、并转换为目标配置对象、并注入到对应的组件初始化方法中,随后才能将该对象运用到业务逻辑中,若干个业务项目均是重复此步骤。其实`goframe`在这块的易用性设计就挺不错,每个包当然是独立设计的,在统一的技术框架体系下,再独立提供一个耦合的单例模块将常用的对象进行单例化封装,自动实现配置读取、配置对象转换、配置对象注入及组件对象初始化,开发者仅需要调用一个单例方法即可。而这个常用单例模块,成为了我们技术框架体系的一部分,极大地提高了业务项目的开发和维护效率。 +3. **版本不一致:**在业务项目不断增多之后,轮子版本不一致性也越来越明显。什么是版本不一致?举个例子。我们有个轮子叫做`httpClient`,总共发布了`10`来个版本;我们总共有`100`多个`Golang`项目,几乎每个版本都在使用。我们提交了一个`bug fix`,却难以让所有项目都能更新。对其他的轮子也是类似的情况,况且我们也有数十个各种轮子,被各个项目独立使用中。 + +经过反思总结,总结了以下几点: + +1. 团队需要一个统一的技术框架,而不是东拼西凑的一堆单仓库包。 +2. 我们只需要维护一个框架的版本,而不是维护数十个单仓库包的版本。 +3. 框架的组件必须模块化、低耦合设计,保证内部组件也可以独立引用。 +4. 核心组件严格禁止单仓库包设计,并且必须由框架统一维护。 + +最终的抉择 +----- + +走过这么多弯路之后,我们决心建立一套成体系的`Golang`开发框架。除了要求团队能够快速学习,维护成本低,并且我们最主要的诉求,是核心组件不能是半成品,框架必须是上过大规模生产验证的,稳定和成熟的。随着,我们重新对行业中流行的技术框架做了技术评估,包括上面说的那些框架。原本的初衷是想将内部的各个轮子统一做一个成体系的框架,在开源项目中找一些有价值的参考。 + +后来找到了`goframe`,仔细评估和学习了框架设计,发现框架设计思想和我们的经验总结如出一则! + +这里不得不提一件尴尬的事情。其实最开始转`Golang`之前(2019年中旬)也做过一些调研,那时`goframe`版本还不高,并且我们负责评估的团队成员有一种先入为主的思想,看到模块和文档这么多,感觉应该挺复杂,性能应该不高,于是没怎么看就PASS。后来选择一些看起来简单的开源轮子自己做了些二次封装。 + +这次经过一段时间的仔细调研和源码学习,得出一个结论,`goframe`框架的框架架构、模块化和工程化设计思想非常棒,执行效率很高,模块不仅丰富,而且质量之高,令人惊叹至极!相比较我们之前写的那些半成品轮子,简直就是小巫见大巫。**团队踩了一年多的坑,才发现团队确实需要一个统一的技术框架而不是一堆不成体系的轮子,其实人家早已给了一条明光大道,并且一直在前面默默努力。** + +经过团队内部的调研和讨论,我们决定使用`goframe`逐步重构我们的业务项目。由于`goframe`是模块化设计的,因此我们也可以对一些模块做必要的替换。重构过程比较顺利,基础技术框架的重构并不会对业务逻辑造成什么影响,反而通过`goframe`的工程化思想和很棒的开发工具链,在统一技术框架后,极大地提高了项目的开发和维护效率,使得团队可以专心于业务开发,部门也陆续有了更多的产出。目前我们已经有大部门业务项目专向了`goframe`。平台每日流量千万级别。 + +最后,感谢开源作者们的默默贡献!我们也在努力推动团队秉着来源社区,回馈社区的思想,未来也会更多参与社区贡献。 + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" new file mode 100644 index 00000000000..ceb689b91ea Binary files /dev/null and "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/golang-framework-choose.jpeg" differ diff --git "a/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" new file mode 100644 index 00000000000..9780aea8b4d --- /dev/null +++ "b/versioned_docs/version-2.8.x/community/\347\244\276\345\214\272\346\212\225\347\250\277/\347\244\276\345\214\272\346\212\225\347\250\277.md" @@ -0,0 +1,13 @@ +--- +slug: '/articles' +title: '社区投稿' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,开源,社区投稿,投稿,社区,投稿,开源项目] +description: 'GoFrame 是一个开源、免费的框架,欢迎所有开发者为其代码和文档贡献力量。通过GitHub主库,您可以参与代码贡献,加入我们开发的史册。此外,您还能通过完善官网文档、撰写博客或录制视频的方式帮助推广GoFrame框架。' +--- + + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/course/QQ_1731756142305.png b/versioned_docs/version-2.8.x/course/QQ_1731756142305.png new file mode 100644 index 00000000000..0e499964019 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/QQ_1731756142305.png differ diff --git a/versioned_docs/version-2.8.x/course/proxima-book/assets/architecture.png b/versioned_docs/version-2.8.x/course/proxima-book/assets/architecture.png new file mode 100644 index 00000000000..768f3655437 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/proxima-book/assets/architecture.png differ diff --git a/versioned_docs/version-2.8.x/course/proxima-book/assets/coffee.jpg b/versioned_docs/version-2.8.x/course/proxima-book/assets/coffee.jpg new file mode 100644 index 00000000000..8113f4f55f1 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/proxima-book/assets/coffee.jpg differ diff --git a/versioned_docs/version-2.8.x/course/proxima-book/proxima-book.md b/versioned_docs/version-2.8.x/course/proxima-book/proxima-book.md new file mode 100644 index 00000000000..b298a93b53b --- /dev/null +++ b/versioned_docs/version-2.8.x/course/proxima-book/proxima-book.md @@ -0,0 +1,37 @@ +--- +slug: '/course/proxima-book' +title: '微服务实战教程-比邻英语本' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame,GoFrame框架,比邻英语本,微服务,gRPC,etcd,服务注册,GoFrame微服务,Golang微服务,Golang教程,编程技巧,项目开发,程序员入门,技术栈,软件开发,计算机科学] +description: '本书通过GoFrame框架,以比邻英语本项目实战为例,帮助读者快速掌握GoFrame微服务开发。适合已经对`GoFrame`小有所成,希望进一步掌握微服务开发的朋友。' +--- + +## 本书简介 +--- +**比邻英语本**是 `GoFrame` 中级实战教程。与初级教程[星辰英语本](../starbook/starbook.md)不同,**微服务**开发是本书的主旋律。 + +## 编写本书的动机 +--- +网上的教程总是哐当一下,甩出各类技术名词、成吨的架构分层,或者不明觉厉的一大段说明。看的人眼花缭乱,往往花了很久的时间,读了一大堆文章,也没能写出一行代码。 + +所以呐,耳闻之不如目见之,目见之不如足践之。实际操作才是掌握微服务的良药,本书将从`GoFrame`框架出发,开发出一个实际的微服务项目,揭开它神秘的面纱。最终您会发现,微服务的开发是一件很简单的事情。当然,这并不是说微服务很简单,只是它的复杂度不是来源于开发,而是微服务治理。 + +以实际项目为导向,分享更多专业、实用的编程技巧和经验,让您学有所成是作者的期望! + +## 目标读者 +--- +已经对`GoFrame`小有所成,希望进一步掌握微服务开发的朋友。 + +## 联系作者 +--- +在编写本书的过程中,不可避免的会有一些错误或者不足之处,如果您有任何问题或者建议,可以在下方留言,也可以联系我,我会尽快回复您! +- 邮箱: `tyyn1022@gmail.com` `tyyn1022@163.com` +- 网站: [https://oldme.net](https://oldme.net) +- 微信: `NobodyIsRight` 来者请备注来意哦! + +## 遇到问题 +--- +在开发中,遇到各种问题都是正常的。如何解决问题才是关键,在遇到问题时,请先尝试自行解决。查阅`GoFrame`文档,使用搜索引擎都是不错的解决问题的方式。如果实在无法解决,可以联系我,我会尽力帮助您解决问题。 + +import DocCardList from '@theme/DocCardList'; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" new file mode 100644 index 00000000000..63f9f71caed --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\345\206\231\344\275\234\347\272\246\345\256\232.md" @@ -0,0 +1,37 @@ +--- +title: '1.1.写作约定' +hide_title: true +slug: '/course/proxima-book/about-convention' +keywords: [GoFrame, proxima-book, writing convention, code examples, command line usage, code simplification, microservices development] +description: "本章介绍了 GoFrame 微服务教程的写作约定,包括代码示例的简化原则、命令行使用规范和代码省略说明,帮助读者更好地理解教程内容。" +--- + +## 代码从简 +--- +能看到本书,就说明您已经是代码江湖上颇具水平的老鸟了。所以,我也会减少不必要的啰嗦,跳过不必要的代码细节,**只着重展示微服务的开发流程与特性。** + +## 命令行 +--- +本书会在一些地方使用命令行,我将使用 `$` 符号作为提示符,您不需要输入这个符号。比如,如果我写了 `$ echo "Hello, GoFrame!" `,您只需要输入 `echo "Hello, GoFrame!" ` 即可。 + +```bash +$ echo "Hello, GoFrame!" +Hello, GoFrame! +``` + +## 代码省略 +--- +为了保证篇幅的整洁,在不必要的竖版代码中,我会使用`...`来省略代码。 + +```go +package main + +import "fmt" + +... + +func main() { + fmt.Println("Hello GoFrame") +} + +... diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..c7f51d70bd9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.\346\236\266\346\236\204\344\273\213\347\273\215.md" @@ -0,0 +1,34 @@ +--- +title: '1.2.架构介绍' +hide_title: true +slug: '/course/proxima-book/about-arch' +keywords: [GoFrame, microservices, architecture design, API gateway, user service, word service, gRPC, HTTP, load balancing, authentication] +description: "介绍比邻英语本项目的微服务架构设计,包括用户服务和单词服务的功能拆分,以及API网关的角色和功能,详细说明了微服务间的通信方式和网关的核心职责。" +--- + + +**比邻英语本**是一个帮助用户学习英语单词的轻量级软件,它包含以下业务功能: +- 用户注册; +- 用户登录; +- 用户信息查询; +- 单词的增删改查。 + +我们将这些服务简单地拆分,将同类功能放在一起,演化出两个微服务: +- 用户服务:处理用户注册、登录、信息查询等功能; +- 单词服务:提供单词相关的功能,比如增删改查。 + +微服务不直接对外提供服务,而是统一交由网关处理。网关作为一个`Web`服务,它不直接提供具体的业务功能,而是负责接收请求,转发到各微服务,最后拼接数据返回,以此来完成业务功能。 + +网关的功能不仅限于协议转换,还包括负载均衡、认证授权、日志记录、监控和限流等。微服务之间通常使用`HTTP`或`gRPC`进行通讯。 + +本书微服务采用`gRPC`协议。 + +![](../assets/architecture.png) + +## 代码仓库 +--- +微服务将单体服务拆开,代码也自然分离。它的代码仓库,有两种常见的管理方式: +- **Multirepo:** 多仓库模式,每个微服务都有独立的仓库。优点是每个仓库相对较小,易于管理。缺点是需要额外的工具,流程协调各个服务之间的依赖和版本。 +- **Monorepo:** 单一仓库模式,所有微服务的代码都存放在一个仓库中。优点是可以统一管理版本和依赖,缺点是仓库可能会变得庞大,管理复杂度增加。 + +我们的项目采用`Monorepo`模式。`Multirepo`模式下一个服务一个目录,无需多言。 diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..9a0b56936d2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,98 @@ +--- +title: '1.3.环境准备' +hide_title: true +slug: '/course/proxima-book/about-prepare' +keywords: [GoFrame, gRPC, Protocol Buffers, development environment, installation guide, etcd, microservices tools, Go installation] +description: "详细介绍了开发 GoFrame 微服务项目所需的环境准备工作,包括 Go 语言环境配置、GoFrame 框架安装、gRPC 工具链配置以及相关依赖组件的安装说明。" +--- + +如果您的版本和我不一致,也无需担心,他们基本是共通的。 + +## GoFrame +--- +`Golang`和`GoFrame`的安装方式不再赘述。这里使用的版本信息如下: +- `go version go1.23.4 windows/amd64` +- `goframe v2.8.2` + +## gRPC +--- +`gRPC`是一个由 `Google` 开发的远程过程调用(RPC)框架,基于`HTTP/2`。它使用 `Protobuf` 作为默认的序列化格式。 + +`Go`语言通过`gRPC-go`插件提供`gRPC`功能。执行命令安装插件: +```bash +$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest +go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest +``` + +### gRPC测试工具 +`gRPC`接口开发完成后,需要一些测试工具,来检测它是否正常运行。比较流行的测试工具有`Postman`、`Apifox`、`Apipost`等,它们大同小异,您可以根据自己的喜好选择一个。 + +本书统一使用`json`展示测试结果,例如: +```json +grpc 127.0.0.1:32001.account.v1.Account.UserRegister +{ +  "username": "oldme", +  "password": "123456", +  "email": "tyyn1022@gmail.com" +} +{ +  "id": 1 +} +``` + +它们分别代表请求地址,请求参数,响应参数。 + +## Protobuf +--- + `Protobuf` 是 `Google` 设计的数据序列化格式,用于结构化数据的序列化和反序列化。使用 `.proto` 文件定义消息结构,然后通过编译器生成相应语言的代码。 + +根据不同的操作系统,在 [Protobuf Release](https://github.com/protocolbuffers/protobuf/releases) 下载对应的文件安装。如果是 `MacOS` 环境,可以使用 `brew` 工具安装依赖: + +```bash +$ brew install grpc protoc-gen-go protoc-gen-go-grpc +``` + +检测是否安装成功。 +```bash +$ protoc --version +libprotoc 26.1 +``` + +## etcd +--- +etcd 是一个分布式键值存储系统,常用于分布式系统中的服务发现。安装它有多种方式,这里贴出 +`docker-compose.yaml`文件用作参考。 + +```yaml +version: "3.7" + +services: + etcd: + image: "bitnami/etcd:3.5" + container_name: "etcd" + restart: "always" + ports: + - 2379:2379 + environment: + - TZ=Asia/Shanghai + - ALLOW_NONE_AUTHENTICATION=yes + - ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379 +``` + +如果安装成功,在浏览器访问 [http://IP:2379/version](http://IP:2379/version),会出现以下信息: +```json +{"etcdserver": "3.5.17","etcdcluster": "3.5.0"} +``` + +如果您想更酷一些,安装个`etcd`集群或者学习一下`etcd`的基础使用,可以参考[此文](https://oldme.net/article/32)。 + +## 数据库 +--- +`MySQL`的安装不必多说,使用其他数据库亦可。 + +需要注意的是,微服务架构下,每个单独的服务都应当有自己的数据库。所以,我们需要建立两个数据库,名称分别是`user`和`word`。 + +```sql +CREATE DATABASE `user`; +CREATE DATABASE `word`; +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 00000000000..0d646fdf1d8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\351\241\271\347\233\256\345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,70 @@ +--- +title: '1.4.项目初始化' +hide_title: true +slug: '/course/proxima-book/about-init' +keywords: [GoFrame, project initialization, Monorepo, project structure, dependency management, go.mod, microservices setup] +description: "详细说明了如何使用 GoFrame CLI 工具初始化微服务项目,包括创建 Monorepo 仓库、配置依赖版本、设置项目结构等关键步骤。" +--- + +## 初始化仓库 +--- +执行命令,初始化一个名为`proxima`的`Monorepo`仓库: + +```bash +$ gf init proxima -m +``` + +修改`go`语言的最低依赖版本为当前环境,确保大于`GoFrame`最低版本要求即可。 + +*go.mod* +```text +module proxima + +go 1.23.4 +``` + +`GoFrame`升级到最新版本: +```bash +$ cd proxima +gf up +``` + +删除不必要的示例文件: +```bash +$ rm -rf app/* +``` + +完成后的项目结构: +```text +app +hack + hack.mk + hack-cli.mk +utility +go.mod +go.sum +``` + +在`Monorepo`仓库模式下,根目录只提供对项目依赖管理,不存在`main.go`文件。 + +`app`目录保存微服务各自的代码文件,例如`app/user/main.go`,`app/word/main.go`。 + +## 安装微服务组件 +--- +安装`grpcx`组件,让`GoFrame`支持微服务开发。 +```bash +$ go get -u github.com/gogf/gf/contrib/rpc/grpcx/v2 +``` + +## 安装数据库驱动 +--- +和单体服务一样,也需要安装对应的数据库驱动,这里演示的是`MySQL`。 +```bash +$ go get -u github.com/gogf/gf/contrib/drivers/mysql/v2 +``` + +## 安装etcd组件 +--- +安装`etcd`组件,提供服务注册功能。 +```bash +$ go get -u github.com/gogf/gf/contrib/registry/etcd/v2 diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" new file mode 100644 index 00000000000..5dfcfcd2ec6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" @@ -0,0 +1,34 @@ +--- +title: '1.5.本书源码' +hide_title: true +slug: '/course/proxima-book/about-source' +keywords: [GoFrame, source code, GitHub repository, MIT license, open source, proxima project] +description: "提供了本教程项目的源代码获取方式,包括 GitHub 仓库地址和 MIT 开源许可证的详细说明。" +--- + +本书的源码开源在 [https://github.com/oldme-git/proxima](https://github.com/oldme-git/proxima)。 + +开源许可证基于`MIT`协议: +```text +MIT License + +Copyright (c) 2024 oldme + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..c3f772b9a93 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" @@ -0,0 +1,20 @@ +--- +title: '第一章-基础信息' +hide_title: true +sidebar_position: 1 +slug: '/course/proxima-book/about' +--- + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..4ad194f14a2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,87 @@ +--- +title: '3.1.前置准备' +hide_title: true +slug: '/course/proxima-book/word-prepare' +keywords: [GoFrame, word service setup, microservice initialization, database configuration, project structure, service preparation] +description: "详细介绍了单词服务的初始化过程,包括使用 GoFrame CLI 创建服务、配置数据库连接、设置项目结构等基础准备工作。" +--- + +搞定了第一个微服务后,第二个微服务开发起来自然轻车熟路。 + +## 代码初始化 +--- +执行以下命令,建立名为`word`的服务,同样保存在`app`目录下。 + +```bash +$ gf init app/word -a +initializing... +initialization done! +you can now run "cd app/word && gf run main.go" to start your journey, enjoy! +``` + +如法炮制,删除下列文件,留下一个空白的环境。 +```text +app/word/api/* +app/word/internal/controller/* +app/word/internal/cmd/cmd.go +``` + +进入微服务仓库,开始正式开发。 +```bash +$ cd app/word +``` + +## 生成数据模型 +--- +### 建立数据表 +在`word`数据库下,执行`SQL`语句,建立保存用户数据的表: +```sql +CREATE TABLE `words` ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + uid INT UNSIGNED NOT NULL, + word VARCHAR ( 255 ) NOT NULL, + definition TEXT, + example_sentence TEXT, + chinese_translation VARCHAR ( 255 ), + pronunciation VARCHAR ( 255 ), + created_at DATETIME, + updated_at DATETIME +); +``` + +### 生成dao模型 +*app/user/hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" + descriptionTag: true +``` + +```bash +$ gf gen dao +generated: D:\project\proxima\app\word\internal\dao\words.go +generated: D:\project\proxima\app\word\internal\dao\internal\words.go +generated: D:\project\proxima\app\word\internal\model\do\words.go +generated: D:\project\proxima\app\word\internal\model\entity\words.go +done! +``` + +### 生成pbentity模型 +*app/user/hack/config.yaml* +```bash +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" + descriptionTag: true + + pbentity: + - link: "mysql:root:12345678@tcp(srv.com:3306)/word" +``` + +```bash +$ gf gen pbentity +generated: D:\project\proxima\app\word\manifest\protobuf\pbentity\words.proto +done! diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" new file mode 100644 index 00000000000..221a1f96384 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" @@ -0,0 +1,39 @@ +--- +title: 3.2.业务逻辑 +hide_title: true +slug: /course/proxima-book/word-logic +keywords: [GoFrame, business logic, word management, CRUD operations, microservices logic, vocabulary service] +description: "介绍了单词服务的核心业务逻辑实现,包括单词的创建、查询等基本功能的代码实现。" +--- + +和前文一样,这里简单做个样子 + +*app/word/internal/logic/words/words.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/os/gtime" + "proxima/app/word/internal/model/entity" +) + +func Create(ctx context.Context) (id uint, err error) { + return 1, nil +} + +func Get(ctx context.Context) (word *entity.Words, err error) { + return &entity.Words{ + Id: 1, + Uid: 1, + Word: "hello", + Definition: "used as a greeting when you meet somebody.", + ExampleSentence: "Hello, I am oldme!", + ChineseTranslation: "你好", + Pronunciation: "həˈləʊ", + CreatedAt: gtime.New("2024-12-05 22:00:00"), + UpdatedAt: gtime.New("2024-12-05 22:00:00"), + }, nil +} +``` diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..24e2fc3ff5d --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" @@ -0,0 +1,42 @@ +--- +title: '3.3.协议文件' +hide_title: true +slug: '/course/proxima-book/word-protocol' +keywords: [GoFrame, Protocol Buffers, gRPC, API definition, word service protocol, microservices communication] +description: "介绍了单词服务的 Protocol Buffers 协议文件定义,包括创建和获取单词等接口的协议设计和实现。" +--- + +这里我就从简了,省略了更新,删除等操作,只写了`Create`和`Get`,用作示例。 + +*app/word/manifest/protobuf/words/v1/words.proto* +```go +syntax = "proto3"; + +package words.v1; + +option go_package = "proxima/app/word/api/words/v1"; + +import "pbentity/words.proto"; + +service Words{ + rpc Create(CreateReq) returns (CreateRes) {} + rpc Get(GetReq) returns (GetRes) {} +} + +message CreateReq { + uint32 uid = 1; // v:required + string word = 2; // v:required + string definition = 3; // v:required +} + +message CreateRes { + uint32 id = 1; +} + +message GetReq { + uint32 id = 1; // v:required +} + +message GetRes { + pbentity.Words words = 1; +} \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..85678b5656f --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.4.\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,65 @@ +--- +title: "3.4.控制器" +hide_title: true +slug: '/course/proxima-book/word-controller' +keywords: [GoFrame, controller generation, gRPC controller, word service controller, protobuf generation, service implementation] +description: "详细介绍了如何生成和实现单词服务的控制器,包括使用 GoFrame 的代码生成工具和实现具体的业务逻辑。" +--- + +执行命令,生成控制器: + +```bash +$ gf gen pb +``` + +编写逻辑,调用单词微服务: + +*app/word/internal/controller/words/words.go* +```go +package words + +import ( + "context" + + "proxima/app/word/api/pbentity" + v1 "proxima/app/word/api/words/v1" + "proxima/app/word/internal/logic/words" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" +) + +type Controller struct { + v1.UnimplementedWordsServer +} + +func Register(s *grpcx.GrpcServer) { + v1.RegisterWordsServer(s.Server, &Controller{}) +} + +func (*Controller) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + id, err := words.Create(ctx) + if err != nil { + return nil, err + } + return &v1.CreateRes{Id: uint32(id)}, nil +} + +func (*Controller) Get(ctx context.Context, req *v1.GetReq) (res *v1.GetRes, err error) { + data, err := words.Get(ctx) + if err != nil { + return nil, err + } + return &v1.GetRes{ + Words: &pbentity.Words{ + Id: uint32(data.Id), + Uid: uint32(data.Uid), + Word: data.Word, + Definition: data.Definition, + ExampleSentence: data.ExampleSentence, + ChineseTranslation: data.ChineseTranslation, + Pronunciation: data.Pronunciation, + CreatedAt: timestamppb.New(data.CreatedAt.Time), + UpdatedAt: timestamppb.New(data.CreatedAt.Time), + }, + }, nil +} +``` diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..8ddb6095e2f --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,132 @@ +--- +title: '3.5.启动运行' +hide_title: true +slug: '/course/proxima-book/word-run' +keywords: [GoFrame, gRPC service, word service startup, microservices deployment, service registration, etcd integration] +description: "详细说明了如何启动和运行单词微服务,包括配置服务注册、gRPC 服务设置和健康检查等关键步骤。" +--- + +## cmd引入控制器 +--- +*app/word/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/os/gcmd" + "google.golang.org/grpc" + "proxima/app/word/internal/controller/words" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "word grpc service", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + c := grpcx.Server.NewConfig() + c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., + ) + s := grpcx.Server.New(c) + words.Register(s) + s.Run() + return nil + }, + } +) +``` + +## 主入口文件 +--- +在入库文件内引入数据库驱动和`cmd`。 + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/word/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +## 配置文件 +--- +*app/user/manifest/config/config.yaml* +```go +grpc: + name: "word" + address: ":32002" + +database: + default: + link: "mysql:root:12345678@tcp(srv.com:3306)/word" + debug: true +``` + +## 启动运行 +--- +确保依赖正常,运行单词微服务。 + +```go +$ cd app/word +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 2416 +2024-12-09 15:10:40.546 [DEBU] {18cc6c8aa5700f18bf2deb5e3439664a} set default registry using file registry as no custom registry set, path: C:\Users\half\AppData\Local\Temp\gsvc +2024-12-09 15:10:40.566 [DEBU] {18cc6c8aa5700f18bf2deb5e3439664a} service register: &{Head: Deployment: Namespace: Name:word Version: Endpoints:192.168.10.98:32002 Metadata:map[protocol:grpc]} +2024-12-09 15:10:40.567 [INFO] {18cc6c8aa5700f18bf2deb5e3439664a} pid[2416]: grpc server started listening on [:32002] +``` + +至此,**比邻英语本**的第二个微服务开发完成。 + +## 测试结果 +--- +```json +grpc 127.0.0.1:32002.words.v1.Words.Create +{ +  "uid": 1, +  "word": "hello", +  "definition": "used as a greeting when you meet somebody." +} +{ +  "id": 1 +} + +grpc 127.0.0.1:32002.words.v1.Words.Get +{ +  "id": 1 +} +{ +  "words": { +  "Id": 1, +  "Uid": 1, +  "Word": "hello", +  "Definition": "used as a greeting when you meet somebody.", +  "ExampleSentence": "Hello, I am oldme!", +  "ChineseTranslation": "你好", +  "Pronunciation": "həˈləʊ", +  "CreatedAt": { +  "seconds": "1733407200", +  "nanos": 0 +  }, +  "UpdatedAt": { +  "seconds": "1733407200", +  "nanos": 0 +  } +  } +} diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..a21e48efd58 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/3.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" @@ -0,0 +1,55 @@ +--- +title: '3.6.服务注册' +hide_title: true +slug: '/course/proxima-book/word-etcd-register' +keywords: [GoFrame, etcd, service registration, word service discovery, microservices registry, configuration] +description: "介绍了如何将单词微服务注册到 etcd 服务注册中心,包括配置文件设置和注册逻辑的实现。" +--- + +添加配置文件: + +*app/word/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +在入口文件添加注册逻辑: + +*app/word/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/word/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +重新运行项目,再次进入`etcd`容器,执行命令查看: +```bash +$ etcdctl get "" --prefix --keys-only +/service/default/default/user/latest/{IP}:32001 +/service/default/default/word/latest/{IP}:32002 +``` + +可以看见,我们的两个微服务都已经成功注册。接下来,运行网关,即可调用它们。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..fd11758015b --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241/\347\254\254\344\270\211\347\253\240-\345\215\225\350\257\215\346\234\215\345\212\241.md" @@ -0,0 +1,12 @@ +--- +title: '第三章-单词服务' +hide_title: true +sidebar_position: 3 +slug: '/course/proxima-book/word' +keywords: [GoFrame, word service, vocabulary management, microservices, CRUD operations, English learning] +description: "本章详细介绍了基于 GoFrame 框架的单词服务实现,包括单词的增删改查等核心功能,以及与其他微服务的集成。" +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..2ed0814d61a --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,94 @@ +--- +title: '2.1.前置准备' +hide_title: true +slug: '/course/proxima-book/user-overview' +keywords: [GoFrame, microservice setup, user service initialization, database configuration, project structure, service preparation] +description: "本节介绍了用户服务的前置准备工作,包括使用 GoFrame CLI 初始化服务、配置数据库连接、设置项目结构等基础内容。" +--- + +微服务开发的大部分内容,都和单体服务一致,甚至比单体服务简单许多。本章是一些基础准备,想必各位都驾轻就熟。 + +## 代码初始化 +--- +`GoFrame`为我们准备好了初始化微服务仓库的命令。执行以下命令,建立名为`user`的服务,并保存在`app`目录下。 + +```bash +$ gf init app/user -a +initializing... +initialization done! +you can now run "cd app/user && gf run main.go" to start your journey, enjoy! +``` + +成功后,会在`app`下建立一个微服务。其实和单体服务没什么两样,就是缺少了`go.mod`和`go.sum`文件。 + +将下列文件全部删除,留下一个空白的环境。 +```text +app/user/api/* +app/user/internal/controller/* +app/user/internal/cmd/cmd.go +``` + +完成后,进入微服务仓库,开始正式开发。 +```bash +$ cd app/user +``` + +## 生成数据模型 +--- +### 建立数据表 +在`user`数据库下,执行`SQL`语句,建立保存用户数据的表: +```sql +CREATE TABLE `users` ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + password CHAR(32) NOT NULL, + email VARCHAR(100), + created_at DATETIME, + updated_at DATETIME +); +``` + +### 生成dao模型 +*app/user/hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" + descriptionTag: true +``` + +```bash +$ gf gen dao +generated: D:\project\proxima\app\user\internal\dao\users.go +generated: D:\project\proxima\app\user\internal\dao\internal\users.go +generated: D:\project\proxima\app\user\internal\model\do\users.go +generated: D:\project\proxima\app\user\internal\model\entity\users.go +done! +``` + +> 注意,应该在微服务仓库下执行`gf gen dao`命令,也就是`app/user`目录下,这里不要弄错了。后续其他的相关操作也类似。 + +### 生成pbentity模型 +*app/user/hack/config.yaml* +```bash +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" + descriptionTag: true + + pbentity: + - link: "mysql:root:12345678@tcp(srv.com:3306)/user" +``` + +```bash +$ gf gen pbentity +generated: D:\project\proxima\app\user\manifest\protobuf\pbentity\users.proto +done! +``` + +### 与 `gen dao` 的差别 + +- `gen dao` 生成的数据是`go`文件,主要在微服务内部使用,例如`ORM`操作; +- `gen pbentity`生成的数据是`proto`文件,主要用作`gRPC`微服务之间的通讯。 diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" new file mode 100644 index 00000000000..2bf124fa3a2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.2.\344\270\232\345\212\241\351\200\273\350\276\221.md" @@ -0,0 +1,41 @@ +--- +title: '2.2.业务逻辑' +hide_title: true +slug: '/course/proxima-book/user-logic' +keywords: [GoFrame, business logic, user registration, account management, microservices logic, user service implementation] +description: "详细说明了用户服务中的业务逻辑实现,包括用户注册等核心功能的代码实现和最佳实践。" +--- + +微服务的业务逻辑存放在`*/internal/logic`下,和单体业务一样。大家都是久经沙场的老将了,作者也不献丑,就简单做个样子。 + +*app/user/internal/logic/account/account.go* +```go +package account + +import ( + "context" + + "github.com/gogf/gf/v2/os/gtime" + "proxima/app/user/internal/dao" + "proxima/app/user/internal/model/entity" +) + +func Register(ctx context.Context) (id int, err error) { + return 1, nil +} + +func Login(ctx context.Context) (token string, err error) { + return "I am token", nil +} + +// Info get user info +func Info(ctx context.Context, token string) (user *entity.Users, err error) { + return &entity.Users{ + Id: 1, + Username: "oldme", + Password: "123456", + Email: "tyyn1022@gmail.com", + CreatedAt: gtime.New("2024-12-05 22:00:00"), + UpdatedAt: gtime.New("2024-12-05 22:00:00"), + }, nil +} \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..61412931400 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.3.\345\215\217\350\256\256\346\226\207\344\273\266.md" @@ -0,0 +1,109 @@ +--- +title: '2.3.协议文件' +hide_title: true +slug: '/course/proxima-book/user-protocol' +keywords: [GoFrame, Protocol Buffers, gRPC, API definition, microservices communication, proto files, user service protocol] +description: "介绍了用户服务中的 Protocol Buffers 协议文件定义,包括用户注册、登录等接口的协议设计,以及 gRPC 服务定义的最佳实践。" +--- + +协议文件指的是`*.proto`文件,`proto`是`gRPC`协议通讯的标准,可以类比为`json`和`HTTP`。但是千万不要理解`proto`就是`json`,他们还是有一定区别的:`proto`同时定义“接口”信息和响应参数,请求参数,而`json`就单纯的保存数据。 + +`proto`文件统一存放在`manifest/protobuf`下,和普通的`HTTP`服务一样,使用目录层级来管理接口版本。 + +## 用户注册 +--- +创建一个名为`account`的目录,管理用户账号相关业务。 + +*app/user/manifest/protobuf/account/v1/account.proto* +```go +syntax = "proto3"; + +package account.v1; + +option go_package = "proxima/app/user/api/account/v1"; + +service Account{ + rpc UserRegister(UserRegisterReq) returns (UserRegisterRes) {} +} + +message UserRegisterReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 + string email = 3; // v:required|email +} + +message UserRegisterRes { + int32 id = 1; +} +``` + +简单的介绍一下`proto`语法: +- **syntax** 规定本文件语法版本; +- **package** 定义的是服务命名空间,可以理解为包名; +- **option** 设定编译选项,`go_package`指定生成的`Go`代码所属的包名。*在`GoFrame`中固定格式是`项目名 + app + 微服务名称 + api + 模块名 + v1`*; +- **service** 定义远程调用方法,一般是`RPC`,规定其请求和响应参数; +- **message** 定义数据结构,`string`是数据类型,`username`是字段名,赋值号后面的递增数字是字段编号。*最后面的注释是框架提供的参数校检,使用方式普通的`HTTP`接口一致*。 + +我们的文件定义了以下内容: +- 使用`proto3`语法版本的定义; +- 定义了包名为`account.v1`; +- 设置了`Go`代码生成的包路径选项`go_package`为`proxima/app/user/api/account/v1`; +- 定义了一个`Account`服务,包含一个`RPC`方法`UserRegister`,它接受`UserRegisterReq`消息并返回`UserRegisterRes`消息; +- 定义了一个消息类型`UserRegisterReq`,包含三个字段: + - `username` (字符串类型,编号为1) + - `password` (字符串类型,编号为2) + - `email` (字符串类型,编号为3) +- 定义了一个消息类型`UserRegisterRes`,包含一个字段: + - `id` (整型,编号为1) + +## 用户登录/查询 +--- +照葫芦画瓢,我们继续定义用户登录和查询接口。最后的文件内容如下: + +*app/user/manifest/protobuf/account/v1/account.proto* +```go +syntax = "proto3"; + +package account.v1; + +option go_package = "proxima/app/user/api/account/v1"; + +import "pbentity/users.proto"; + +service Account{ + rpc UserRegister(UserRegisterReq) returns (UserRegisterRes) {} + rpc UserLogin(UserLoginReq) returns (UserLoginRes) {} + rpc UserInfo(UserInfoReq) returns (UserInfoRes) {} +} + +message UserRegisterReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 + string email = 3; // v:required|email +} + +message UserRegisterRes { + int32 id = 1; +} + +message UserLoginReq { + string username = 1; // v:required|min-length:2 + string password = 2; // v:required|min-length:6 +} + +message UserLoginRes { + string token = 1; +} + +message UserInfoReq { + string token = 1; // v:required +} + +message UserInfoRes { + pbentity.Users user = 1; +} +``` + +这里多了两个新的语法: +- `import "pbentity/users.proto";`,代表引入了其他`proto`文件。引入的这个文件是`gf gen pbentity`生成的; +- `pbentity.Users user`调用引入的数据模型,和`go`的结构体几乎一致。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..5c47e0c6123 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.4.\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,74 @@ +--- +title: "2.4.控制器" +hide_title: true +slug: '/course/proxima-book/user-controller' +keywords: [GoFrame, controller generation, gRPC controller, protobuf generation, microservices controller, service implementation] +description: "详细介绍了如何使用 GoFrame 的代码生成工具生成 gRPC 控制器,以及如何实现用户服务的各项功能接口。" +--- + +`HTTP`服务的控制器由`gf gen ctrl`生成,微服务也有控制器,由`gf gen pb`生成。 + +```bash +$ gf gen pb +``` + +`gen pb`命令需要各类依赖都正常。如果执行成功,会生成若干`go`文件。我们只需要关注控制器文件即可,其他由框架维护。后续的开发过程,和`HTTP`服务一样——调用`logic`。 + +*app/user/internal/controller/account/account.go* +```go +package account + +import ( + "context" + + "google.golang.org/protobuf/types/known/timestamppb" + v1 "proxima/app/user/api/account/v1" + "proxima/app/user/api/pbentity" + "proxima/app/user/internal/logic/account" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" +) + +type Controller struct { + v1.UnimplementedAccountServer +} + +func Register(s *grpcx.GrpcServer) { + v1.RegisterAccountServer(s.Server, &Controller{}) +} + +func (*Controller) UserRegister(ctx context.Context, req *v1.UserRegisterReq) (res *v1.UserRegisterRes, err error) { + id, err := account.Register(ctx) + if err != nil { + return nil, err + } + return &v1.UserRegisterRes{ + Id: int32(id), + }, nil +} + +func (*Controller) UserLogin(ctx context.Context, req *v1.UserLoginReq) (res *v1.UserLoginRes, err error) { + token, err := account.Login(ctx) + if err != nil { + return nil, err + } + return &v1.UserLoginRes{ + Token: token, + }, nil +} + +func (*Controller) UserInfo(ctx context.Context, req *v1.UserInfoReq) (res *v1.UserInfoRes, err error) { + data, err := account.Info(ctx, req.Token) + if err != nil { + return nil, err + } + return &v1.UserInfoRes{ + User: &pbentity.Users{ + Id: uint32(data.Id), + Username: data.Username, + Password: data.Password, + Email: data.Email, + CreatedAt: timestamppb.New(data.CreatedAt.Time), + UpdatedAt: timestamppb.New(data.UpdatedAt.Time), + }, + }, nil +} diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..f2b997ea19b --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.5.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,153 @@ +--- +title: '2.5.启动运行' +hide_title: true +slug: '/course/proxima-book/user-run' +keywords: [GoFrame, gRPC service, service startup, microservices deployment, service registration, etcd integration] +description: "详细说明了如何启动和运行用户微服务,包括服务注册、gRPC 服务配置、与 etcd 的集成以及服务健康检查等关键步骤。" +--- + +## cmd引入控制器 +--- +和单体服务一样,微服务也需要在`cmd`中引入。不同的是,启动服务由`HTTP`变成了`gRPC`。 + +*app/user/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/os/gcmd" + "google.golang.org/grpc" + "proxima/app/user/internal/controller/account" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "user grpc service", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + c := grpcx.Server.NewConfig() + c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., + ) + s := grpcx.Server.New(c) + account.Register(s) + s.Run() + return nil + }, + } +) +``` + +## 主入口文件 +--- +在入库文件内引入数据库驱动和`cmd`。 + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/user/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +## 配置文件 +--- +*app/user/manifest/config/config.yaml* +```go +grpc: + name: "user" + address: ":32001" + +database: + default: + link: "mysql:root:12345678@tcp(srv.com:3306)/user" + debug: true +``` + +`gprc`字段定义了两个字段,微服务名称和监听端口。微服务名称会用作服务注册,监听端口不必多言。这两个是必要的,其他的配置见[配置模板](../../../docs/微服务开发/服务端配置.md)。 + +## 启动运行 +--- +切换到根目录,确保所有的依赖正常。 + +```bash +$ cd ../../ +go mod tidy +``` + +回到微服务仓库,正式运行用户微服务。 + +```go +$ cd app/user +gf run .\main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 15480 +2024-12-06 15:02:01.246 [DEBU] {d8e6fef56e840e1815d0325bc73eda8f} set default registry using file registry as no custom registry set, path: C:\Users\half\AppData\Local\Temp\gsvc +2024-12-06 15:02:01.269 [DEBU] {d8e6fef56e840e1815d0325bc73eda8f} service register: &{Head: Deployment: Namespace: Name:user Version: Endpoints:192.168.10.91:32001 Metadata:map[protocol:grpc]} +2024-12-06 15:02:01.270 [INFO] {d8e6fef56e840e1815d0325bc73eda8f} pid[15480]: grpc server started listening on [:32001] +``` + +至此,**比邻英语本**的第一个微服务开发完成,和单体服务其实相差不大。 + +## 测试结果 +--- +> 在测试工具中请求`gRPC`时,需要使用`proto`协议文件,请注意填写正确的依赖路径。 + +```json +grpc 127.0.0.1:32001.account.v1.Account.UserRegister +{ +    "username": "oldme", +    "password": "123456", +    "email": "tyyn1022@gmail.com" +} +{ +    "id": 1 +} + +grpc 127.0.0.1:32001.account.v1.Account.UserLogin +{ +    "username": "oldme", +    "password": "123456" +} +{ +    "token": "I am token" +} + +grpc 127.0.0.1:32001.account.v1.Account.UserInfo +{ +    "token": "I am token" +} +{ + +    "user": { +        "Id": 1, +        "Username": "oldme", +        "Password": "123456", +        "Email": "tyyn1022@gmail.com", +        "CreatedAt": { +            "seconds": "1733407200", +            "nanos": 0 +        }, +        "UpdatedAt": { +            "seconds": "1733407200", +            "nanos": 0 +        } +    } +} diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..b2859398c85 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/2.6.\346\234\215\345\212\241\346\263\250\345\206\214.md" @@ -0,0 +1,65 @@ +--- +title: 2.6.服务注册 +hide_title: true +slug: /course/proxima-book/user-etcd-register +keywords: [GoFrame, etcd, service registration, service discovery, microservices registry, configuration management] +description: "介绍了如何将用户微服务注册到 etcd 服务注册中心,包括配置文件设置、注册逻辑实现以及服务发现机制的配置。" +--- + +接下来,我们将用户微服务注册到`etcd`,供其他服务调用。 + +添加配置文件,写入`etcd`的访问地址。 + +*app/user/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +在入口文件添加注册逻辑: + +*app/user/main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + + "proxima/app/user/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +实际上,服务注册的关键代码只有一行,其他都是读取文件配置的代码: +```go +grpcx.Resolver.Register(etcd.New(address)) +``` + +重新运行项目,让代码生效。然后进入`etcd`容器,执行命令查看注册是否成功。 +```bash +$ etcdctl get "" --prefix --keys-only +``` + +这条命令用作查看`etcd`中所有存在的`key`,在其中我们会看到注册的服务: +```text +/service/default/default/user/latest/{IP}:32001 +``` + +> 服务注册可以理解成`DNS`域名解析。配置文件中的服务名称`grpc.name`,它可以类比成域名。 diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..10177a7cffa --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\234\215\345\212\241.md" @@ -0,0 +1,12 @@ +--- +title: '第二章-用户服务' +hide_title: true +sidebar_position: 2 +slug: '/course/proxima-book/user' +keywords: [GoFrame, user service, microservices, authentication, user management, registration, login, user information] +description: "本章详细介绍了基于 GoFrame 框架的用户服务实现,包括用户注册、登录和信息查询等核心功能的开发流程。" +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" new file mode 100644 index 00000000000..df3b4d13a0e --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221/\347\254\254\344\272\224\347\253\240-\350\277\233\344\270\200\346\255\245\347\232\204\346\226\271\345\220\221.md" @@ -0,0 +1,25 @@ +--- +title: 第五章-进一步的方向 +hide_title: true +sidebar_position: 99 +slug: /course/proxima-book/appendix +keywords: [GoFrame, microservices learning, authentication, load balancing, service configuration, interceptors, future directions] +description: "本章总结了 GoFrame 微服务开发的进阶学习方向,包括用户认证、多服务调用、拦截器使用、负载均衡策略等关键主题。" +--- + +首先,感谢每个读者能够耐心看完本书!您的三个服务有正常运行吗?像比邻星和他的两个伙伴一样。 +然后,这里还有一些学习方向提供给大家。作者本人水平有限,不足之处还请海涵。希望大家事业能够步步高升,蒸蒸日上! + +## 进一步的学习方向 +--- +- 用户认证授权:在微服务中完善用户认证授权,并在网关服务中使用它; +- 调用多个微服务:在同一个控制器中,调用多个微服务完成业务功能; +- 服务端拦截器:使用`GoFrame`提供的`gRPC`服务端拦截器; +- 负载策略:了解微服务负载均衡; +- 服务配置管理:对接一些配置中心,为微服务提供更灵活的配置方式。 + +## 来杯咖啡 +--- +**感谢每个支持者!** + +![功能清单](../assets/coffee.jpg) diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..9988b25e636 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.1.\345\211\215\347\275\256\345\207\206\345\244\207.md" @@ -0,0 +1,31 @@ +--- +title: '4.1.前置准备' +hide_title: true +slug: '/course/proxima-book/gateway-prepare' +keywords: [GoFrame, gateway initialization, API Gateway setup, microservices gateway, project structure] +description: "介绍了业务网关的初始化过程,包括使用 GoFrame CLI 创建网关服务、配置项目结构等基础准备工作。" +--- + +业务网关和单体`Web`服务基本一致,不同的地方在于,原来的具体业务逻辑交由调用微服务来实现。 + +## 代码初始化 +--- +执行以下命令,建立名为`gateway`的服务,同样保存在`app`目录下。 + +```bash +$ gf init app/gateway -a +initializing... +initialization done! +you can now run "cd app/gateway && gf run main.go" to start your journey, enjoy! +``` + +删除下列文件,留下一个空白的环境。 +```text +app/word/api/* +app/word/internal/controller/* +app/word/internal/cmd/cmd.go +``` + +进入仓库,开始正式开发。 +```bash +$ cd app/gateway \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" new file mode 100644 index 00000000000..24710cb3926 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.2.\346\216\245\345\217\243\344\270\216\346\216\247\345\210\266\345\231\250.md" @@ -0,0 +1,79 @@ +--- +title: "4.2.接口与控制器" +hide_title: true +slug: '/course/proxima-book/gateway-controller' +keywords: [GoFrame, API design, gateway controller, HTTP endpoints, request validation, response handling] +description: "详细介绍了业务网关的 API 接口设计和控制器实现,包括用户登录、注册等接口的定义和请求处理逻辑。" +--- + +这一步大家应该很熟悉,就不过多介绍。 + +## Api +--- +*app/gateway/api/user/v1/user.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type LoginReq struct { + g.Meta `path:"users/login" method:"post" sm:"登录" tags:"用户"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` +} + +type LoginRes struct { + Token string `json:"token" dc:"在需要鉴权的接口中header加入Authorization: token"` +} +``` + +*app/gateway/api/words/v1/words.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +type CreateReq struct { + g.Meta `path:"words" method:"post" sm:"创建" tags:"单词"` + Word string `json:"word" v:"required|length:1,100" dc:"单词"` + Definition string `json:"definition" v:"required|length:1,300" dc:"单词定义"` +} + +type CreateRes struct { +} + +type DetailReq struct { + g.Meta `path:"words/{id}" method:"get" sm:"详情" tags:"单词"` + Id uint `json:"id" v:"required"` +} + +type DetailRes struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ExampleSentence string `json:"exampleSentence"` + ChineseTranslation string `json:"chineseTranslation"` + Pronunciation string `json:"pronunciation"` + CreatedAt *gtime.Time `json:"createdAt"` + UpdatedAt *gtime.Time `json:"updatedAt"` +} +``` + +## Controller +--- +执行命令,生成控制器。 +```bash +$ gf gen ctrl +generated: D:\project\proxima\app\gateway\api\user\user.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user_new.go +generated: D:\project\proxima\app\gateway\internal\controller\user\user_v1_login.go +generated: D:\project\proxima\app\gateway\api\words\words.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_new.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_v1_create.go +generated: D:\project\proxima\app\gateway\internal\controller\words\words_v1_detail.go +done! diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" new file mode 100644 index 00000000000..272e811ae4b --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.3.\345\220\257\345\212\250\350\277\220\350\241\214.md" @@ -0,0 +1,131 @@ +--- +title: "4.3.启动运行" +hide_title: true +slug: '/course/proxima-book/gateway-run' +keywords: [GoFrame, gateway configuration, service startup, HTTP server, OpenAPI, Swagger, logging setup] +description: "详细说明了如何配置和启动业务网关服务,包括服务器配置、OpenAPI 文档、日志设置等关键内容。" +--- + +我们先将服务运行起来,之后再调用微服务。 + +## 编写配置文件 +--- +*app/gateway/manifest/config/config.yaml* +```yaml +server: + address: ":8000" + openapiPath: "/api.json" + swaggerPath: "/swagger" + logger: + path: "./log" + file: "{Y-m-d}.log" + level: "all" + stdout: true +``` + +*app/gateway/manifest/config/etcd.yaml* +```yaml +etcd: + address: "srv.com:2379" +``` + +## CMD文件 +--- +*app/gateway/internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "proxima/app/gateway/internal/controller/user" + "proxima/app/gateway/internal/controller/words" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http gateway server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Group("/", func(group *ghttp.RouterGroup) { + group.Bind(user.NewV1()) + group.Bind(words.NewV1()) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +## 启动运行 +--- +*app/gateway/main.go* +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "proxima/app/gateway/internal/cmd" +) + +func main() { + var ctx = gctx.New() + conf, err := g.Cfg("etcd").Get(ctx, "etcd.address") + if err != nil { + panic(err) + } + + var address = conf.String() + grpcx.Resolver.Register(etcd.New(address)) + + cmd.Main.Run(ctx) +} +``` + +```bash +$ gf run .\main.go +build running pid: 16144 +2024-12-10 15:30:30.788 [INFO] pid[16144]: http server started listening on [:8000] +2024-12-10 15:30:30.788 [INFO] {f0f10d9d4ec00f181b7a6f615f39d54b} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-12-10 15:30:30.789 [INFO] {f0f10d9d4ec00f181b7a6f615f39d54b} openapi specification is serving at address: http://127.0.0.1:8000/api.json +2024-12-10 15:30:30.817 [DEBU] {f0f10d9d4ec00f181b7a6f615f39d54b} service register: &{Head: Deployment: Namespace: Name:default Version: Endpoints:192.168.10.98:8000 Metadata:map[insecure:true protocol:http]} +2024-12-10 15:30:30.900 [DEBU] {f0f10d9d4ec00f181b7a6f615f39d54b} etcd put success with key "/service/default/default/default/latest/192.168.10.98:8000", value "{"insecure":true,"protocol":"http"}", lease "7587883327293376805" + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | POST | /v1/users/login | proxima/app/gateway/internal/controller/user.(*ControllerV1).Login | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | POST | /v1/words | proxima/app/gateway/internal/controller/words.(*ControllerV1).Create | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- + :8000 | GET | /v1/words/{id} | proxima/app/gateway/internal/controller/words.(*ControllerV1).Detail | +----------|--------|-----------------|----------------------------------------------------------------------|-------------------- +``` + +进入`etcd`容器,执行命令查看: +```bash +$ etcdctl get "" --prefix --keys-only + +/service/default/default/default/latest/{IP}:8000 +/service/default/default/word/latest/{IP}:32001 +/service/default/default/word/latest/{IP}:32002 +``` + +可以看到,我们的三个服务都注册成功了。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" new file mode 100644 index 00000000000..bcfa3ee2ab2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.4.gRPC\345\256\242\346\210\267\347\253\257.md" @@ -0,0 +1,140 @@ +--- +title: "4.4.封装gRPC客户端" +hide_title: true +slug: '/course/proxima-book/gateway-client' +keywords: [GoFrame, gRPC client, microservices communication, client configuration, service discovery, etcd integration] +description: "详细介绍了如何在业务网关中封装和配置 gRPC 客户端,实现与微服务的通信,包括客户端初始化、服务发现等功能。" +--- + +## 客户端 +--- +业务网关相当于`gRPC`客户端,各个微服务相当于`gRPC`服务端。我们将在控制器属性里,定义`gRPC client`,供后续使用。 + +定义`client`由`grpcx.Client.MustNewGrpcClientConn(service, opts...)`完成。 + +*app/gateway/internal/controller/user/user_new.go* +```go +package user + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/user" + v1 "proxima/app/user/api/account/v1" +) + +type ControllerV1 struct { + AccountClient v1.AccountClient +} + +func NewV1() user.IUserV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("user") + + return &ControllerV1{ + AccountClient: v1.NewAccountClient(conn), + } +} +``` + +*app/gateway/internal/controller/words/words_new.go* +```go +package words + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/words" + v1 "proxima/app/word/api/words/v1" +) + +type ControllerV1 struct { + WordsClient v1.WordsClient +} + +func NewV1() words.IWordsV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("word") + + return &ControllerV1{ + WordsClient: v1.NewWordsClient(conn), + } +} +``` + +## 拦截器 +--- +当前客户端没有超时处理,`gRPC`的默认超时时间阈值又非常大。如果`gRPC`服务端、`etcd`服务,或者网络出现异常,业务网关会无限卡死。我们来添加超时拦截器,以应对这种情况。 + +### 定义拦截器 +超时机制很简单,通过`Go`的上下文提供。 + +*app/gateway/utility/grpc.go* +```go +package utility + +import ( + "context" + "time" + "google.golang.org/grpc" +) + +func GrpcClientTimeout(ctx context.Context, method string, req, reply interface{}, + cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption, +) error { + ctx, cancel := context.WithTimeout(ctx, 3*time.Second) + defer cancel() + + err := invoker(ctx, method, req, reply, cc, opts...) + return err +} +``` + +### 调用拦截器 + +*app/gateway/internal/controller/user/user_new.go* +```go +package user + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/user" + "proxima/app/gateway/utility" + v1 "proxima/app/user/api/account/v1" +) + +type ControllerV1 struct { + AccountClient v1.AccountClient +} + +func NewV1() user.IUserV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("user", grpcx.Client.ChainUnary( + utility.GrpcClientTimeout, + )) + + return &ControllerV1{ + AccountClient: v1.NewAccountClient(conn), + } +} +``` + +*app/gateway/internal/controller/words/words_new.go* +```go +package words + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "proxima/app/gateway/api/words" + "proxima/app/gateway/utility" + v1 "proxima/app/word/api/words/v1" +) + +type ControllerV1 struct { + WordsClient v1.WordsClient +} + +func NewV1() words.IWordsV1 { + var conn = grpcx.Client.MustNewGrpcClientConn("word", grpcx.Client.ChainUnary( + utility.GrpcClientTimeout, + )) + + return &ControllerV1{ + WordsClient: v1.NewWordsClient(conn), + } +} \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..1d4f440ef47 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/4.5.\350\260\203\347\224\250\345\276\256\346\234\215\345\212\241.md" @@ -0,0 +1,123 @@ +--- +title: 4.5.调用微服务 +hide_title: true +slug: '/course/proxima-book/gateway-call' +keywords: [GoFrame, microservices integration, service invocation, gRPC communication, gateway implementation, service orchestration] +description: "详细说明了如何在业务网关中调用微服务,包括用户服务和单词服务的调用实现,以及业务逻辑的封装方式。" +--- + +接下来,我们要在控制器里调用微服务,完成具体的业务逻辑。 + +> 实际开发中,复杂业务逻辑需要封装到`logic`中,像`Web`单体服务一样,由控制器调用。 + +## 用户服务 +--- +*app/gateway/internal/controller/user/user_v1_login.go* +```go +package user + +import ( + "context" + + account "proxima/app/user/api/account/v1" + + "proxima/app/gateway/api/user/v1" +) + +func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) { + user, err := c.AccountClient.UserLogin(ctx, &account.UserLoginReq{ + Username: req.Username, + Password: req.Password, + }) + + if err != nil { + return nil, err + } + + return &v1.LoginRes{ + Token: user.GetToken(), + }, nil +} +``` + +发起请求测试,网关调用微服务是否成功: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/login \ + -H "Content-Type: application/json" \ + -d '{ + "username": "oldme", + "password": "12345678" + }' + +{ +  "code": 0, +  "message": "", +  "data": { +  "token": "I am token" +  } +} +``` + +看到这个返回结果,恭喜您,大功告成。接下来,您可以尝试把其他的几个服务也开发出来。这里我贴出源码,您可以对照着看看。 + +## 单词服务 +--- +*app/gateway/internal/controller/words/words_v1_create.go* +```go +package words + +import ( + "context" + + words "proxima/app/word/api/words/v1" + + "proxima/app/gateway/api/words/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + _, err = c.WordsClient.Create(ctx, &words.CreateReq{ + Uid: 1, + Word: req.Word, + Definition: req.Definition, + }) + + if err != nil { + return nil, err + } + + return &v1.CreateRes{}, nil +} +``` + +*app/gateway/internal/controller/words/words_v1_detail.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + words "proxima/app/word/api/words/v1" + + "proxima/app/gateway/api/words/v1" +) + +func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) { + word, err := c.WordsClient.Get(ctx, &words.GetReq{ + Id: uint32(req.Id), + }) + + if err != nil { + return nil, err + } + + if word == nil { + return nil, gerror.New("word not found") + } + + return &v1.DetailRes{ + Id: uint(word.Words.Id), + Word: word.Words.Word, + Definition: word.Words.Definition, + }, nil +} \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" new file mode 100644 index 00000000000..4f8da4c6606 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/proxima-book/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263/\347\254\254\345\233\233\347\253\240-\344\270\232\345\212\241\347\275\221\345\205\263.md" @@ -0,0 +1,12 @@ +--- +title: '第四章-业务网关' +hide_title: true +sidebar_position: 4 +slug: '/course/proxima-book/gateway' +keywords: [GoFrame, API Gateway, microservices gateway, service orchestration, request routing, load balancing] +description: "本章详细介绍了基于 GoFrame 框架实现的业务网关,包括请求路由、服务编排、负载均衡等核心功能的实现。" +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/course/starbook/assets/coffee.jpg b/versioned_docs/version-2.8.x/course/starbook/assets/coffee.jpg new file mode 100644 index 00000000000..8113f4f55f1 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/starbook/assets/coffee.jpg differ diff --git a/versioned_docs/version-2.8.x/course/starbook/assets/mvc.png b/versioned_docs/version-2.8.x/course/starbook/assets/mvc.png new file mode 100644 index 00000000000..79be3768448 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/starbook/assets/mvc.png differ diff --git a/versioned_docs/version-2.8.x/course/starbook/assets/svg/mvc.svg b/versioned_docs/version-2.8.x/course/starbook/assets/svg/mvc.svg new file mode 100644 index 00000000000..83e2c0869e5 --- /dev/null +++ b/versioned_docs/version-2.8.x/course/starbook/assets/svg/mvc.svg @@ -0,0 +1,4 @@ + + + +
    View
    Controller
    Model
    \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" "b/versioned_docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" new file mode 100644 index 00000000000..f42f83e6f78 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/assets/svg/\345\212\237\350\203\275\346\270\205\345\215\225.svg" @@ -0,0 +1,186 @@ +功能清单

    星辰英语本

    用户中心

    注册

    登录

    获取用户信息

    单词管理

    添加单词

    编辑单词

    单词列表

    查看单词

    学习单词

    随机获取若干单词

    设置掌握程度

    删除单词

    \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" "b/versioned_docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" new file mode 100644 index 00000000000..e1d3594df45 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/assets/svg/\346\265\201\347\250\213.svg" @@ -0,0 +1 @@ +
    Request
    Api
    Response
    Input
    Controller
    Output
    Do
    Logic
    Entity
    ORM
    Dao
    Database
    Model
    \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/course/starbook/assets/swagger.png b/versioned_docs/version-2.8.x/course/starbook/assets/swagger.png new file mode 100644 index 00000000000..e9c631fce07 Binary files /dev/null and b/versioned_docs/version-2.8.x/course/starbook/assets/swagger.png differ diff --git "a/versioned_docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" "b/versioned_docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" new file mode 100644 index 00000000000..0473940a4b6 Binary files /dev/null and "b/versioned_docs/version-2.8.x/course/starbook/assets/\346\265\201\347\250\213.png" differ diff --git a/versioned_docs/version-2.8.x/course/starbook/starbook.md b/versioned_docs/version-2.8.x/course/starbook/starbook.md new file mode 100644 index 00000000000..af13d06a03d --- /dev/null +++ b/versioned_docs/version-2.8.x/course/starbook/starbook.md @@ -0,0 +1,49 @@ +--- +slug: '/course/starbook' +title: '入门实战教程-星辰英语本' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame,GoFrame框架,星辰英语本,Golang教程,编程技巧,项目开发,程序员入门,技术栈,软件开发,计算机科学] +description: '本书通过GoFrame框架,以星辰英语本项目实战为例,帮助读者快速掌握GoFrame框架和Golang语言。不涉及前端开发,适合对Golang有基础的读者,包括在校学生和程序员。本书提供编程技巧和经验分享,建议读者结合官方手册进行学习,以更好地理解和实践。' +--- + +## 本书简介 +--- +`Web`开发领域知识庞杂,各类库、标准、框架等知识盘根错节,像一棵百年老树的树根。新手朋友面对它们如同面对一个刺猬,无从下手。 + +本书将从`GoFrame`框架出发,带领新手朋友们实际开发一个项目:**星辰英语本**。旨在为读者提供一份深入浅出的`GoFrame`框架学习指南,帮助读者快速掌握`GoFrame`框架。 + +以实际项目为导向,分享更多专业、实用的编程技巧和经验,让您学有所成是作者的期望! + +## 目标读者 +--- +已经对`Golang`有初步的掌握,想学习`GoFrame`框架的朋友都适合本书。 + +本书不会设计到任何的前端开发,即便您完全不了解任何前端技术也可以无障碍阅读。 + +### 在校学生 +如果您是一名在校学生,对`Go`语言感兴趣,想要学习如何使用`Go`语言开发一个有趣的项目或者毕业设计,为以后的职业生涯夯实基础,那么这本书可以作为您敲开编程的大门,引导您如何成为真正的程序员。 + +### 新手程序员 +如果您已经是一名程序员,但是对`Go`了解不深,想要进一步掌握`Go`语言或者学习`GoFrame`框架,那么这本书可以帮助您快速上手`GoFrame`框架,让您更快的融入`Go`语言的世界,进一步扩充您的技术栈。 + +## 阅读建议 +--- +本书是一本操作向的书籍,唯一的建议就是跟着书籍一步一步的操作。纸上得来终觉浅,绝知此事要躬行! + +在跟着本书操作过程中,最好结合[官方开发手册](../../docs/框架设计/框架设计.md),尝试举一反三,才能更好的理解和掌握。 + +## 联系作者 +--- +在编写本书的过程中,不可避免的会有一些错误或者不足之处,如果您有任何问题或者建议,可以在下方留言,也可以联系我,我会尽快回复您! +- 邮箱: `tyyn1022@gmail.com` `tyyn1022@163.com` +- 网站: [https://oldme.net](https://oldme.net) +- 微信: `NobodyIsRight` 来者请备注来意哦! + +## 遇到问题 +--- +在开发中,遇到各种问题都是正常的。如何解决问题才是关键,在遇到问题时,请先尝试自行解决。查阅`GoFrame`文档,使用搜索引擎都是不错的解决问题的方式。如果实在无法解决,可以联系我,我会尽力帮助您解决问题。 + + +import DocCardList from '@theme/DocCardList'; + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..f2fcca412f6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.1.\351\241\271\347\233\256\344\273\213\347\273\215.md" @@ -0,0 +1,52 @@ +--- +title: '1.1.项目介绍' +hide_title: true +slug: '/course/starbook/about-intro' +keywords: [项目需求,星辰英语本,用户注册,单词管理,掌握度设置,前后端分离,Web程序,前端框架,JSON格式,GoFrame框架] +description: '星辰英语本是一个GoFrame实战演示项目,帮助用户学习和管理英语单词,包括注册及登陆功能,并提供随机复习和掌握度设置。项目采用前后端分离模式,使用GoFrame框架进行开发,输出标准的Json格式数据,以便前端通过HTTP请求获取并构建页面。' +--- +开发一个实际的项目前,一定要先了解项目的需求,这是后续开发的基础。根基不牢,地动山摇。 + +**星辰英语本**是一个帮助用户学习英语单词的轻量级软件,它会提供如下功能: +- 用户注册; +- 登录后管理自己的单词; +- 随机获取若干单词复习; +- 能设置单词的掌握度。 + +从功能点出发,我们可以做一个更直观的思维导图: +![功能清单](../assets/svg/功能清单.svg) + +## 前后端分离 +--- +前文说过,本项目不会设计到任何前端开发,那么这里就有必要交代一下前端该如何处理。 + +对于一个 Web 程序而言,前端与后端是两个重要的组成部分。前端是指用户可以操作交互的页面,通常由`HTML`,`CSS`,`Javascript`构建,是软件程序的面子。前端并不会提供任何数据,它只是一个展示数据的工具,数据的来源是后端。后端提供所有的应用数据并且处理它们,是软件程序的里子。 + +很多年前,前后端并没有明确的界限,通常后端编程语言会直接输出`HTML`页面。随着互联网的发展,前端页面变得越来越复杂,这给后端程序员带来了很大的负担。于是,前后端分离逐渐成为主流,前端也出现了许多框架,如`Vue`、`React`、`Angular`等,这些框架可以帮助更好地管理前端项目。 + +**星辰英语本**的设计目的是为了让读者快速掌握`GoFrame`,所以也采取了前后端分离的模式,所有开发的内容都不直接输出`HTML`,而是绕过前端,直接输出标准的`Json`格式数据。 + +### Json +`Json`是当下最主流的前后端交互数据格式,一个标准接口返回的数据如下: +```json +{ +    "code": 0, +    "message": "", +    "data": { +        "id": 1, +        "uid": 1, +        "word": "example", +        "definition": "A representative form or pattern.", +        "exampleSentence": "This is an example sentence.", +        "chineseTranslation": "例子", +        "pronunciation": "ɪɡˈzɑːmp(ə)l", +        "proficiencyLevel": 3, +        "createdAt": "2024-11-12 15:38:50", +        "updatedAt": "2024-11-13 14:42:19" +    } + +} +``` +`code`为状态码,`0`代表成功,`message`为自定义消息,`data`为响应数据。 + +当前端程序员通过`HTTP`请求拿到`Json`数据后,便可以在自己的领域大展身手,构建出具体的页面。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" new file mode 100644 index 00000000000..3cb7908b3bd --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.2.MVC.md" @@ -0,0 +1,18 @@ +--- +title: '1.2.MVC' +hide_title: true +slug: '/course/starbook/about-mvc' +keywords: [MVC,Model,View,Controller,GoFrame,程序设计架构,前后端分离,数据管理,用户界面,业务逻辑] +description: '了解MVC(Model-View-Controller)程序设计架构,其中Model负责应用程序的数据和业务逻辑,管理数据与数据库交互;View负责显示数据和用户界面,与用户交互展示数据;Controller处理用户输入和请求,作为模型与视图之间的中介。前后端分离中,注重Controller和Model层。' +--- +如果您已经对`MVC`有所了解,那么可以跳过本节。如果您是新手,则需要了解一下`MVC(Model-View-Controller)`程序设计架构。 + +`MVC`将应用程序分成三个主要部分:Model、View 和 Controller。 + +![mvc](../assets/mvc.png) + +- **Model(模型)**:负责应用程序的数据和业务逻辑。直接管理数据、逻辑和规则。与数据库进行交互,处理数据的存储和检索。 +- **View(视图)**:负责显示数据和用户界面。直接与用户交互,展示数据和接收用户输入。更新显示以反映模型的最新状态。 +- **Controller(控制器)**:负责处理用户输入和请求。从视图接收输入并处理后,更新模型或视图。作为模型和视图之间的中介,协调它们的交互。 + +因为我们已经进行了前后端分离,实际上`View`层是交由前端处理的。我们只需要注重`Controller`和`Model`层即可。需要说明的是,`MVC`只是一种设计思想,不必深究于此。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" new file mode 100644 index 00000000000..90fa4c46f6f --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.3.\345\206\231\344\275\234\347\272\246\345\256\232.md" @@ -0,0 +1,35 @@ +--- +title: '1.3.写作约定' +hide_title: true +slug: '/course/starbook/about-convention' +keywords: [命令行,GoFrame,GoFrame框架,代码省略,输出命令,终端,提示符,代码整洁,程序示例,Go语言] +description: '本书在某些部分使用命令行,其中使用美元符号作为提示符号。通过命令输出信息到终端,并在不必要的代码中省略部分以保持整洁。示例展示了如何使用GoFrame框架进行基本的输出操作和代码编写技巧。' +--- +## 命令行 +--- +本书会在一些地方使用命令行,我将使用 `$` 符号作为提示符,您不需要输入这个符号。比如,如果我写了 `$ echo "Hello, GoFrame!" `,您只需要输入 `echo "Hello, GoFrame!" ` 即可。 + +```bash +$ echo "Hello, GoFrame!" +Hello, GoFrame! +``` + +`echo` 是一个输出命令,它会将 `Hello, GoFrame!` 输出到终端。 + +## 代码省略 +--- +为了保证篇幅的整洁,在不必要的竖版代码中,我会使用`...`来省略代码。 + +```go +package main + +import "fmt" + +... + +func main() { + fmt.Println("Hello GoFrame") +} + +... +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..cdfe72d54bd --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.4.\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,155 @@ +--- +title: '1.4.环境准备' +hide_title: true +slug: '/course/starbook/about-environment' +keywords: [GoFrame,Go,MySQL,数据库,接口测试,Postman,项目初始化,项目目录,GF CLI,IDEA插件] +description: 'GoFrame 是一个基于 Go 语言的 Web 后端框架,要求 Go 版本至少为 1.20。项目开发需要数据库支持,例子中使用 MySQL,并且兼容 MariaDB、TiDB 等多种数据库。推荐使用 Postman 或其他工具进行接口测试。还介绍了 GoFrame 的 GF CLI 安装和项目初始化过程,并提供了详细的项目目录结构说明。' +--- +## 基础环境 +--- +### Go 环境 +`GoFrame`是基于`Go`语言的 Web 后端框架,首先,我们需要拥有一个`Go`语言的开发环境,才能进行后续的学习。确保您的`Go`版本在`1.20`以上即可。 +```bash +$ go version +go version go1.22.2 windows/amd64 +``` + +### 数据库 +软件程序在使用过程中会产生许多数据,这些数据最后需要存储到数据库中。所以您需要准备好一个数据库,本书以`MySQL`为例,示例项目的数据库名称为`star`。 + +除`MySQL`外,`GoFrame`还支持`MariaDB`、`TiDB`、`PostgreSQL`、`SQL Server`、`SQLite`、`Oracle`、`ClickHouse`、`DM`多种数据库。只需要简单的修改配置、引入相关的驱动即可,这点会在使用时详细说明。 + +### 接口测试工具 +接口开发完成后,我会使用`curl`命令来测试接口是否符合预期。但是命令行总是不方便的,所以推荐您准备一个接口测试工具,将`curl`命令转换到工具中进行测试。比较流行的测试工具有`Postman`、`Apifox`、`Apipost`等,它们大同小异,您可以根据自己的喜好选择一个。 + +## 安装 GF CLI 工具 +--- +`GoFrame` 框架提供了功能强大的开发辅助工具,是框架的一个重要组成部分。用于创建项目、生成代码、运行项目等。我们可以通过以下命令安装最新的`GF CLI`: +```bash +$ go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +结束后运行以下命令检查是否安装成功: +```bash +$ gf version +v2.8.0 +Welcome to GoFrame! +Env Detail: + Go Version: go1.22.2 windows/amd64 + GF Version(go.mod): cannot find go.mod +... +``` + +> 本书的开发环境是`Go 1.22.2`和`GoFrame v2.8.0`,如果您的版本和我不一致,也无需担心,`Go`和`GoFrame`都有着强大的向下兼容性。 + +### Github 不能访问 +在国内由于网络原因,访问`Github`可能会失败,这是国内程序员必然会遇到的问题,您可以在网上找寻一个可用的`Github`镜像来解决此问题或使用其他代理工具。 + +## 初始化项目 +--- +准备好环境后,我们就可以正式初始化项目了。找好您要存放程序的目录,执行以下命令: +```bash +$ gf init star +initializing... +initialization done! +you can now run "cd star && gf run main.go" to start your journey, enjoy! +``` + +`star`是项目名称,代表星辰英语本的意思,您也可以取一个您喜欢的名字。接下来进入目录并运行项目。 + +```bash +$ cd star && gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 13628 +2024-11-06 16:45:46.015 [INFO] pid[13628]: http server started listening on [:8000] +2024-11-06 16:45:46.015 [INFO] {4c9b7c33a654051860769a5fdef82a84} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-06 16:45:46.015 [INFO] {4c9b7c33a654051860769a5fdef82a84} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | star/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|---------------------------------- +``` +因为演示使用的是`Windows`系统,所以生成的是`main.exe`文件,如果您使用的是`Mac/Linux`系统,生成的是`main`文件。 + +然后我们在浏览器中输入[http://127.0.0.1:8000/hello](http://127.0.0.1:8000/hello),或者使用接口测试工具访问,看到`Hello, GoFrame!`即表示您已经成功启动了一个`GoFrame`项目。恭喜您走出了伟大编程的第一步! + +> 如果您使用的编辑器是`IntelliJ IDEA`或者`GoLand`,可以安装[GoFrame Helper](https://plugins.jetbrains.com/plugin/23324-goframe-helper)插件,以提供更友善的代码提示和自动补全功能等功能。 + +### 升级项目 +默认安装的`GoFrame`不一定是最新版本,您可以在项目目录下执行`up`命令升级: +```bash +$ gf up +``` + +## 项目目录结构 +--- +项目初始化后,我们来看一下项目的目录结构: + +| 目录/文件名称 | 说明 | 描述 | +| -------------------------------------------------------- | ---- | -------------------------------------------------------- | +| `api` | 对外接口 | 对外提供服务的输入/输出数据结构定义。考虑到版本管理需要,往往以 `api/xxx/v1...` 存在。 | +| `hack` | 工具脚本 | 存放项目开发工具、脚本等内容。例如,CLI 工具的配置,各种 shell/bat 脚本等文件。 | +| `internal` | 内部逻辑 | 业务逻辑存放目录。通过 Golang `internal` 特性对外部隐藏可见性。 | +|     `cmd` | 入口指令 | 命令行管理目录。可以管理维护多个命令行。 | +|     `consts` | 常量定义 | 项目所有常量定义。 | +|     `controller` | 接口处理 | 接收/解析用户输入参数的入口/接口层。 | +|     `dao` | 数据访问 | 数据访问对象,这是一层抽象对象,用于和底层数据库交互,仅包含最基础的 CRUD 方法。 | +|     `logic` | 业务封装 | 业务逻辑封装管理,特定的业务逻辑实现和封装。往往是项目中最复杂的部分。 | +|     `model` | 结构模型 | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义。 | +|         `do` | 领域对象 | 用于 dao 数据操作中业务模型与实例模型转换,由工具维护,用户不能修改。 | +|         `entity` | 数据模型 | 数据模型是模型与数据集合的一对一关系,由工具维护,用户不能修改。 | +|     `service` | 业务接口 | 用于业务模块解耦的接口定义层。具体的接口实现在 logic 中进行注入。 | +| `manifest` | 交付清单 | 包含程序编译、部署、运行、配置的文件。常见内容如下: | +|     `config` | 配置管理 | 配置文件存放目录。 | +|     `docker` | 镜像文件 | Docker 镜像相关依赖文件,脚本文件等等。 | +|     `deploy` | 部署文件 | 部署相关的文件。默认提供了 Kubernetes 集群化部署的 Yaml 模板,通过 kustomize 管理。 | +|     `protobuf` | 协议文件 | GRPC 协议时使用的 protobuf 协议定义文件,协议文件编译后生成 go 文件到 api 目录。 | +| `resource` | 静态资源 | 静态资源文件。这些文件往往可以通过资源打包/镜像编译的形式注入到发布文件中。 | +| `go.mod` | 依赖管理 | 使用 Go Module 包管理的依赖描述文件。 | +| `main.go` | 入口文件 | 程序入口文件。 | + +看起来似乎一头雾水啊,不必担心,重要的目录都会在后面陆续用到。 + +然后我们把初始的示例文件全部删除,留下一个空白的环境,用作后续开发。先按`CTRL+C`终止项目运行,删除如下目录内的所有文件: +```text +api/* +internal/controller/* +``` + +编辑`cmd`文件,去掉不必要的代码。 + +*internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + }) + s.Run() + return nil + }, + } +) +``` diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" new file mode 100644 index 00000000000..662d24b80a8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/1.5.\346\234\254\344\271\246\346\272\220\347\240\201.md" @@ -0,0 +1,33 @@ +--- +title: '1.5.本书源码' +hide_title: true +slug: '/course/starbook/about-source' +keywords: [GoFrame,开源,源码,MIT协议,软件许可,代码管理,版权,软件发布,在线资源,源码获取] +description: '本书的源码开源于GitHub,其开源许可证基于MIT协议,允许用户免费使用、复制及修改软件而不受限制。文档中详细描述了软件许可的内容和条件,确保用户在使用版权内容时遵循规定,并对软件的适用性和责任做出声明,以便于更好地应用和开发。' +--- +本书的源码开源在 [https://github.com/oldme-git/star](https://github.com/oldme-git/star)。 + +开源许可证基于`MIT`协议: +```text +MIT License + +Copyright (c) 2024 oldme + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..1d4ab8c4138 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257/\347\254\254\344\270\200\347\253\240-\345\237\272\347\241\200\344\277\241\346\201\257.md" @@ -0,0 +1,27 @@ +--- +title: '第一章-基础信息' +hide_title: true +sidebar_position: 1 +slug: '/course/starbook/about' +keywords: [GoFrame,GoFrame框架,编程框架,开源,Web开发,应用开发,高性能,模块化设计,企业级应用,后端开发] +description: 'GoFrame框架是一款开源的、高性能的Web应用开发框架,支持模块化设计,适用于企业级应用开发。其易于上手的特性和强大的功能集合使其成为开发人员实现高效后端开发的可靠工具。' +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..b80be3a7500 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,27 @@ +--- +title: '3.1.前置说明' +hide_title: true +slug: '/course/starbook/session-overview' +keywords: [GoFrame,用户会话管理,JWT,HTTP无状态协议,Cookie,Json Web Token,golang-jwt,用户登录,身份认证,Local Storage] +description: '我们需要实现用户会话管理功能,包括登录和用户信息获取。JWT是一种用于用户身份验证的现代解决方案,通过HTTP头部传递,不依赖Cookie,并且支持跨域。JWT由Header、Payload和Signature组成,在前后端分离项目中广泛使用。' +--- +在本章中,我们需要完成用户的会话管理功能: +- 登录; +- 获取用户信息。 + +## JWT 简介 +--- +`HTTP` 是一种无状态协议,这意味着每次请求都是独立的,没有上下文关系。这就需要一种机制来保存用户的状态信息,`Cookie` 就是其中一种解决方案。`Cookie` 是存储在用户浏览器中的小块数据,可以在后续请求中发送给服务器,用于保持会话状态。然而,`Cookie` 也有一些限制,例如跨域问题和安全性问题。相比之下,`JWT` 是一种更现代的解决方案,可以通过 `HTTP` 头部传递,无需依赖 `Cookie`,且具有更好的跨域支持和安全性。 + +`JWT`全名`Json Web Token`,其看起来是一串无序的字符串。由三部分组成:头部(`Header`)、负载(`Payload`)和签名(`Signature`)。 + +在前后端分离的项目中,用户登录后,服务端生成`JWT`并返回,客户端自行保存它,例如浏览器本地储存`Local Storage`。在后续的请求中,通常在`Header`头的`Authorization`字段中携带它,以完成用户身份认证。 + +## 安装 golang-jwt +--- +生成验证`JWT`需要复杂的加密解密的逻辑,自己编写需费一番手脚。所幸的是,前人已经造好了轮子,直接安装调用即可。 + +```bash +$ go get -u github.com/golang-jwt/jwt/v5 +``` + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" new file mode 100644 index 00000000000..2429e4d1970 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.2.\347\231\273\345\275\225.md" @@ -0,0 +1,132 @@ +--- +title: '3.2.登录' +hide_title: true +slug: '/course/starbook/session-login' +keywords: [GoFrame,登录接口,用户认证,生成Token,密码加密,用户请求,错误处理,JwtKey,接口测试,编写逻辑] +description: '登录功能通过接收用户名和密码,验证成功生成Token返回。采用GoFrame框架,遵循三板斧开发原则,包括Api生成Controller,编写核心Logic逻辑。通过JwtKey生成签名,Token有效期为六小时。在Controller中调用核心逻辑实现登录功能,并测试接口确保功能正常。' +--- +登录接口收到用户名和密码后,与数据库中的信息做比对,如果正确则生成`Token`返回,否则提示”用户名或密码错误“。 + +同样遵循三板斧开发原则:编写`Api`生成`Controller`,编写核心逻辑`Logic`,`Controller`调起`Logic`。 +## 添加Api +--- +*api/users/v1/users.go* +```go +... + +type LoginReq struct { + g.Meta `path:"users/login" method:"post" sm:"登录" tags:"用户"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` +} + +type LoginRes struct { + Token string `json:"token" dc:"在需要鉴权的接口中header加入Authorization: token"` +} +``` + +> 别忘记执行`gf gen ctrl`哦!每次变更`Api`都需要执行它,后文不再重复提示。 + +## 编写Logic +--- +登录逻辑的的难点在于生成`Token`。准备好一个随机字符串`JwtKey`用作签名。 + +*internal/consts/consts.go* +```go +package consts + +const ( + JwtKey = "db03d23b03ec405793b38f10592a2f34" +) +``` + +编写核心逻辑,先根据用户名进行`Where`查询,获取到数据后,将密码再次加密,如果和数据库中的密文一致则说明是合法用户,生成`Token`返回。 + +*internal/logic/users/users_account.go* +```go +package users + +import ( + "context" + "errors" + "time" + + "github.com/golang-jwt/jwt/v5" + + "star/internal/dao" + "star/internal/model/entity" + "star/utility" +) + +type jwtClaims struct { + Id uint + Username string + jwt.RegisteredClaims +} + +func (u *Users) Login(ctx context.Context, username, password string) (tokenString string, err error) { + var user entity.Users + err = dao.Users.Ctx(ctx).Where("username", username).Scan(&user) + if err != nil { + return "", gerror.New("用户名或密码错误") + } + + if user.Id == 0 { + return "", gerror.New("用户不存在") + } + + // 将密码加密后与数据库中的密码进行比对 + if user.Password != u.encryptPassword(password) { + return "", gerror.New("用户名或密码错误") + } + + // 生成token + uc := &jwtClaims{ + Id: user.Id, + Username: user.Username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(6 * time.Hour)), + }, + } + token := jwt.NewWithClaims(jwt.SigningMethodHS256, uc) + return token.SignedString(consts.JwtKey) +} +``` + +从上面的代码可以看到,我们需要声明一个结构体`userClaims`保存签名信息,这里保存了`Id`和`Username`并设置了`Token`有效期为 6 个小时。最后的声明对象还需要调用`SignedString`方法传入`JwtKey`生成签名。 + +## Controller调用Logic +--- +*internal/controller/users/users_v1_login.go* +```go +package users + +import ( + "context" + + "star/internal/logic/users" + "star/api/users/v1" +) + +func (c *ControllerV1) Login(ctx context.Context, req *v1.LoginReq) (res *v1.LoginRes, err error) { + token, err := c.users.Login(ctx, req.Username, req.Password) + if err != nil { + return + } + return &v1.LoginRes{Token: token}, nil +} +``` + +## 接口测试 +--- +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/login -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\"}" + +{ + "code":0, + "message":"", + "data":{ + "token":"eyJhbGciOi...ZY_ATzOU" + } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..b6f32076500 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.3.\350\216\267\345\217\226\347\224\250\346\210\267\344\277\241\346\201\257.md" @@ -0,0 +1,222 @@ +--- +title: '3.3.获取用户信息' +hide_title: true +slug: '/course/starbook/session-get-user-info' +keywords: [GoFrame,用户信息接口,中间件,权限认证,Token验证,HTTP处理,请求控制,API设计,JWT解析,接口开发] +description: '用户信息接口需要登录后才能访问,并通过中间件统一验证Token的有效性。GoFrame框架提供了灵活的中间件机制,支持请求前后操作。通过Auth中间件验证HTTP请求中的Token,在Logic中解析Token获取用户信息,并通过定义Api和Controller调用控制器进行接口注册,实现用户信息接口的访问控制和权限认证。' +--- +用户信息是一个敏感的接口,它必须要保证用户登录之后才能访问。同样的,用户编辑单词库相关的接口也需要权限认证。我们不可能在每个需要认证的接口前都编写同样的代码用以权限认证。所以要编写一个**中间件/拦截器**来统一验证`Token`是否有效。 + +> 中间件/拦截器是处理 `HTTP` 请求和响应的函数或组件。它们通常用于在请求到达最终处理器之前或响应发送给客户端之前执行一些操作。 + +## Auth 中间件 +--- +`GoFrame` 提供了优雅的中间件请求控制方式, 该方式也是主流的 `WebServer` 提供的请求流程控制方式, 基于中间件设计可以为 `WebServer` 提供更灵活强大的插件机制。 + +*internal/logic/middleware/auth.go* +```go +package middleware + +import ( + "net/http" + + "star/internal/consts" + + "github.com/gogf/gf/v2/net/ghttp" + "github.com/golang-jwt/jwt/v5" +) + +func Auth(r *ghttp.Request) { + var tokenString = r.Header.Get("Authorization") + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return consts.JwtKey, nil + }) + if err != nil || !token.Valid { + r.Response.WriteStatus(http.StatusForbidden) + r.Exit() + } + r.Middleware.Next() +} +``` + +`r.Middleware.Next()` 这段代码是中间件的控制核心,放在函数的最后面表示它是一个**前置中间件**,代表请求前调用。放在最前面则是**后置中间件**,在请求后生效。 + +`r.Header.Get("Authorization")`从`HTTP Header`中获取`Authorization`字段,即获取前端传过来的`Token`。`jwt.Parse` 解析`Token`后再通过`token.Valid`验证是否有效,如果失效则返回 `HTTP StatusForbidden 403` 状态码,即权限不足,反之调用`r.Middleware.Next()`进入下一步。 + +编写好的中间件留用,等接口开发完成后统一注册到`cmd`中。接下来又是愉快的三板斧法则。 + +## 添加Api +--- +*api/account/v1/account.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type InfoReq struct { + g.Meta `path:"account/info" method:"get" sm:"获取信息" tags:"用户"` +} + +type InfoRes struct { + Username string `json:"username" dc:"用户名"` + Email string `json:"email" dc:"邮箱"` + CreatedAt *gtime.Time `json:"created" dc:"创建时间"` + UpdatedAt *gtime.Time `json:"update" dc:"更新时间"` +} +``` + +`InfoRes`结构体定义了四个响应数据,其中`*gtime.Time`数据类型是`gtime`组件提供的框架时间类型。 + +## 编写Logic +--- +*internal/logic/users/users_account.go* +```go +package users + +import ( + "context" + "errors" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/golang-jwt/jwt/v5" + "star/internal/dao" + "star/internal/model/entity" + "star/utility" +) + +... + +func (u *Users) Info(ctx context.Context) (user *entity.Users, err error) { + tokenString := g.RequestFromCtx(ctx).Request.Header.Get("Authorization") + tokenClaims, _ := jwt.ParseWithClaims(tokenString, &jwtClaims{}, func(token *jwt.Token) (interface{}, error) { + return consts.JwtKey, nil + }) + + if claims, ok := tokenClaims.Claims.(*jwtClaims); ok && tokenClaims.Valid { + err = dao.Users.Ctx(ctx).Where("id", claims.Id).Scan(&user) + } + return +} +``` + +在`Logic`中不能直接获取`HTTP`对象,需要使用`g.RequestFromCtx(ctx).Request`从上下文中获取。获取`Token`后解析出用户`ID`,调用`Scan` 方法将查询结果赋值给`entity.Users`结构体。 + +`Scan`方法是一个非常强大的方法,它会根据给定的参数类型自动识别并转换,是数据查询操作中的常客。 + +## Controller调用Logic +--- +同样将`logic`注册到控制器中。 + +*internal/controller/account/account_new.go* +```go +... + +package account + +import ( + "star/api/account" + "star/internal/logic/users" +) + +type ControllerV1 struct { + users *users.Users +} + +func NewV1() account.IAccountV1 { + return &ControllerV1{ + users: users.New(), + } +} +``` + +*internal/controller/account/account_v1_info.go* +```go +package account + +import ( + "context" + + "star/api/account/v1" +) + +func (c *ControllerV1) Info(ctx context.Context, req *v1.InfoReq) (res *v1.InfoRes, err error) { + user, err := c.users.Info(ctx) + if err != nil { + return nil, err + } + return &v1.InfoRes{ + Username: user.Username, + Email: user.Email, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + }, nil +} +``` + +## 注册新的控制器 +--- +使用`group.Group`新增一个路由分组,并使用`group.Middleware`注册`Auth`中间件,凡是在本组下的控制器在访问前都需要经过认证。 + +*internal/cmd/cmd.go* +```go +package cmd + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "star/internal/controller/account" + "star/internal/controller/users" + "star/internal/logic/middleware" +) + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + group.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(middleware.Auth) + group.Bind( + account.NewV1(), + ) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +## 接口测试 +--- +注意把`Authorization`换成自己的: +```bash +$ curl -H "Authorization: eyJhbGci...W6Ed_d3P77Mc" http://127.0.0.1:8000/v1/account/info + +{ + "code":0, + "message":"", + "data":{ + "username":"oldme", + "email":"tyyn1022@gmail.com", + "created":"2024-11-08 17:02:16", + "update":"2024-11-08 17:02:16" + } +} +``` diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..224a3637b30 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/3.4.\346\200\273\347\273\223.md" @@ -0,0 +1,29 @@ +--- +title: '3.4.总结' +hide_title: true +slug: '/course/starbook/session-summary' +keywords: [GoFrame,用户会话管理,登录功能,获取用户信息,Token生成,权限认证,中间件使用,分组路由,JWT,黑白名单机制] +description: '使用GoFrame完成用户会话管理,实现登录和用户信息获取功能。通过声明用户结构体生成Token,以及Token解析用户信息接口,深入理解GoFrame。引入中间件完成用户权限认证,并应用于分组路由。在使用JWT时,介绍解决退出登录问题的黑白名单机制,讨论其优劣和实现方式。' +--- +在本章节中,我们使用`GoFrame`完成了用户的会话管理,提供了**登录**和**获取用户信息**两大功能,加深了`GoFrame`的理解,并学会了以下内容: +- 声明用户结构体,生成`Token`; +- `Token`解析出用户信息,提供获取用户信息接口; +- 中间件的基础使用,使用中间件完成用户权限认证; +- 分组路由注册中间件。 + +## 关于退出登录 +--- +为什么我们没有开发退出登录功能呢?这是因为`JWT`本质上是一个无状态的令牌,一旦签发,服务器不会存储它。这导致了在使用 `JWT` 时,退出登录不能像传统会话那样简单地在服务器端销毁会话。解决`JWT`退出登录的方案大致有两种,它们各有优劣: + +1. **黑名单机制**: + - 当用户登出或 `JWT` 被撤销时,将该令牌添加到黑名单数据库中; + - 每次请求时,从请求头中提取 `JWT`,并检查它是否在黑名单中; + - 如果令牌在黑名单中,则拒绝请求; + - 其优势在于易于实现和维护,适用于大多数情况下;劣势在于需要存储所有被撤销的令牌,可能会导致存储空间增加。 +2. **白名单机制**: + - 在用户登录时,将生成的 `JWT` 添加到白名单数据库中; + - 每次请求时,从请求头中提取 `JWT`,并检查它是否在白名单中; + - 如果令牌不在白名单中,则拒绝请求。 + - 其优势在于更高的安全性,只有白名单中的令牌才能被接受,确保了更严格的访问控制。劣势在于复杂性增加,需要在用户登录时将令牌添加到白名单,并在合适的时候移除。 + +黑白名单数据一般会储存在非关系型数据库中,例如`Redis`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..243b9e79a08 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206/\347\254\254\344\270\211\347\253\240-\344\274\232\350\257\235\347\256\241\347\220\206.md" @@ -0,0 +1,27 @@ +--- +title: '第三章-会话管理' +hide_title: true +sidebar_position: 3 +slug: '/course/starbook/session' +keywords: [GoFrame,GoFrame框架,文档网站,网络课程,编程指南,软件开发,应用程序,框架教程,技术文档,工程项目] +description: 'GoFrame是一个高效率的Go语言开发框架,适用于快速构建可扩展的应用程序。通过使用GoFrame框架,开发者能够轻松应对复杂的工程项目需求,同时提供丰富的功能模块以支持各种应用场景。' +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..6c2c68ab888 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,141 @@ +--- +title: '2.1.前置说明' +hide_title: true +slug: '/course/starbook/register-overview' +keywords: [星辰英语本,用户注册,GoFrame,代码分层,MVC思想,ORM,数据库驱动,MySQL驱动,Go语言,软件开发] +description: '星辰英语本项目的用户注册功能的开发,详细阐述了GoFrame框架的代码分层设计和ORM功能的应用,提供了MySQL驱动的安装与使用说明,通过简单示例说明数据库交互的实现过程,提高代码可读性与维护性,避免SQL注入风险,并与MySQL数据库的实际连接过程进行了讲解。' +--- +本章将正式开发**星辰英语本**项目,我们将开发第一个接口:用户注册,以提供用户注册功能。 + +## 代码分层 +--- +`GoFrame`框架具有出色的代码分层设计,软件结构清晰明了。在开发过程中,可以遵循“三板斧”法则:首先在 `Api` 层定义接口信息,然后由 `Controller` 层接收 `HTTP` 接口请求,最后调用 `Logic` 层完成具体逻辑和数据库交互。这实际上是 `MVC` 思想的一类变种。 + +![流程](../assets/流程.png) + + +## ORM +--- +`ORM`全名`Object-Relational Mapping`,其核心功能是将数据表映射为对象,在`Go`语言里一般是结构体。 + +经过映射后,我们可以通过操作对象来处理数据表,而避免直接编写`SQL`语句。这不仅提高了代码的可读性和维护性,还从安全性上避免了常见的 `SQL` 注入问题。 + +`GoFrame`通过 [gdb](../../../docs/核心组件/数据库ORM/数据库ORM.md) 组件内置了`ORM`功能,提供了便捷常用的`SQL`操作。 + +举一个例子: +```go +ctx := gctx.New() +Users.Ctx(ctx).Where("username", "admin").One() +``` + +这是一个简单的查询操作,`ORM`会根据它自动生成`SQL`语句。 +```sql +SELECT * FROM users WHERE username = 'admin'; +``` + +`ORM`仅仅提供了数据表映射功能,实际上与数据库交互还需要用到数据库驱动。 + +## 数据库驱动 +--- +`GoFrame`是一款组件化的框架,为了更好的扩展性,数据库驱动做了解耦化处理,用户可以根据自己的需求选择不同的数据库驱动,从而实现对不同数据库的支持。 + +### 安装驱动 +安装`MySQL`驱动: +```bash +$ go get -u github.com/gogf/gf/contrib/drivers/mysql/v2 +``` + +其他数据库驱动: +```text +go get -u github.com/gogf/gf/contrib/drivers/clickhouse/v2 +go get -u github.com/gogf/gf/contrib/drivers/dm/v2 +go get -u github.com/gogf/gf/contrib/drivers/mssql/v2 +go get -u github.com/gogf/gf/contrib/drivers/oracle/v2 +go get -u github.com/gogf/gf/contrib/drivers/pgsql/v2 +go get -u github.com/gogf/gf/contrib/drivers/sqlite/v2 +go get -u github.com/gogf/gf/contrib/drivers/sqlitecgo/v2 +``` + +### 引入驱动 +全局引入驱动: + +*main.go* +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + ... +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` + +### 检查驱动 +完成后,我们需要检查一下数据库驱动是否安装成功。在配置文件中加入数据库配置,如果里面有内容则全部清除,保持和下面的一致: + +*manifest/config/config.yaml* +```yaml +server: + address: ":8000" # 服务监听端口 + openapiPath: "/api.json" # OpenAPI接口文档地址 + swaggerPath: "/swagger" # 内置SwaggerUI展示地址 + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/star?loc=Local" + debug: true +``` + +修改主函数,在程序运行前检查数据库是否正常连接。 + +*main.go* +```go +package main + +··· + +func main() { + var err error + + // 检查数据库是否能连接 + err = connDb() + if err != nil { + panic(err) + } + + cmd.Main.Run(gctx.GetInitCtx()) +} + +// connDb 检查数据库连接是否正常 +func connDb() error { + err := g.DB().PingMaster() + if err != nil { + return errors.New("连接到数据库失败") + } + return nil +} +``` + +运行项目,如果没有报错,说明数据库驱动安装成功。 +```base +$ gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 24612 +2024-11-07 16:42:51.197 [INFO] {f89117371ba305188476a74abc958a23} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-07 16:42:51.197 [INFO] pid[24612]: http server started listening on [:8000] +2024-11-07 16:42:51.197 [INFO] {f89117371ba305188476a74abc958a23} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|-------------------- +``` + +`gf run main.go`是`GF CLI`提供的程序运行命令,当开发者修改了项目中的 `go` 文件时,该命令将停止原有程序,自动编译并运行当前程序。可用作替代`go run main.go`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..5023e69594a --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,60 @@ +--- +title: '2.2.数据模型' +hide_title: true +slug: '/course/starbook/register-model' +keywords: [GoFrame框架,数据表,数据库,数据模型,GF-CLI,数据访问对象,创建时间,用户信息,电子邮件地址,自动递增] +description: '在数据库中创建名为users的数据表用于储存用户信息,包括用户名和密码等字段,支持自动递增主键标识。通过修改配置文件和执行GF-CLI命令生成数据模型和数据访问对象,生成的四个文件在model和dao层负责管理数据结构和数据访问,GoFrame框架通过ORM实现对数据表的操作。' +--- +## 建立数据表 +--- +在您的数据库中执行以下`sql`语句创建一张名为 `users` 的数据表,用于储存用户信息。 + +```sql +CREATE TABLE users ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + username VARCHAR(50) NOT NULL, + password CHAR(32) NOT NULL, + email VARCHAR(100), + created_at DATETIME, + updated_at DATETIME +); +``` + +| 字段名 | 类型 | 解释 | +| ------------ | -------------- | ----------------------------- | +| `id` | `INT UNSIGNED` | 主键,自动递增,唯一标识用户 | +| `username` | `VARCHAR(50)` | 用户名,不允许为空 | +| `password` | `CHAR(32)` | 用户密码hash 值,固定长度为32个字符 ,不允许为空, | +| `email` | `VARCHAR(100)` | 用户的电子邮件地址,可以为空 | +| `created_at` | `DATETIME` | 创建时间 | +| `updated_at` | `DATETIME` | 记录最后更新时间 | +## 生成数据模型 +--- +修改工具配置文件: + +*hack/config.yaml* +```yaml +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/star" +``` + +> `hack/config.yaml` 与 `manifest/config/config.yaml` 两个配置文件不要混淆,前者是用于开发工具的配置文件,后者是用于业务配置的文件。 + +执行`GF-CLI`命令生成数据模型: +```bash +$ gf gen dao +``` + +执行成功后,会在`model`层和`dao`层生成四个文件: +```text +internal/model/do/users.go +internal/model/entity/users.go +internal/dao/internal/users.go +internal/dao/users.go +``` + +`model`层用作`GoFrame`管理数据结构,与数据表一一对应,不允许用户修改。`model/do/users.go`用作数据写入对象,采用泛型设计,方便数据入库;`model/entity/users.go`用作数据读取对象,类型与数据表保持一致。 + +`dao`层管理数据访问对象,`GoFrame ORM`通过它实现对数据表的增删改查。`dao/internal/users.go`保存内部对象实现,不允许用户修改,也不对外暴露。`dao/users.go`实例化数据访问对象并对外暴露,用户可以在这里添加自定义的方法。 diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..e346ac73fc1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.3.\346\263\250\345\206\214\346\216\245\345\217\243.md" @@ -0,0 +1,216 @@ +--- +title: '2.3.注册接口' +hide_title: true +slug: '/course/starbook/register-general' +keywords: [GoFrame,用户注册接口,数据模型,API开发,业务逻辑层,HTTP请求,代码生成,控制器注册,数据库交互,项目运行] +description: '使用GoFrame框架开发用户注册接口,包含添加API、编写业务逻辑层、控制器调用逻辑、控制器注册以及项目运行的详细步骤。通过生成的数据访问对象和数据模型,实现接口与数据库的交互,最终在项目运行中测试接口的功能。' +--- +准备好数据模型后,就可以使用我们的"三板斧"法则开发用户接口了。 +## 添加Api +--- +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post"` + Username string `json:"username"` + Password string `json:"password"` + Email string `json:"email"` +} + +type RegisterRes struct { +} +``` + +为了更好的维护接口,通常会在接口地址的前面加上版本号。`GoFrame`推荐使用多级目录管理版本,这个接口的版本号是`v1`。 + +`RegisterReq`和`RegisterRes`分别定义`HTTP`的请求对象和响应对象。`g.Meta`内嵌到请求结构体中,并通过`Go Tag`方式来定义一般的接口属性。这段代码意味着我们新增了一个用户注册的接口,接口地址为`/users/register`,请求方式为`POST`,并且拥有三个请求参数:`Username`、`Password`、`Email`。 + +执行命令生成`Api`对应的`Controller`: +```bash +$ gf gen ctrl +generated: D:\project\star\api\users\users.go +generated: internal\controller\users\users.go +generated: internal\controller\users\users_new.go +generated: internal\controller\users\users_v1_register.go +done! +``` + +这里生成的四个文件,我们只需要关注`users_v1_register.go`即可,它用作接收`HTTP`请求,并调用`Logic`完成业务流程。 + +> 如果您已经安装了[GoFrame Helper](https://plugins.jetbrains.com/plugin/23324-goframe-helper)插件,会自动执行`gf gen ctrl`命令。也可以使用官网提供自动生成方式:[教程配置](../../../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md#自动模式推荐)。 + +## 编写Logic +--- +`Logic` 是业务逻辑层,存放在`internal/logic`下,供`Controller`调用从而实现具体的业务逻辑。 + +定义一个`Users`对象,并新建一个`New`函数用作实例化它: + +*internal/logic/users/users.go* +```go +package users + +type Users struct { +} + +func New() *Users { + return &Users{} +} +``` + +编写注册方法: + +*internal/logic/users/users_register.go* +```go +package users + +import ( + "context" + + "star/internal/dao" + "star/internal/model/do" +) + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: password, + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} +``` + +`dao.Users`是前面生成的数据访问对象,通过它与数据库交互。`do.Users`是生成的数据模型,它用作数据入库,还有一个类似的数据模型`entity.Users`用作数据出库。 + +## Controller调用Logic +--- +`Controller` 层负责接收 `Req` 请求对象后调用一个或多个`Logic`完成业务逻辑,一些简单的逻辑也可以直接放在`Controller`中处理。处理完成后的结果封装在约定的 `Res` 数据结构中返回。这里的`Res`数据结构为空,返回`nil`即可。 + +将`Users`对象封装到控制器中,方便后续调用。 + +*internal/controller/users/users_new.go* +```go +... + +package users + +import ( + "star/api/users" + userLogic "star/internal/logic/users" +) + +type ControllerV1 struct { + users *userLogic.Users +} + +func NewV1() users.IUsersV1 { + return &ControllerV1{ + users: userLogic.New(), + } +} +``` + + +*internal/controller/users/users_v1_register.go* +```go +package users + +import ( + "context" + + "star/internal/logic/users" + + "star/api/users/v1" +) + +func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { + err = c.users.Register(ctx, req.Username, req.Password, req.Email) + return nil, err +} +``` + +## 注册控制器 +--- +所有的控制器都必须要在`cmd`中注册才能生效。`cmd` 层负责引导程序启动,显著的工作是初始化逻辑、注册路由对象、启动 `server` 监听、阻塞运行程序直至 `server` 退出。 + +*internal/cmd/cmd.go* +```go +package cmd + +··· + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + }) + }) + s.Run() + return nil + }, + } +) +``` + +`group.Group` 是框架提供的分组路由注册方式,也是框架推荐的注册方式。我们在前面加上`v1`对应`api`目录,方便接口版本管理。 + +## 运行项目 +--- +```bash +$ gf run main.go +build: .\main.go +go build -o .\main.exe .\main.go +.\main.exe +build running pid: 8648 +2024-11-08 10:36:48.013 [INFO] pid[8648]: http server started listening on [:8000] +2024-11-08 10:36:48.013 [INFO] {e05c16b565dd0518360ebe639e1c623d} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-08 10:36:48.014 [INFO] {e05c16b565dd0518360ebe639e1c623d} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- + :8000 | POST | /v1/users/register | star/internal/controller/users.(*ControllerV1).Register | ghttp.MiddlewareHandlerResponse +----------|--------|--------------------|---------------------------------------------------------|---------------------------------- +``` + +运行结果中打印出了三个接口地址。`/swagger` 和 `/api.json`是框架生成的接口文档地址,我们会在[2.5 - 接口文档](./2.5.接口文档.md)详细的说明它。另外一个地址`/v1/users/register`便是我们开发出来的用户注册接口。发起一个`POST`请求来测试它。 + +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":0, + "message":"", + "data":null +} +``` + +`code`为`0`代表成功,去数据库查看是否插入了一条数据: +```sql +SELECT * FROM users; +``` + +| ID | Username | Password | Email | Created_At | Updated_At | +| --- | -------- | -------- | ------------------ | ------------------- | ------------------- | +| 1 | oldme | 123456 | tyyn1022@gmail.com | 2024-11-08 10:36:48 | 2024-11-08 10:36:48 | + +`Created_At`和`Updated_At`是两个约定字段,会被`ORM`自动维护,分别代表创建时间和修改时间。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" new file mode 100644 index 00000000000..c20b852d2ba --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.4.\344\270\232\345\212\241\344\274\230\345\214\226.md" @@ -0,0 +1,329 @@ +--- +title: '2.4.业务优化' +hide_title: true +slug: '/course/starbook/register-optimization' +keywords: [注册流程优化,接口参数校验,用户名校验,密码加密,GoFrame框架,i18n多语言,数据唯一性,结构体优化,验证规则,密码安全] +description: '注册流程存在的问题及优化方案,包括接口参数校验、禁止重复用户名、密码加密方法和函数参数优化等内容。利用GoFrame框架进行参数校验,包含用户名密码基本验证,并应用i18n实现多语言支持。通过设置数据库唯一索引,确保用户唯一性,同时采用MD5加密保障密码安全,提升系统安全和易用性。' +--- +当前注册流程还存在几个显著的问题需要优化: +- 接口参数没有校验,即使全部留空或随意填写也能入库成功。为此,用户名、密码、邮箱都应该必传,且需要有一定的安全验证。例如,密码应该在`6-12`位之间,邮箱应该是`xx@xx.xx`格式; +- 禁止注册相同的用户; +- 密码不应该明文入库,应加密后入库; +- `logic/Register`函数参数过多,一来显示不优雅,二来不利于维护,应该将用户信息定义到一个结构体中,将其作为函数入参。 +## 参数校检 +--- +`GoFrame`内置了强大的接口参数校检功能,只需要在`g.Meta`的`tag`上加上`v`即可启用。 + +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post"` + Username string `json:"username" v:"required|length:3,12"` + Password string `json:"password" v:"required|length:6,16"` + Email string `json:"email" v:"required|email"` +} + +type RegisterRes struct { +} +``` + +多个验证规则使用`|`隔开,`required`表示此字段必填,`length`表示位数在`3-12`之间,`email`表示只接受合法的邮箱地址。所有可用的验证规则可在[开发手册](../../../docs/核心组件/数据校验/数据校验-校验规则.md)中查阅。 + +发起一个空用户名请求测试: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":51, + "message":"The Username field is required", + "data":null +} +``` + +`The Username field is required`提示我们用户名不能为空。 + +如果您对英文提示不满意,还可以使用框架提供的`i18n`组件改成中文提示。 + +### 参数校检i18n +从[Github](https://github.com/gogf/gf/blob/master/util/gvalid/testdata/i18n/cn/validation.toml)下载文件并且存放到`manifest/i18n`目录,直接从下文复制也行。 + +*manifest/i18n/zh-CN/validation.toml* +```toml +"gf.gvalid.rule.required" = "{field}字段不能为空" +"gf.gvalid.rule.required-if" = "{field}字段不能为空" +"gf.gvalid.rule.required-unless" = "{field}字段不能为空" +"gf.gvalid.rule.required-with" = "{field}字段不能为空" +"gf.gvalid.rule.required-with-all" = "{field}字段不能为空" +"gf.gvalid.rule.required-without" = "{field}字段不能为空" +"gf.gvalid.rule.required-without-all" = "{field}字段不能为空" +"gf.gvalid.rule.date" = "{field}字段值`{value}`日期格式不满足Y-m-d格式,例如: 2001-02-03" +"gf.gvalid.rule.datetime" = "{field}字段值`{value}`日期格式不满足Y-m-d H:i:s格式,例如: 2001-02-03 12:00:00" +"gf.gvalid.rule.date-format" = "{field}字段值`{value}`日期格式不满足{format}" +"gf.gvalid.rule.email" = "{field}字段值`{value}`邮箱地址格式不正确" +"gf.gvalid.rule.phone" = "{field}字段值`{value}`手机号码格式不正确" +"gf.gvalid.rule.phone-loose" = "{field}字段值`{value}`手机号码格式不正确" +"gf.gvalid.rule.telephone" = "{field}字段值`{value}`电话号码格式不正确" +"gf.gvalid.rule.passport" = "{field}字段值`{value}`账号格式不合法,必需以字母开头,只能包含字母、数字和下划线,长度在6~18之间" +"gf.gvalid.rule.password" = "{field}字段值`{value}`密码格式不合法,密码格式为任意6-18位的可见字符" +"gf.gvalid.rule.password2" = "{field}字段值`{value}`密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母和数字" +"gf.gvalid.rule.password3" = "{field}字段值`{value}`密码格式不合法,密码格式为任意6-18位的可见字符,必须包含大小写字母、数字和特殊字符" +"gf.gvalid.rule.postcode" = "{field}字段值`{value}`邮政编码不正确" +"gf.gvalid.rule.resident-id" = "{field}字段值`{value}`身份证号码格式不正确" +"gf.gvalid.rule.bank-card" = "{field}字段值`{value}`银行卡号格式不正确" +"gf.gvalid.rule.qq" = "{field}字段值`{value}`QQ号码格式不正确" +"gf.gvalid.rule.ip" = "{field}字段值`{value}`IP地址格式不正确" +"gf.gvalid.rule.ipv4" = "{field}字段值`{value}`IPv4地址格式不正确" +"gf.gvalid.rule.ipv6" = "{field}字段值`{value}`IPv6地址格式不正确" +"gf.gvalid.rule.mac" = "{field}字段值`{value}`MAC地址格式不正确" +"gf.gvalid.rule.url" = "{field}字段值`{value}`URL地址格式不正确" +"gf.gvalid.rule.domain" = "{field}字段值`{value}`域名格式不正确" +"gf.gvalid.rule.length" = "{field}字段值`{value}`字段长度应当为{min}到{max}个字符" +"gf.gvalid.rule.min-length" = "{field}字段值`{value}`字段最小长度应当为{min}" +"gf.gvalid.rule.max-length" = "{field}字段值`{value}`字段最大长度应当为{max}" +"gf.gvalid.rule.size" = "{field}字段值`{value}`字段长度必须应当为{size}" +"gf.gvalid.rule.between" = "{field}字段值`{value}`字段大小应当为{min}到{max}" +"gf.gvalid.rule.min" = "{field}字段值`{value}`字段最小值应当为{min}" +"gf.gvalid.rule.max" = "{field}字段值`{value}`字段最大值应当为{max}" +"gf.gvalid.rule.json" = "{field}字段值`{value}`字段应当为JSON格式" +"gf.gvalid.rule.xml" = "{field}字段值`{value}`字段应当为XML格式" +"gf.gvalid.rule.array" = "{field}字段值`{value}`字段应当为数组" +"gf.gvalid.rule.integer" = "{field}字段值`{value}`字段应当为整数" +"gf.gvalid.rule.float" = "{field}字段值`{value}`字段应当为浮点数" +"gf.gvalid.rule.boolean" = "{field}字段值`{value}`字段应当为布尔值" +"gf.gvalid.rule.same" = "{field}字段值`{value}`字段值必须和{field}相同" +"gf.gvalid.rule.different" = "{field}字段值`{value}`字段值不能与{field}相同" +"gf.gvalid.rule.in" = "{field}字段值`{value}`字段值应当满足取值范围:{pattern}" +"gf.gvalid.rule.not-in" = "{field}字段值`{value}`字段值不应当满足取值范围:{pattern}" +"gf.gvalid.rule.regex" = "{field}字段值`{value}`字段值不满足规则:{pattern}" +"gf.gvalid.rule.__default__" = "{field}字段值`{value}`字段值不合法" +"CustomMessage" = "自定义错误" +"project id must between {min}, {max}" = "项目ID必须大于等于{min}并且要小于等于{max}" +``` + +修改主函数,启用`i18n`: + +*main.go* +```go +package main + +··· + +func main() { + var err error + + // 全局设置i18n + g.I18n().SetLanguage("zh-CN") + + // 检查数据库是否能连接 + err = connDb() + if err != nil { + panic(err) + } + + cmd.Main.Run(gctx.GetInitCtx()) +} + +··· +``` + +再次发起请求: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":51, + "message":"Username字段不能为空", + "data":null +} +``` + +可以看到`message`已经变成中文提示了。 + +## 禁止重复用户名 +--- +用户名是登录的重要依据,如果碰巧系统中有两个同名用户,则会出现重大的逻辑混乱。所以我们需要在数据入库前查询该用户是否存在,如果存在,则返回错误信息,提示用户已经存在。 + +*internal/logic/users/users_register.go* +```go +package users + +... + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + if err := u.checkUser(ctx, username); err != nil { + return err + } + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: password, + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} + +func (u *Users) checkUser(ctx context.Context, username string) error { + count, err := dao.Users.Ctx(ctx).Where("username", username).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("用户已存在") + } + return nil +} +``` + +发起请求测试结果: +```bash +$ curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" + +{ + "code":50, + "message":"用户已存在", + "data":null +} +``` + +只有代码检测还不够安全,我们在数据表中加上唯一索引,强制限制用户唯一。 +```sql +ALTER TABLE users ADD UNIQUE (username); +``` + +## 密码加密 +--- +密码明文保存是一种非常不安全的行为,通常的做法是对其`hash`计算后存入数据库,例如`md5`、`SHA-1`等。 + +新增一个方法`encryptPassword`实现密码加密功能。 + +*internal/logic/users/users.go* +```go +package users + +import "github.com/gogf/gf/v2/crypto/gmd5" + +... + +func (u *Users) encryptPassword(password string) string { + return gmd5.MustEncryptString(password) +} +``` + +`gmd5`组件帮助我们快速实现`md5`加密功能。编写注册逻辑代码,引入密码加密。 + +*internal/logic/users/users_register.go* +```go +package users + +... + +func (u *Users) Register(ctx context.Context, username, password, email string) error { + ... + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: username, + Password: u.encryptPassword(password), + Email: email, + }).Insert() + if err != nil { + return err + } + return nil +} + +... +``` + +删除原本的用户: +```sql +DELETE FROM users WHERE id = 1; +``` + +重新请求接口查看密码是否成功加密: +```bash +curl -X POST http://127.0.0.1:8000/v1/users/register -H "Content-Type: application/json" -d "{\"username\":\"oldme\", \"password\":\"123456\", \"email\":\"tyyn1022@gmail.com\"}" +``` + +结果: + +| ID | Username | Password | Email | Created_At | Updated_At | +| --- | -------- | -------------------------------- | ------------------ | ------------------- | ------------------- | +| 1 | oldme | e10adc3949ba59abbe56e057f20f883e | tyyn1022@gmail.com | 2024-11-08 10:36:48 | 2024-11-08 10:36:48 | + +## Register 函数优化 +--- +自定义一个数据模型,用作`Logic`层的入参。 + +*internal/logic/users/users_register.go* +```go +package users + +... + +type RegisterInput struct { + Username string + Password string + Email string +} + +... +``` + +*internal/logic/users/users_register.go* +```go +package users + +import ( + "star/internal/model" + ... +) + +func (u *Users) Register(ctx context.Context, in RegisterInput) error { + if err := u.checkUser(ctx, in.Username); err != nil { + return err + } + + _, err := dao.Users.Ctx(ctx).Data(do.Users{ + Username: in.Username, + Password: encryptPassword(in.Password), + Email: in.Email, + }).Insert() + if err != nil { + return err + } + return nil +} + +... +``` + +更改`Controller`层,将`RegisterInput`传入。 + +*internal/controller/users/users_v1_register.go* +```go +package users + +import ( + "context" + + "star/api/users/v1" + "star/internal/logic/users" +) + +func (c *ControllerV1) Register(ctx context.Context, req *v1.RegisterReq) (res *v1.RegisterRes, err error) { + err = c.users.Register(ctx, users.RegisterInput{ + Username: req.Username, + Password: req.Password, + Email: req.Email, + }) + return nil, err +} +``` + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" new file mode 100644 index 00000000000..b98aadaa4ae --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.5.\346\216\245\345\217\243\346\226\207\346\241\243.md" @@ -0,0 +1,45 @@ +--- +title: '2.5.接口文档' +hide_title: true +slug: '/course/starbook/register-api-doc' +keywords: [GoFrame,接口文档,自动生成,API工具,注册接口,Swagger,OpenAPI,标签,用户接口,JSON文档] +description: '使用GoFrame框架内置功能为前端提供接口文档,通过在接口代码中添加特定标签,开发者可以自动生成详细的接口文档。文档可通过Swagger浏览器访问,或以JSON格式对接其他API工具,节省开发者的时间和精力。列举了常见的OpenAPI标签及其用途,帮助开发者更好地组织与管理接口信息。' +--- +编写完成的接口交给前端调用时,需要为他们准备好一份接口文档。幸运的是,`GoFrame`已经内置自动生成接口文档功能,节省了我们大量的时间。只需要在编写`api`时携带一些额外的`tag`,即可生成优美的文档。 + +*api/users/v1/users.go* +```go +package v1 + +import "github.com/gogf/gf/v2/frame/g" + +type RegisterReq struct { + g.Meta `path:"users/register" method:"post" sm:"注册" tags:"用户"` + Username string `json:"username" v:"required|length:3,12" dc:"用户名"` + Password string `json:"password" v:"required|length:6,16" dc:"密码"` + Email string `json:"email" v:"required|email" dc:"邮箱"` +} + +type RegisterRes struct { +} +``` + +在浏览器中打开 [http://127.0.0.1:8000/swagger](http://127.0.0.1:8000/swagger): +![swagger](../assets/swagger.png) + + 另外一个地址[http://127.0.0.1:8000/api.json](http://127.0.0.1:8000/api.json)提供了`Json`格式的接口文档,可以导入到各类`Api`工具中使用。 + +除了`sm`、`tags`和`dc`标签外,`GoFrame`还提供了如下标签: + +| 常见OpenAPIv3标签 | 说明 | 备注 | +| ------------- | ---------------------------------------------------------------------- | -------------------------- | +| `path` | 结合注册时的前缀共同构成接口URI路径 | 用于 `g.Meta` 标识接口元数据 | +| `tags` | 接口所属的标签,用于接口分类 | 用于 `g.Meta` 标识接口元数据 | +| `method` | 接口的请求方式: `GET/PUT/POST/DELETE...(不区分大小写)` | 用于 `g.Meta` 标识接口元数据 | +| `deprecated` | 标记该接口废弃 | 用于 `g.Meta` 标识接口元数据 | +| `summary` | 接口/参数概要描述 | 缩写 `sm` | +| `description` | 接口/参数详细描述 | 缩写 `dc` | +| `in` | 参数的提交方式 | `header/path/query/cookie` | +| `default` | 参数的默认值 | 缩写 `d` | +| `mime` | 接口的 `MIME` 类型,例如 `multipart/form-data` 一般是全局设置,默认为 `application/json`。 | 用于 `g.Meta` 标识接口元数据 | +| `type` | 参数的类型,一般不需要设置,特殊参数需要手动设置,例如 `file` | 仅用于参数属性 | diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..2858680f795 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/2.6.\346\200\273\347\273\223.md" @@ -0,0 +1,14 @@ +--- +title: '2.6.总结' +hide_title: true +slug: '/course/starbook/register-summary' +keywords: [GoFrame,注册接口,数据库驱动,gf gen dao,数据模型,GoFrame ORM,api文件,gf gen ctrl,控制器,接口参数校检] +description: '使用GoFrame完成注册接口的实践过程中,学习了安装数据库驱动、生成数据模型、通过DAO与数据库交互、编写API文件、使用gf gen ctrl生成控制器、以及接口参数校检和国际化处理方法,掌握了Controller层与Logic层的业务逻辑对接。' +--- +在本章节中,我们使用`GoFrame`完成了一个注册接口。初步接触了`GoFrame`并学会了以下内容: +- 安装数据库驱动; +- 使用`gf gen dao`生成数据模型; +- 通过`dao`调用`GoFrame ORM`与数据库交互; +- 编写`api`文件并使用`gf gen ctrl`生成控制器; +- `Controller`层调用`Logic`层完成业务逻辑; +- 接口参数校检与`i18n`。 diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..84987a39070 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214/\347\254\254\344\272\214\347\253\240-\347\224\250\346\210\267\346\263\250\345\206\214.md" @@ -0,0 +1,27 @@ +--- +title: '第二章-用户注册' +hide_title: true +sidebar_position: 2 +slug: '/course/starbook/register' +keywords: [GoFrame,GoFrame框架,用户注册,注册功能,账号管理,用户账号,用户体验,安全注册,注册流程,身份验证] +description: '本节详细描述了如何在GoFrame框架中实现用户注册功能,包括账号创建、用户信息验证以及安全保障。用户可以通过该流程轻松注册并管理个人账号,享受优质的用户体验。' +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..62ca6ece5ff --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,12 @@ +--- +title: '5.1.前置说明' +hide_title: true +slug: '/course/starbook/learn-word-overview' +keywords: [GoFrame,单词复习,星辰英语本,单词掌握程度,RESTful接口,随机获取单词,用户功能,复习目标,英语学习,接口设计] +description: '本章探讨星辰英语本的两个功能,用户可以随机获取单词以便复习,还可以设置单词的掌握程度,从而能够更好地明确复习目标。通过这两个功能,用户对单词学习有更清晰的规划。同时,深入学习RESTful相关技术,增强接口的丰富性与实用性。' +--- +在本章中我们将进一步丰富星辰英语本,为用户提供两个实用功能: +- 随机获取若干单词,方便用户复习; +- 设置单词掌握程度,清晰复习目标。 + +并在此过程中深入学习`RESTful`,提供更为丰富的接口。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..f88aa030d9d --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.2.\351\232\217\346\234\272\350\216\267\345\217\226\350\213\245\345\271\262\345\215\225\350\257\215.md" @@ -0,0 +1,127 @@ +--- +title: '5.2.随机获取若干单词' +hide_title: true +slug: '/course/starbook/learn-word-rand' +keywords: [GoFrame,API,随机单词,单词查询,GoFrame框架,分页列表,接口设计,编程逻辑,接口测试,单词列表] +description: '使用GoFrame框架设计随机获取单词接口。提供路径words/rand的API,支持在1到300之间限制获取单词个数。使用OrderRandom实现随机查询,Limit限制查询返回数量。控制器调用逻辑处理查询结果,并进行接口测试以验证功能。' +--- +随机获取若干单词与获取单词分页列表大同小异: +- 没有分页查询,转而使用随机查询; +- 没有模糊查询; +- 返回的数据基本一致。 +## 添加Api +--- +*api/words/v1/words_learn.go* +```go +package v1 + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +type RandListReq struct { + g.Meta `path:"words/rand" method:"get" sm:"随机获取单词列表" tags:"单词"` + Limit uint `json:"limit" v:"between:1,300" dc:"限制个数,默认50"` +} + +type RandListRes struct { + List []List `json:"list"` +} +``` + +`RandListReq`提供`Limit`字段,表示想要获取的个数,范围在`1-300`。`RandListRes`使用与单词分页列表一样的数据结构,但是少了`Total`字段。 + +> `words/rand`是一种精确匹配,它的优先级要高于单词详情接口:`words/{id}`。 + +## 编写Logic +--- +*internal/logic/words/words_learn.go* +```go +package words + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gerror" + "star/internal/dao" + "star/internal/model" + "star/internal/model/entity" +) + +func (w *Words) Rand(ctx context.Context, uid, limit uint) ([]entity.Words, error) { + if limit <= 0 { + limit = 50 + } + var ( + err error + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + list = make([]entity.Words, limit) + ) + if uid > 0 { + orm = orm.Where(cls.Uid, uid) + } + err = orm.Limit(int(limit)).OrderRandom().Scan(&list) + return list, err +} +``` + +`OrderRandom`是`GoFrame ORM`提供的随机查询方法,`Limit`方法用于限制查询个数 + +## Controller调用Logic +--- +*internal/controller/words/words_v1_rand_list.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) RandList(ctx context.Context, req *v1.RandListReq) (res *v1.RandListRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + wordList, err := c.words.Rand(ctx, uid, req.Limit) + if err != nil { + return nil, err + } + + var list []v1.List + for _, v := range wordList { + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: v1.ProficiencyLevel(v.ProficiencyLevel), + }) + } + + return &v1.RandListRes{ + List: list, + }, nil +} +``` + +## 接口测试 +--- +准备一些测试数据并测试,这里为了节省篇幅就不再详细介绍了。 +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words/rand \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": { +        "list": [ +     ... +        ] +    } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" new file mode 100644 index 00000000000..babaf892f8c --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.3.\350\256\276\347\275\256\346\216\214\346\217\241\347\250\213\345\272\246.md" @@ -0,0 +1,92 @@ +--- +title: '5.3.设置掌握程度' +hide_title: true +slug: '/course/starbook/learn-word-set-level' +keywords: [GoFrame,ProficiencyLevel,PATCH方法,API设计,路由风格,数据验证,接口测试,数据库更新,Go语言,等级设置] +description: '通过GoFrame框架实现设置单词掌握程度功能。使用PATCH方法修改ProficiencyLevel字段,引入参数验证确保等级在1到5之间。设计的API路由风格遵循资源层级关系,推荐使用words/{id}/level形式。通过数据库更新完成等级设置,提供接口测试示例以验证功能的正确性。' +--- +设置掌握程度的功能本质上是一种编辑,只改变`ProficiencyLevel`一个字段,所以这里使用`PATCH`方式比`PUT`更为合适。 +## 添加Api +--- +*api/words/v1/words_learn.go* +```go +... + +type SetLevelReq struct { + g.Meta `path:"words/{id}/level" method:"patch"` + Id uint `json:"id" v:"required"` + Level ProficiencyLevel `json:"level" v:"required|between:1,5"` +} + +type SetLevelRes struct { +} +``` + +`words/{id}/level`和`words/level/{id}`这两种路由风格均可,但是前者更符合资源层级关系,推荐使用。 + +## 编写Logic +--- +*internal/logic/words/words_learn.go* +```go +... + +// SetLevel 设置单词熟练度 +func (w *Words) SetLevel(ctx context.Context, uid, id uint, level v1.ProficiencyLevel) error { + if level < 0 || level > 5 { + return gerror.New("熟练度值不合法") + } + + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + if uid > 0 { + orm = orm.Where(cls.Uid, uid) + } + + _, err := orm.Data(cls.ProficiencyLevel, level).Where(cls.Id, id).Update() + return err +}**** +``` + +为了防止数据异常,我们要在入库前检测等级是否在`1-5`之间。 + +## Controller调用Logic +--- +*internal/controller/words/words_v1_set_level.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) SetLevel(ctx context.Context, req *v1.SetLevelReq) (res *v1.SetLevelRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + err = c.words.SetLevel(ctx, uid, req.Id, req.Level) + return nil, err +} +``` + +## 接口测试 +--- +```bash +$ curl -X PATCH http://127.0.0.1:8000/v1/words/1/level \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "proficiency_level": 5 + }' + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..ebb845b5ba3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/5.4.\346\200\273\347\273\223.md" @@ -0,0 +1,10 @@ +--- +title: '5.4.总结' +hide_title: true +slug: '/course/starbook/learn-word-summary' +keywords: [GoFrame,单词学习,功能开发,随机查询,RESTful,PATCH方法,编程技术,网络应用,API接口,软件工程] +description: '本章节开发了获取随机单词和使用PATCH方式加深RESTful理解的功能。这包括随机查询技术,以及通过实践提升对RESTful架构的理解,帮助用户在单词学习过程中应用和掌握相关编程技术。' +--- +在本章节中,我们开发了学习单词的相关功能: + - 获取随机若干单词,学会了随机查询; + - 使用`PATCH`方式加深`RESTful`的理解。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..0610a28c9d3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215/\347\254\254\344\272\224\347\253\240-\345\255\246\344\271\240\345\215\225\350\257\215.md" @@ -0,0 +1,27 @@ +--- +title: '第五章-学习单词' +hide_title: true +sidebar_position: 5 +slug: '/course/starbook/learn-word' +keywords: [GoFrame,GoFrame框架,课程指南,Starbook教程,GoFrame学习,编程指南,框架使用,应用开发,软件开发] +description: '本章节涵盖了GoFrame框架在Starbook中的应用,提供详细的课程指南和学习教程,帮助开发者深入掌握GoFrame框架的使用,提升编程技能和开发效率,适用于各种应用开发场景。' +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" new file mode 100644 index 00000000000..3fc4e635e08 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200/\347\254\254\345\205\255\347\253\240-\351\231\204\350\250\200.md" @@ -0,0 +1,34 @@ +--- +title: '第六章-附言' +hide_title: true +sidebar_position: 6 +slug: '/course/starbook/appendix' +keywords: [GoFrame,GoFrame框架,Golang,中间件,JWT机制,配置管理,业务优化,错误信息自定义,数据验证,黑白名单] +description: '本书提供了一系列开发功能的优化建议,包括中间件的使用和自定义错误信息,将Jwtkey纳入配置文件,实施JWT黑白名单机制以及在入库数据时进行验证。通过这些方向,读者可以在加深对GoFrame和Golang的理解的同时,提升编程技能,并在业务优化中获得更多经验。' +--- + +首先,感谢每个读者能够耐心看完本书!然后,这里还有一些学习方向提供给大家。作者本人水平有限,不足之处还请海涵。希望大家事业能够步步高升,蒸蒸日上! +## 进一步的学习方向与建议 +--- +在本书所有开发的功能中,作者预留了一些业务优化空间,不妨试试? +- 中间件的进一步使用,新增后置中间件,自定义错误信息; +- 将`Jwtkey`纳入到`config.yaml`中,而不是直接存放在代码中; +- 实现`JWT`的黑白名单机制; +- 新增/编辑单词时没有对入库数据进行验证,比如单词掌握程度。 + +在优化这些业务的同时,您需要大量的查询相关文档,在这个过程中,您会加深`GoFrame`乃至`Golang`的理解。 + +## 来杯咖啡 +--- +**感谢每个支持者!** + +![功能清单](../assets/coffee.jpg) + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..59a79c631df --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.1.\345\211\215\347\275\256\350\257\264\346\230\216.md" @@ -0,0 +1,36 @@ +--- +title: '4.1.前置说明' +hide_title: true +slug: '/course/starbook/word-overview' +keywords: [GoFrame,CRUD操作,单词管理,RESTful架构,Web服务,HTTP方法,增删查改,资源管理,URI,客户端接口] +description: '程序员的核心技能CRUD操作,通过创建、读取、更新和删除操作完成单词管理。RESTful是一种设计原则,实现资源管理的简单灵活的Web服务架构,使用URI访问资源,通过GET、POST、PUT、PATCH、DELETE等HTTP动词进行操作确保系统之间的通信' +--- +在本章中,您将学习到程序员的核心技能——大名鼎鼎的增删查改操作,即`Create`(创建)、`Read`(读取)、`Update`(更新)、`Delete`(删除),简称为`CRUD`。这些操作构成了大多数应用程序的基础功能,通过它们我们将完成以下业务: +- 新增单词; +- 编辑单词; +- 单词分页列表; +- 单词详情; +- 删除单词。 +## `RESTful`简介 +--- +`RESTful`是一种基于`REST(Representational State Transfer)`架构风格的Web服务设计原则。它主要用于创建和访问Web资源,并具有以下特点: +- **资源**:`RESTful`服务中的每一个对象都被视为资源,可以通过`URI`(统一资源标识符)进行访问。例如,`/words`可以表示单词资源; +- **HTTP动词**:`RESTful`服务主要使用`HTTP Method` 来执行操作: + - `GET`:读取资源 + - `POST`:创建资源 + - `PUT`:更新资源 + - `PATCH`:更新部分资源 + - `DELETE`:删除资源 +- **表现层状态转移**:客户端通过请求特定的`URI`来操作资源,服务器通过响应返回资源的表现形式(通常为JSON或XML)。 +- **统一接口**:通过标准化的接口设计,使得不同的系统能够互相通信。 + +通过这些原则,`RESTful`架构使得`Web`服务更加简洁、灵活和可扩展。以单词的`CRUD`为例。 + +| 操作 | Method | URI | 描述 | +| ----------- | ------ | ------------- | -------------- | +| 创建 (`Create`) | `POST` | `/words` | 创建一个新的单词 | +| 读取 (`Read`) | `GET` | `/words` | 获取所有单词列表 | +| 读取 (`Read`) | `GET` | `/words/{id}` | 根据`ID`获取单个单词 | +| 更新 (`Update`) | `PUT` | `/words/{id}` | 更新指定`ID`的单词 | +| 更新 (`Update`) | `PATCH` | `/words/{id}` | 更新指定`ID`的单词的部分字段 | +| 删除 (`Delete`) | `DELETE` | `/words/{id}` | 删除指定`ID`的单词 | diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..31c9e5b922f --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.2.\346\225\260\346\215\256\346\250\241\345\236\213.md" @@ -0,0 +1,53 @@ +--- +title: '4.2.数据模型' +hide_title: true +slug: '/course/starbook/word-model' +keywords: [建立数据表,单词管理,GoFrame框架,数据模型生成,自定义数据模型,单词定义,中文翻译,发音记录,掌握程度,程序设计技巧] +description: '创建新的数据表保存单词信息,并加入联合唯一索引限制用户重复添加。生成数据模型并自定义ProficiencyLevel类型,用于表示单词掌握程度,定义五个等级。此种使用固定枚举值的技巧提升代码可读性维护性,适用于多种状态场景。' +--- +## 建立数据表 +--- +建立一张新的数据表,用以保存单词,并且加上联合唯一索引`uid, word`,限制单个用户不能添加相同的单词。 +```sql +CREATE TABLE words ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + uid INT UNSIGNED NOT NULL, + word VARCHAR ( 255 ) NOT NULL, + definition TEXT, + example_sentence TEXT, + chinese_translation VARCHAR ( 255 ), + pronunciation VARCHAR ( 255 ), + proficiency_level SMALLINT UNSIGNED, + created_at DATETIME, + updated_at DATETIME +); + +ALTER TABLE words ADD UNIQUE (uid, word); +``` + +| 字段名 | 类型 | 解释 | +| --------------------- | -------------- | ---------------- | +| `id` | `INT UNSIGNED` | 主键,自动递增,唯一标识单词 | +| `uid` | `INT UNSIGNED` | 用户id,标记该单词所属用户 | +| `word` | `VARCHAR(255)` | 单词,不允许为空 | +| `definition` | `TEXT` | 单词定义 | +| `example_sentence` | `TEXT` | 示例句子 | +| `chinese_translation` | `VARCHAR(255)` | 单词的中文翻译 | +| `pronunciation` | `VARCHAR(255)` | 单词的发音 | +| `proficiency_level` | `SMALLINT` | 单词掌握程度,1级最低,5级最高 | +| `created_at` | `DATETIME` | 记录创建时间 | +| `updated_at` | `DATETIME` | 记录最后更新时间 | + +## 生成单词数据模型 +--- +```bash +$ gf gen dao +``` + +执行成功后,会生成下列文件: +```text +internal/model/do/words.go +internal/model/entity/words.go +internal/dao/internal/words.go +internal/dao/words.go +``` diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..208e13e675f --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.3.\346\226\260\345\242\236\345\215\225\350\257\215.md" @@ -0,0 +1,248 @@ +--- +title: '4.3.新增单词' +hide_title: true +slug: '/course/starbook/word-create' +keywords: [GoFrame,RESTful,API,单词创建,数据接收层,逻辑操作层,数据模型,数据库一致性,单词表,控制器注册] +description: '使用GoFrame框架实现RESTful风格的单词创建API,包括架构设计中的API层和逻辑层的职责划分,强调数据结构不宜透传。详细解说如何在逻辑层中确保数据的一致性并避免重复输入,以及在控制器中调用多层逻辑以保持功能单一。此外,还涵盖了控制器的路由注册及接口测试的方法。' +--- +根据`RESTful`风格,新增应该使用`POST`方式,祭出我们的三板斧,无情的搬砖式开发。 +## 添加 Api +--- +*api/words/v1/words.go* +```go +type ProficiencyLevel uint + +const ( + ProficiencyLevel1 ProficiencyLevel = iota + 1 + ProficiencyLevel2 + ProficiencyLevel3 + ProficiencyLevel4 + ProficiencyLevel5 +) + +type CreateReq struct { + g.Meta `path:"words" method:"post" sm:"创建" tags:"单词"` + Word string `json:"word" v:"required|length:1,100" dc:"单词"` + Definition string `json:"definition" v:"required|length:1,300" dc:"单词定义"` + ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"例句"` + ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"中文翻译"` + Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"发音"` + ProficiencyLevel ProficiencyLevel `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"` +} + +type CreateRes struct { +} +``` + +在这里我们自定义了一个数据类型`ProficiencyLevel`,表示单词的掌握程度,并定义了五个枚举值:`ProficiencyLevel1-5`从低到高表示级别。 + +这种自定义类型加上固定枚举值的方式是一种高级的程序设计技巧,可以广泛用在各类状态上,比如订单状态,项目阶段等。新手在编程总喜欢使用`int`一把梭,最后造成代码里全是`1,2,3...`这种数字状态,导致代码的可读性和可维护性较差。 + +## 编写Logic +--- +同样的,定义`Words`对象,新建`New`函数用作实例化。 + +*internal/logic/words/words.go* +```go +package words + +type Words struct { +} + +func New() *Words { + return &Words{} +} +``` + +*internal/logic/words/words.go* +```go +... + +type CreateInput struct { + Uid uint + Word string + Definition string + ExampleSentence string + ChineseTranslation string + Pronunciation string + ProficiencyLevel v1.ProficiencyLevel +} + +func (w *Words) Create(ctx context.Context, in CreateInput) error { + var cls = dao.Words.Columns() + + count, err := dao.Words.Ctx(ctx). + Where(cls.Uid, in.Uid). + Where(cls.Word, in.Word).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("单词已存在") + } + + _, err = dao.Words.Ctx(ctx).Data(do.Words{ + Uid: in.Uid, + Word: in.Word, + Definition: in.Definition, + ExampleSentence: in.ExampleSentence, + ChineseTranslation: in.ChineseTranslation, + Pronunciation: in.Pronunciation, + ProficiencyLevel: in.ProficiencyLevel, + }).Insert() + if err != nil { + return err + } + return nil +} +``` + +在`Logic`中我们也需要确保同一用户单词不能重复,和数据库保持一致。 + +### account logic +单词表中保存有`uid`字段,我们需要在`logic/users`包中封装一个`GetUid`函数提供`uid`。 + +*internal/logic/users/users_account.go* +```go +func (u *Users) GetUid(ctx context.Context) (uint, error) { + user, err := u.Info(ctx) + if err != nil { + return 0, err + } + return user.Id, nil +} +``` + +## Controller调用Logic +--- +在创建单词的控制器中,我们需要调用`account`和`words`两个`logic`,我们将他封装到控制器中。 + +*internal/controller/words/words_new.go* +```go +... + + +package words + +import ( + "star/api/words" + usersLogic "star/internal/logic/users" + wordsLogic "star/internal/logic/words" +) + +type ControllerV1 struct { + users *usersLogic.Users + words *wordsLogic.Words +} + +func NewV1() words.IWordsV1 { + return &ControllerV1{ + users: usersLogic.New(), + words: wordsLogic.New(), + } +} +``` + + +*internal/controller/words/words_v1_create.go* +```go +package words + +import ( + "context" + + "star/internal/model" + "star/api/words/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + err = c.words.Create(ctx, &model.WordInput{ + Uid: uid, + Word: req.Word, + Definition: req.Definition, + ExampleSentence: req.ExampleSentence, + ChineseTranslation: req.ChineseTranslation, + Pronunciation: req.Pronunciation, + ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel), + }) + return nil, err +} +``` + +在`Controller`中调用两个`Logic`层级的方法:`users.GetUid`和`words.Create`来实现功能。注意,不要在`words.Create`中直接调用`users.GetUid`,这样会加重`words`包的耦合。 + +最佳实验是,**尽量保证`Logic`函数的功能单一化,在`Controller`中多次调用`Logic`完成功能。** + +## 注册控制器 +--- +*internal/cmd/cmd.go* +```go +package cmd + +... + +var ( + Main = gcmd.Command{ + Name: "main", + Usage: "main", + Brief: "start http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Group("/v1", func(group *ghttp.RouterGroup) { + group.Bind( + users.NewV1(), + ) + group.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(middleware.Auth) + group.Bind( + account.NewV1(), + words.NewV1(), + ) + }) + }) + }) + s.Run() + return nil + }, + } +) +``` + +控制器注册到与`account.NewV1()`同一个路由组下,确保能经过`Auth`中间件。 + +## 接口测试 +--- +```bash +$ curl -X POST http://127.0.0.1:8000/v1/words \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "word": "example", + "definition": "A representative form or pattern.", + "example_sentence": "This is an example sentence.", + "chinese_translation": "例子", + "pronunciation": "ɪɡˈzɑːmp(ə)l", + "proficiency_level": 3 + }' + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` + +执行命令,查询数据是否正常添加: +```sql +$ SELECT * FROM words; +``` + +| id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at | +| --- | --- | ------- | --------------------------------- | ---------------------------- | ------------------- | ------------- | ----------------- | ------------------- | ------------------- | +| 1 | 1 | example | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 | diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..d1c350e9126 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.4.\347\274\226\350\276\221\345\215\225\350\257\215.md" @@ -0,0 +1,134 @@ +--- +title: '4.4.编辑单词' +hide_title: true +slug: '/course/starbook/word-update' +keywords: [GoFrame,单词更新,REST API,PUT请求,单词定义,唯一性检查,更新逻辑,权限验证,数据库操作,错误处理] +description: '利用GoFrame框架中的REST API来更新单词信息,包括单词的定义、例句、中文翻译及发音等。强调了在编辑操作中的唯一性检查和权限验证逻辑,并通过代码示例展示了如何使用数据库进行数据更新及错误处理。' +--- +编辑单词使用`PUT`方式,代表更新资源。 +## 添加 Api +--- +*api/words/v1/words.go* +```go +type UpdateReq struct { + g.Meta `path:"words/{id}" method:"put" sm:"更新" tags:"单词"` + Id uint `json:"id" v:"required"` + Word string `json:"word" v:"required|length:1,100" dc:"单词"` + Definition string `json:"definition" v:"required|length:1,300" dc:"单词定义"` + ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"例句"` + ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"中文翻译"` + Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"发音"` + ProficiencyLevel ProficiencyLevel `json:"proficiency_level" v:"required|between:1,5" dc:"熟练度,1最低,5最高"` +} + +type UpdateRes struct { +} +``` + +## 编写Logic +--- +*internal/logic/words/words.go* +```go +package words + +... + +type UpdateInput struct { + Uid uint + Word string + Definition string + ExampleSentence string + ChineseTranslation string + Pronunciation string + ProficiencyLevel v1.ProficiencyLevel +} + +func (w *Words) Update(ctx context.Context, id uint, in UpdateInput) error { + var cls = dao.Words.Columns() + + count, err := dao.Words.Ctx(ctx). + Where(cls.Uid, in.Uid). + Where(cls.Word, in.Word). + WhereNot(cls.Id, id). + Count() + if err != nil { + return err + } + if count > 0 { + return gerror.New("单词已存在") + } + + _, err = dao.Words.Ctx(ctx).Data(do.Words{ + Word: in.Word, + Definition: in.Definition, + ExampleSentence: in.ExampleSentence, + ChineseTranslation: in.ChineseTranslation, + Pronunciation: in.Pronunciation, + ProficiencyLevel: in.ProficiencyLevel, + }).Where(cls.Id, id).Where(cls.Uid, in.Uid).Update() + if err != nil { + return err + } + return nil +} + +... +``` + +必须在`ORM`链式中加上`Uid`判断条件,以防止越权,后续的查询,删除动作同样如此。另外加上`WhereNot`,以忽略自身的单词重复检测。 + +## Controller调用Logic +--- +*internal/controller/words/words_v1_update.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" + "star/internal/logic/words" +) + +func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + err = c.words.Update(ctx, req.Id, words.UpdateInput{ + Word: req.Word, + Definition: req.Definition, + ExampleSentence: req.ExampleSentence, + ChineseTranslation: req.ChineseTranslation, + Pronunciation: req.Pronunciation, + ProficiencyLevel: req.ProficiencyLevel, + }) + return nil, err +} +``` + +## 接口测试 +--- +```bash +$ curl -X PUT http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + -d '{ + "word": "example_update", + "definition": "A representative form or pattern.", + "example_sentence": "This is an example sentence.", + "chinese_translation": "例子", + "pronunciation": "ɪɡˈzɑːmp(ə)l", + "proficiency_level": 3 + }' + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` +执行命令,查询数据是否正常更新: +```sql +$ SELECT * FROM words; +``` + +| id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at | +| --- | --- | -------------- | --------------------------------- | ---------------------------- | ------------------- | ------------- | ----------------- | ------------------- | ------------------- | +| 1 | 1 | example_update | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 | diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..68c6bbb76a7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.5.\345\215\225\350\257\215\345\210\206\351\241\265\345\210\227\350\241\250.md" @@ -0,0 +1,157 @@ +--- +title: '4.5.单词分页列表' +hide_title: true +slug: '/course/starbook/word-list' +keywords: [GoFrame,单词查询,API分页,模糊查询,数据结构,单词列表,接口测试,GoFrame框架,API请求,数据处理] +description: '使用GET方式查询单词的分页列表,包含模糊查询功能。定义结构体保存单词字段,包括id、单词、定义及熟练度。在GoFrame框架中编写Logic,用于处理数据的查询和分页操作。通过Controller调用Logic,实现数据的获取和返回,支持综合的接口测试。' +--- +查询单词分页列表使用`GET`方式,并且提供一些入参: + +| 字段名 | 类型 | json | valid | 描述 | +| ---- | -------- | ------------- | --------------- | --------- | +| Word | `string` | `json:"word"` | `length:1,100` | 模糊查询单词 | +| Page | `int` | `json:"page"` | `min:1` | 页码,默认1 | +| Size | `int` | `json:"size"` | `between:1,100` | 每页数量,默认10 | +## 添加Api +--- +定义一个结构体`List`保存一个单词的字段。在`ListRes`中返回一个`List`切片,表示单词列表,`Total`表示所有单词数量,返回给前端方便做分页操作。 + +*api/words/v1/words.go* +```go +... + +type List struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ProficiencyLevel model.ProficiencyLevel `json:"proficiencyLevel"` +} + +type ListReq struct { + g.Meta `path:"words" method:"get" sm:"列表" tags:"单词"` + Word string `json:"word" v:"length:1,100" dc:"模糊查询单词"` + Page int `json:"page" v:"min:1" dc:"页码,默认1"` + Size int `json:"size" v:"between:1,100" dc:"每页数量,默认10"` +} + +type ListRes struct { + List []List `json:"list"` + Total uint `json:"total"` +} +``` + +## 编写Logic +--- +*internal/logic/words/words.go* +```go +... + +type ListInput struct { + Uid uint + Word string + Page int + Size int +} + +func (w *Words) List(ctx context.Context, in ListInput) (list []entity.Words, total int, err error) { + // 对于查询初始值的处理 + if in.Page == 0 { + in.Page = 1 + } + if in.Size == 0 { + in.Size = 15 + } + + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + // 组成查询链 + if in.Uid > 0 { + orm = orm.Where(cls.Uid, in.Uid) + } + + // 模糊查询 + if len(in.Word) != 0 { + orm = orm.WhereLike(cls.Word, "%"+in.Word+"%") + } + orm = orm.OrderDesc(cls.CreatedAt).OrderDesc(cls.Id).Page(in.Page, in.Size) + if err = orm.ScanAndCount(&list, &total, true); err != nil { + return + } + return +} +``` + +上述代码用到了`orm.WhereLike(cls.Word, "%"+in.Word+"%")`,它是模糊查询的意思,最终会生成`word LIKE '%{word}%'` 子句。 + +`AllAndCount`用于同时查询数据记录列表及总数量,一般用于分页查询场景中,简化分页查询逻辑。 + +## Controller调用Logic +--- +*internal/controller/words/words_v1_list.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" + "star/internal/logic/words" +) + +func (c *ControllerV1) List(ctx context.Context, req *v1.ListReq) (res *v1.ListRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + wordList, total, err := c.words.List(ctx, words.ListInput{ + Uid: uid, + Word: req.Word, + Page: req.Page, + Size: req.Size, + }) + if err != nil { + return nil, err + } + + var list []v1.List + for _, v := range wordList { + list = append(list, v1.List{ + Id: v.Id, + Word: v.Word, + Definition: v.Definition, + ProficiencyLevel: v1.ProficiencyLevel(v.ProficiencyLevel), + }) + } + + return &v1.ListRes{ + List: list, + Total: uint(total), + }, nil +} +``` + +## 接口测试 +--- +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": { +        "list": [ +            { +                "id": 1, +                "word": "example_update", +                "definition": "A representative form or pattern.", +                "proficiencyLevel": 3 +            } +        ], +        "total": 1 +    } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" new file mode 100644 index 00000000000..01669a56e28 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.6.\345\215\225\350\257\215\350\257\246\346\203\205.md" @@ -0,0 +1,114 @@ +--- +title: '4.6.单词详情' +hide_title: true +slug: '/course/starbook/word-detail' +keywords: [GoFrame,单词详情,中文翻译,模糊路由,例句,Pronunciation,接口测试,GoFrame框架,API,详细信息] +description: '单词详情接口通过GET请求获取不在列表中的单词详细信息,包含例句、中文翻译、发音等字段。使用模糊路由匹配,并通过接口的逻辑层实现对数据库的查询操作。' +--- +单词详情同样使用`GET`方式,用作获取单词的详细信息,会包含例句,中文翻译等列表中不存在的字段。 +## 添加Api +--- +*api/words/v1/words.go* +```go +... + +type DetailReq struct { + g.Meta `path:"words/{id}" method:"get" sm:"详情" tags:"单词"` + Id uint `json:"id" v:"required"` +} + +type DetailRes struct { + Id uint `json:"id"` + Word string `json:"word"` + Definition string `json:"definition"` + ExampleSentence string `json:"exampleSentence"` + ChineseTranslation string `json:"chineseTranslation"` + Pronunciation string `json:"pronunciation"` + ProficiencyLevel ProficiencyLevel `json:"proficiencyLevel"` + CreatedAt *gtime.Time `json:"createdAt"` + UpdatedAt *gtime.Time `json:"updatedAt"` +} +``` + +`words/{id}`是一种模糊路由匹配方式,它会识别类似`words/1`,`words/2`,`words/abc`这种路由,并将匹配到的值赋予`Id`字段。`Id`字段的类型是`uint`,如果值不合法,则会使用`uint`的零值。比如访问`words/abc`,`Id`字段的值就会是`0`。 + +## 编写Logic +--- +*internal/logic/words/words.go* +```go +... + +func (w *Words) Detail(ctx context.Context, uid, id uint) (word *entity.Words, err error) { + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + orm = orm.Where(cls.Id, id) + if uid > 0 { + orm = orm.Where(cls.Uid, uid) + } + err = orm.Scan(&word) + return +} +``` + +## Controller调用Logic +--- +*internal/controller/words/words_v1_detail.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) Detail(ctx context.Context, req *v1.DetailReq) (res *v1.DetailRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + word, err := c.words.Detail(ctx, uid, req.Id) + if err != nil { + return nil, err + } + + return &v1.DetailRes{ + Id: word.Id, + Word: word.Word, + Definition: word.Definition, + ExampleSentence: word.ExampleSentence, + ChineseTranslation: word.ChineseTranslation, + Pronunciation: word.Pronunciation, + ProficiencyLevel: v1.ProficiencyLevel(word.ProficiencyLevel), + CreatedAt: word.CreatedAt, + UpdatedAt: word.UpdatedAt, + }, nil +} +``` + +## 接口测试 +--- +```bash +$ curl -X GET http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": { +        "id": 1, +        "word": "example_update", +        "definition": "A representative form or pattern.", +        "exampleSentence": "This is an example sentence.", +        "chineseTranslation": "例子", +        "pronunciation": "ɪɡˈzɑːmp(ə)l", +        "proficiencyLevel": 3, +        "createdAt": "2024-11-14 15:40:54", +        "updatedAt": "2024-11-14 16:09:37" +    } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" new file mode 100644 index 00000000000..e87a3ab266e --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.7.\345\210\240\351\231\244\345\215\225\350\257\215.md" @@ -0,0 +1,78 @@ +--- +title: '4.7.删除单词' +hide_title: true +slug: '/course/starbook/word-delete' +keywords: [GoFrame框架,DELETE方法,API删除单词,Logic层实现,接口开发,控制器调用,请求响应,上下文管理,路径参数,数据库操作] +description: '通过API实现单词删除功能,使用DELETE方法请求删除指定ID的单词。Logic层根据用户ID和单词ID进行数据库删除操作,控制器层负责接收请求并调用逻辑删除单词。提供了详细的接口测试步骤用于验证功能实现。' +--- +删除单词使用`DELETE`方式。 +## 添加Api +--- +*api/words/v1/words.go* +```go +... +type DeleteReq struct { + g.Meta `path:"words/{id}" method:"delete" sm:"删除" tags:"单词"` + Id uint `json:"id" v:"required"` +} + +type DeleteRes struct { +} +``` + +## 编写Logic +--- +*internal/logic/words/words.go* +```go +... + +func (w *Words) Delete(ctx context.Context, uid, id uint) (err error) { + var ( + cls = dao.Words.Columns() + orm = dao.Words.Ctx(ctx) + ) + orm = orm.Where(cls.Id, id) + if uid > 0 { + orm = orm.Where(cls.Uid, uid) + } + _, err = orm.Delete() + return +} +``` + +## Controller调用Logic +--- +*internal/controller/words/words_v1_delete.go* +```go +package words + +import ( + "context" + + "star/api/words/v1" +) + +func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { + uid, err := c.users.GetUid(ctx) + if err != nil { + return nil, err + } + + err = c.words.Delete(ctx, uid, req.Id) + return +} +``` + +## 接口测试 +--- +```bash +$ curl -X DELETE http://127.0.0.1:8000/v1/words/1 \ + -H "Authorization: eyJhbGci...5U" \ + -H "Content-Type: application/json" \ + +{ +    "code": 0, +    "message": "", +    "data": null +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" new file mode 100644 index 00000000000..5f758eb479a --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/4.8.\346\200\273\347\273\223.md" @@ -0,0 +1,19 @@ +--- +title: '4.8.总结' +hide_title: true +slug: '/course/starbook/word-summary' +keywords: [GoFrame,单词管理,创建单词,读取单词,更新单词,删除单词,RESTful API,数据库操作,分页查询,模糊查询] +description: '基于RESTful开发单词管理功能,包括创建、读取、更新和删除单词,实现枚举值管理状态,采用分层数据模型进行数据操作,支持联合索引和分页、模糊查询的优化技术。' +--- +在本章节中,我们基于`RESTful`开发了单词相关的基础功能: +- **创建单词(Create)**:通过`POST`请求,将单词及其相关信息存储到数据库中; +- **读取单词(Read)**:通过`GET`请求,获取单词及其详细信息,支持分页查询和`word`模糊查询; +- **更新单词(Update)**:通过`PUT`请求,更新已存在单词的信息; +- **删除单词(Delete)**:通过`DELETE`请求,删除指定的单词。 + +学会了一些进阶技巧: +- 使用枚举值管理状态; +- 分层数据模型的思想,限制透传; +- 联合索引; +- 分页查询; +- 模糊查询。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..23cf70af621 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/starbook/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206/\347\254\254\345\233\233\347\253\240-\345\215\225\350\257\215\347\256\241\347\220\206.md" @@ -0,0 +1,27 @@ +--- +title: '第四章-单词管理' +hide_title: true +sidebar_position: 4 +slug: '/course/starbook/word' +keywords: [GoFrame,GoFrame框架,文档,网站,课程,教程,指南,开发,框架] +description: '使用GoFrame构建单词管理的课程内容,通过详细的指南帮助用户更好地理解和应用GoFrame框架。' +--- + + + + + + +import DocCardList from '@theme/DocCardList'; + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" "b/versioned_docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" new file mode 100644 index 00000000000..51cc0dabf9e --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/\347\244\276\345\214\272\346\225\231\347\250\213.md" @@ -0,0 +1,24 @@ +--- +slug: '/course' +title: '社区教程' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame,GoFrame框架,学习资源,社区教程,编纂资料,教程提交,代码协作,资料更新,分享资源,教程整合] +description: '整合社区中优质的GoFrame框架学习资源,方便大家学习和使用。感谢社区老师们贡献的学习资料,欢迎为他们的努力续上一杯咖啡。本章节内容将不断更新,欢迎参与贡献。' +--- + +大家好,我们将社区中比较优质的学习资源整合到这里,了便大家学习和使用`GoFrame`框架 🚀。 + +感谢所有辛苦编纂学习资料的社区老师们 💖💐!如果大家觉得他们做的不错,也欢迎大家给老师们续上一杯咖啡 ☕️! + +本章节资料会陆续更新,也欢迎感兴趣的老师们们踊跃报名分享学习资料! + +参与方式:将您的教程通过`PR`的方式提交,我们将提供全程协助。 + +仓库地址:https://github.com/gogf/gf-site + + + + + + diff --git "a/versioned_docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" "b/versioned_docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" new file mode 100644 index 00000000000..1aa1a56b732 --- /dev/null +++ "b/versioned_docs/version-2.8.x/course/\350\247\206\351\242\221\345\205\245\351\227\250\346\225\231\347\250\213.md" @@ -0,0 +1,29 @@ +--- +slug: '/course/bilibili-video' +title: '视频入门教程' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame,GoFrame框架,入门教程,视频教程,无限十三年,学习视频,循序渐进,Bilibili,gf框架,编程] +description: '无限十三年老师制作的入门系列视频教程,通过循序渐进的方式非常适合初学者学习,提供了构建GoFrame框架的基础知识和实践经验,帮助用户快速上手并掌握GoFrame的使用技巧。' +--- + + + +无限十三年老师做的挺不错的 **入门系列视频教程** 💖, +**该视频循序渐进,非常适合入门学习**: +https://www.bilibili.com/video/BV1Uu4y1u7kX + +![goframe视频入门教程](QQ_1731756142305.png) + + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" new file mode 100644 index 00000000000..c98f0b7fd50 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Cookie.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/web/cookie' +title: 'Cookie' +sidebar_position: 6 +hide_title: true +keywords: [Cookie,GoFrame,GoFrame框架,ghttp,SessionId,接口文档,SetCookie,HTTP Server,Session,web开发] +description: '在GoFrame框架中使用Cookie进行会话管理。通过ghttp.Request对象,开发者可以轻松获取、设置和删除Cookie。还讨论了SessionId的获取和设置,Cookie的过期时间处理,以及在控制器中继承和使用会话对象的简易方法。这些功能为Web开发者提供了强大的工具来管理用户会话,确保Web应用的灵活性和应变能力。' +--- + +## 基本介绍 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Cookie](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Cookie) + +常用方法: + +```go +type Cookie + func GetCookie(r *Request) *Cookie + func (c *Cookie) Contains(key string) bool + func (c *Cookie) Flush() + func (c *Cookie) Get(key string, def ...string) string + func (c *Cookie) GetSessionId() string + func (c *Cookie) Map() map[string]string + func (c *Cookie) Remove(key string) + func (c *Cookie) RemoveCookie(key, domain, path string) + func (c *Cookie) Set(key, value string) + func (c *Cookie) SetCookie(key, value, domain, path string, maxAge time.Duration, httpOnly ...bool) + func (c *Cookie) SetHttpCookie(httpCookie *http.Cookie) + func (c *Cookie) SetSessionId(id string) +``` + +任何时候都可以通过 `*ghttp.Request` 对象获取到当前请求对应的 `Cookie` 对象,因为 `Cookie` 和 `Session` 都是和请求会话相关,因此都属于 `ghttp.Request` 的成员对象,并对外公开。 `Cookie` 对象不需要手动 `Close`,请求流程结束后, `HTTP Server` 会自动关闭掉。 + +此外, `Cookie` 中封装了两个 `SessionId` 相关的方法: + +1. `Cookie.GetSessionId()` 用于获取当前请求提交的 `SessionId`,每个请求的 `SessionId` 都是唯一的,并且伴随整个请求流程,该值可能为空。 +2. `Cookie.SetSessionId(id string)` 用于自定义设置 `SessionId` 到 `Cookie` 中,返回给客户端(往往是浏览器)存储,随后客户端每一次请求在 `Cookie` 中可带上该 `SessionId`。 + +在设置 `Cookie` 变量的时候可以给定过期时间,该时间为可选参数,默认的 `Cookie` 过期时间为一年。 +:::tip +默认的 `SessionId` 在 `Cookie` 中的存储名称为 `gfsession`。 +::: +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/cookie", func(r *ghttp.Request) { + datetime := r.Cookie.Get("datetime") + r.Cookie.Set("datetime", gtime.Datetime()) + r.Response.Write("datetime:", datetime) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行外层的 `main.go`,可以尝试刷新页面 [http://127.0.0.1:8199/cookie](http://127.0.0.1:8199/cookie) ,显示的时间在一直变化。 + +对于控制器对象而言,从基类控制器中继承了很多会话相关的对象指针,可以看做alias,可以直接使用,他们都是指向的同一个对象: + +```go +type Controller struct { + Request *ghttp.Request // 请求数据对象 + Response *ghttp.Response // 返回数据对象(r.Response) + Server *ghttp.Server // WebServer对象(r.Server) + Cookie *ghttp.Cookie // COOKIE操作对象(r.Cookie) + Session *ghttp.Session // SESSION操作对象 + View *View // 视图对象 +} +``` + +由于对于Web开发者来讲, `Cookie` 都已经是非常熟悉的组件了,相关 `API` 也非常简单,这里便不再赘述。 + +## `Cookie` 会话过期 + +`Cookie` 的有效期默认是1年,如果我们期望Cookie随着用户的浏览会话过期,像这样: + +![](/markdown/6aca8ffefa9db267e2a4ecf1423ba6be.png) + +那么我们仅需要通过 `SetCookie` 来设置 `Cookie` 键值对并将 `maxAge` 参数设置为 `0` 即可。像这样: + +``` +r.Cookie.SetCookie("MyCookieKey", "MyCookieValue", "", "/", 0) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" new file mode 100644 index 00000000000..f50897b3dc2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\344\273\243\347\220\206Proxy\350\256\276\347\275\256.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/web/http-client-proxy' +title: 'HTTPClient-代理Proxy设置' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTP客户端,代理设置,SetProxy,Proxy方法,http代理,socks5代理,链式调用,HTTP请求] +description: '在GoFrame框架的HTTP客户端中设置代理服务器地址,支持http和socks5两种形式。通过SetProxy和Proxy方法,用户可以轻松配置代理,实现对外网资源的访问,包括普通调用示例和链式调用示例,帮助用户快速掌握代理功能的使用。' +--- + +## 代理 `Proxy` 设置 + +HTTP客户端发起请求时可以设置代理服务器地址 `proxyURL`,该该特性使用 `SetProxy*` 相关方法实现。代理主要支持 `http` 和 `socks5` 两种形式,分别为 `http://USER:PASSWORD@IP:PORT` 或 `socks5://USER:PASSWORD@IP:PORT` 形式。 + +方法列表: + +```go +func (c *Client) SetProxy(proxyURL string) +func (c *Client) Proxy(proxyURL string) *Client +``` + +我们来看下客户端设置 `proxyURL` 的示例。 + +## 普通调用示例 + +使用 `SetProxy` 配置方法。 + +```go +client := g.Client() +client.SetProxy("http://127.0.0.1:1081") +client.SetTimeout(5 * time.Second) +response, err := client.Get(gctx.New(), "https://api.ip.sb/ip") +if err != nil { + fmt.Println(err) +} +response.RawDump() +``` + +## 链式调用示例 + +使用 `Proxy` 链式方法。 + +```go +client := g.Client() +response, err := client.Proxy("http://127.0.0.1:1081").Get(gctx.New(), "https://api.ip.sb/ip") +if err != nil { + fmt.Println(err) +} +fmt.Println(response.RawResponse()) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..ede4f604ab2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/web/http-client-example' +title: 'HTTPClient-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [HTTP,GoFrame,GoFrame框架,HTTP客户端,GET请求,POST请求,JSON数据,DELETE请求,ghttp客户端,网络请求] +description: '使用GoFrame框架通过基本的HTTP客户端操作来发送GET、POST、DELETE请求,并处理返回值。本文还讨论了如何使用POST方法发送JSON数据、使用多参数、map类型参数进行请求。同时,提供了*Bytes、*Content和*Var方法的简要介绍,以帮助开发者更便捷地处理HTTP请求和响应内容。' +--- + +## 基本使用 + +最基本的 `HTTP` 客户端使用是通过 `HTTP Method` 同名的几个操作方法来发送请求, **但需要注意的是返回的结果对象需要执行 `Close` 防止内存溢出**。我们来看几个 `HTTP` 客户端请求的简单示例。 + +### 发送 `GET` 请求,打印出返回值 + +```go +if r, err := g.Client().Get(ctx, "https://goframe.org"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +### 发送 `GET` 请求,下载远程文件 + +```go +if r, err := g.Client().Get(ctx, "https://goframe.org/cover.png"); err != nil { + panic(err) +} +defer r.Close() +gfile.PutBytes("/Users/john/Temp/cover.png", r.ReadAll()) +``` + +下载文件操作,小文件下载非常简单。需要注意的是,如果远程文件比较大时,服务端会分批返回数据,因此会需要客户端发多个 `GET` 请求,每一次通过 `Header` 来请求分批的文件范围长度,感兴趣的同学可自行研究相关细节。 + +### 发送 `POST` 请求,打印出返回值 + +```go +if r, err := g.Client().Post(ctx, "http://127.0.0.1:8199/form", "name=john&age=18"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +传递多参数的时候用户可以使用 `&` 符号进行连接,注意参数值往往需要通过 `gurl.Encode` 编码一下。 + +### 发送 `POST` 请求,参数为 `map` 类型,打印出返回值 + +```go +if r, err := g.Client().Post( + ctx, + "http://127.0.0.1:8199/form", + g.Map{ + "submit" : "1", + "callback" : "http://127.0.0.1/callback?url=http://baidu.com", + } +)); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +传递多参数的时候用户可以使用 `&` 符号进行连接,也可以直接使用 `map`( **其实之前也提到,任意数据类型都支持,包括 `struct`**)。 + +### 发送 `POST` 请求,参数为 `JSON` 数据,打印出返回值 + +```go +if r, err := g.Client().Post( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +可以看到,通过 `ghttp` 客户端发送 `JSON` 数据请求非常方便,直接通过 `Post` 方法提交即可。当没有显式设置 `ContentType` 时,客户端会自动识别参数类型并将请求的 `Content-Type` 设置为 `application/json`。 + +### 发送 `DELETE` 请求,打印出返回值 + +```go +if r, err := g.Client().Delete(ctx, "http://user.svc/v1/user/delete/1", "10000"); err != nil { + panic(err) +} +defer r.Close() +fmt.Println(r.ReadAllString()) +``` + +## `*Bytes` 及 `*Content` 方法 + +以 `Bytes` 及 `Content` 后缀结尾的请求方法为直接获取返回内容的 **快捷方法**,这些方法将会自动读取服务端返回内容 **并自动关闭请求连接**。 `*Bytes` 方法用于获取 `[]byte` 类型结果, `*Content` 方法用于获取 `string` 类型结果。 **需要注意的是,如果请求执行失败,返回内容将会为空。** + +### 发送 `GET` 请求,打印出返回值 + +```go + // 返回content为[]bytes类型 + content := g.Client().GetBytes(ctx, "https://goframe.org") +``` + +```go + // 返回content为string类型 + content := g.Client().GetContent(ctx, "https://goframe.org") +``` + +### 发送 `POST` 请求,打印出返回值 + +```go +// 返回content为[]bytes类型 +content := g.Client().PostBytes( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +) +``` + +```go +// 返回content为string类型 +content := g.Client().PostContent( + ctx, + "http://user.svc/v1/user/create", + `{"passport":"john","password":"123456","password-confirm":"123456"}`, +) +``` + +## `*Var` 方法 + +以 `Var` 后缀结尾的请求方法直接请求并获取 `HTTP` 接口结果为 `g.Var` 泛型类型 **便于下一步执行类型转换,特别是将请求结果转换到结构体对象上**。往往用于服务端返回格式为 `JSON/XML` 的情况,通过返回的 `g.Var` 泛型对象可根据需要自动解析。此外,如果请求失败或者请求结果为空,会返回一个空的 `g.Var` 泛型对象,不影响转换方法调用。 + +使用示例: + +```go +type User struct { + Id int + Name string +} +``` + +```go +// Struct +var user *User +g.Client().GetVar(ctx, url).Scan(&user) +``` + +```go +// Struct数组 +var users []*User +g.Client().GetVar(ctx, url).Scan(&users) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..66a60a141b4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,46 @@ +--- +slug: '/docs/web/http-client-faq' +title: 'HTTPClient-常见问题' +sidebar_position: 9 +hide_title: true +keywords: [HTTPClient,GoFrame,GoFrame框架,gclient.Client,连接池,高效,资源使用,短连接请求,表单请求,ContentType] +description: '解释如何有效使用GoFrame框架中的gclient.Client对象,以提高效率和降低资源使用。包含gclient.Client对象复用的建议以及如何处理非法字符问题,通过示例演示设置正确的ContentType。' +--- + +## 是否需要保存复用创建的 `gclient.Client` 对象 + +无论是通过 `g.Client` 或者 `gclient.New` 方法创建 `gclient.Client` 对象,该对象都应该保存下来复用,而不是每一次都新建 `Client` 对象,这样可以提高效率、降低资源使用、使用方式对 `GC` 友好。该对象内建连接池设计,可充分管理大量的短连接请求。由于 `Client` 对象对资源消耗不是很高,所以很多同学可能都没太注意这个点。 + +什么情况下我应该新建 `gclient.Client` 对象而不是复用呢?你可以按照业务模块的解耦设计,每个业务模块单独管理维护自己的 `gclient.Client` 对象。也可以当针对不同的场景,使用 `Client` 的配置不同时,那么可以新建不同的 `Client` 来使用。 + +## `invalid semicolon separator in query` + +**问题原因**:默认 **表单请求** 中带 `;` 字符是非法的(需要 `urlencode`)。具体请参考讨论: [https://github.com/golang/go/issues/25192](https://github.com/golang/go/issues/25192) + +**错误示例**: + +```bash +curl localhost:8000/Execute -d '{ + "Component": "mysql", + "ResourceId": "cdb-gy6hm0ee", + "Port": 6379, + "SQL": "show databases;", + "UserName": "root", + "Password": "" +}' +``` + +**修复示例**: + +提交请求的时候需要注明 `ContentType`,例如这里应当标明是 `JSON` 请求。 + +```bash +curl -X POST -H "Content-Type: application/json" localhost:8000/Execute -d '{ + "Component": "mysql", + "ResourceId": "cdb-gy6hm0ee", + "Port": 6379, + "SQL": "show databases;", + "UserName": "root", + "Password": "" +}' +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" new file mode 100644 index 00000000000..854390f1d96 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\213\246\346\210\252\345\231\250\344\270\255\351\227\264\344\273\266.md" @@ -0,0 +1,180 @@ +--- +slug: '/docs/web/http-client-middleware' +title: 'HTTPClient-拦截器/中间件' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTPClient,拦截器,中间件,客户端请求,参数校验,签名生成,接口安全,请求拦截] +description: 'GoFrame框架中的HTTPClient拦截器/中间件特性,可用于全局请求拦截和参数校验。通过中间件,开发者可以在请求的前置和后置阶段插入自定义逻辑,修改提交参数或返回参数,实现签名参数注入等功能,确保接口参数的安全性。' +--- + +## 基本介绍 + +`HTTPClient` 支持强大的拦截器/中间件特性,该特性使得对于客户端的全局请求拦截及注入成为了可能,例如修改/注入提交参数、修改/注入返回参数、基于客户端的参数校验等等。中间件的注入通过以下方法实现: + +```go +func (c *Client) Use(handlers ...HandlerFunc) *Client +``` + +在中间件中通过 `Next` 方法执行下一步流程, `Next` 方法定义如下: + +```go +func (c *Client) Next(req *http.Request) (*Response, error) +``` + +## 中间件类型 + +`HTTPClient` 中间件功能同 `HTTPServer` 的中间件功能类似,同样也是分为了前置中间件和后置中间件两种。 + +### 前置中间件 + +处理逻辑位于 `Next` 方法之前,格式形如: + +```go +c := g.Client() +c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + // 自定义处理逻辑 + resp, err = c.Next(r) + return resp, err +}) +``` + +### 后置中间件 + +处理逻辑位于 `Next` 方法之后,格式形如: + +```go +c := g.Client() +c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + resp, err = c.Next(r) + // 自定义处理逻辑 + return resp, err +}) +``` + +## 使用示例 + +我们来一个代码示例更好介绍使用,该示例通过给客户端增加拦截器,对提交的JSON数据注入自定义的额外参数,这些额外参数实现对提交参数的签名生成体积签名相关参数提交,也就是实现一版简单的接口参数安全校验。 + +### 服务端 + +服务端的逻辑很简单,就是把客户端提交的 `JSON` 参数按照 `map` 解析后再构造成 `JSON` 字符串返回给客户端。 +:::note +往往服务端也需要通过中间件进行签名校验,我这里偷了一个懒,直接返回了客户端提交的数据。体谅一下文档维护作者😸。 +::: +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/", func(r *ghttp.Request) { + r.Response.Write(r.GetMap()) + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +### 客户端 + +客户端的逻辑是实现基本的客户端参数提交、拦截器注入、签名相关参数注入以及签名参数生成。 + +```go +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/crypto/gmd5" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/internal/json" + "github.com/gogf/gf/v2/net/gclient" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/util/guid" + "github.com/gogf/gf/v2/util/gutil" +) + +const ( + appId = "123" + appSecret = "456" +) + +// 注入统一的接口签名参数 +func injectSignature(jsonContent []byte) []byte { + var m map[string]interface{} + _ = json.Unmarshal(jsonContent, &m) + if len(m) > 0 { + m["appid"] = appId + m["nonce"] = guid.S() + m["timestamp"] = gtime.Timestamp() + var ( + keyArray = garray.NewSortedStrArrayFrom(gutil.Keys(m)) + sigContent string + ) + keyArray.Iterator(func(k int, v string) bool { + sigContent += v + sigContent += gconv.String(m[v]) + return true + }) + m["signature"] = gmd5.MustEncryptString(gmd5.MustEncryptString(sigContent) + appSecret) + jsonContent, _ = json.Marshal(m) + } + return jsonContent +} + +func main() { + c := g.Client() + c.Use(func(c *gclient.Client, r *http.Request) (resp *gclient.Response, err error) { + bodyBytes, _ := ioutil.ReadAll(r.Body) + if len(bodyBytes) > 0 { + // 注入签名相关参数,修改Request原有的提交参数 + bodyBytes = injectSignature(bodyBytes) + r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + r.ContentLength = int64(len(bodyBytes)) + } + return c.Next(r) + }) + content := c.ContentJson().PostContent(gctx.New(), "http://127.0.0.1:8199/", g.Map{ + "name": "goframe", + "site": "https://goframe.org", + }) + fmt.Println(content) +} +``` + +### 运行测试 + +先运行服务端: + +```bash +$ go run server.go + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|---------|---------|--------|-------|-------------------|------------- + default | default | :8199 | ALL | / | main.main.func1.1 | +----------|---------|---------|--------|-------|-------------------|------------- + +2021-05-18 09:23:41.865 97906: http server started listening on [:8199] +``` + +再运行客户端: + +```bash +$ go run client.go +{"appid":"123","name":"goframe","nonce":"12vd8tx23l6cbfz9k59xehk1002pixfo","signature":"578a90b67bdc63d551d6a18635307ba2","site":"https://goframe.org","timestamp":1621301076} +$ +``` + +可以看到,服务端接受到的参数多了多了几项,包括 `appid/nonce/timestamp/signature`,这些参数往往都是签名校验算法所需要的参数。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" new file mode 100644 index 00000000000..08a00bdf48c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\346\226\207\344\273\266\344\270\212\344\274\240.md" @@ -0,0 +1,200 @@ +--- +slug: '/docs/web/http-client-file-uploading' +title: 'HTTPClient-文件上传' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,HTTP客户端,文件上传,服务器接口,表单文件,GoFrame框架,单文件上传,多文件上传,文件路径,上传参数] +description: '使用GoFrame框架进行HTTP客户端文件上传,实现了方便的文件上传功能,并提供了三个主要的接口以支持单个和多个文件的上传。详细讲解了服务端及客户端的实现代码,并提供了自定义文件名称和规范路由接收上传文件的方法,适用于需要集成文件上传功能的开发场景。' +--- + +`GoFrame` 支持非常方便的表单文件上传功能,并且HTTP客户端对上传功能进行了必要的封装并极大简化了上传功能调用。 +:::warning +注意哦:上传文件大小受到 `ghttp.Server` 的 `ClientMaxBodySize` 配置影响: [https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig) 默认支持的上传文件大小为 `8MB`。 +::: +## 服务端 + +在服务端通过 `Request` 对象获取上传文件: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +// Upload uploads files to /tmp . +func Upload(r *ghttp.Request) { + files := r.GetUploadFiles("upload-file") + names, err := files.Save("/tmp/") + if err != nil { + r.Response.WriteExit(err) + } + r.Response.WriteExit("upload successfully: ", names) +} + +// UploadShow shows uploading simgle file page. +func UploadShow(r *ghttp.Request) { + r.Response.Write(` + + + GoFrame Upload File Demo + + +
    + + +
    + + + `) +} + +// UploadShowBatch shows uploading multiple files page. +func UploadShowBatch(r *ghttp.Request) { + r.Response.Write(` + + + GoFrame Upload Files Demo + + +
    + + + +
    + + + `) +} + +func main() { + s := g.Server() + s.Group("/upload", func(group *ghttp.RouterGroup) { + group.POST("/", Upload) + group.ALL("/show", UploadShow) + group.ALL("/batch", UploadShowBatch) + }) + s.SetPort(8199) + s.Run() +} +``` + +该服务端提供了3个接口: + +1. [http://127.0.0.1:8199/upload/show](http://127.0.0.1:8199/upload/show) 地址用于展示单个文件上传的H5页面; +2. [http://127.0.0.1:8199/upload/batch](http://127.0.0.1:8199/upload/batch) 地址用于展示多个文件上传的H5页面; +3. [http://127.0.0.1:8199/upload](http://127.0.0.1:8199/upload) 接口用于真实的表单文件上传,该接口同时支持单个文件或者多个文件上传; + +我们这里访问 [http://127.0.0.1:8199/upload/show](http://127.0.0.1:8199/upload/show) 选择需要上传的单个文件,提交之后可以看到文件上传成功到服务器上。 + +**关键代码说明** + +1. 我们在服务端可以通过 `r.GetUploadFiles` 方法获得上传的所有文件对象,也可以通过 `r.GetUploadFile` 获取单个上传的文件对象。 +2. 在 `r.GetUploadFiles("upload-file")` 中的参数 `"upload-file"` 为本示例中客户端上传时的表单文件域名称,开发者可以根据前后端约定在客户端中定义,以方便服务端接收表单文件域参数。 +3. 通过 `files.Save` 可以将上传的多个文件方便地保存到指定的目录下,并返回保存成功的文件名。如果是批量保存,只要任意一个文件保存失败,都将会立即返回错误。此外, `Save` 方法的第二个参数支持随机自动命名上传文件。 +4. 通过 `group.POST("/", Upload)` 注册的路由仅支持 `POST` 方式访问。 + +## 客户端 + +### 单文件上传 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + var ( + ctx = gctx.New() + path = "/home/john/Workspace/Go/github.com/gogf/gf/v2/version.go" + ) + result, err := g.Client().Post(ctx, "http://127.0.0.1:8199/upload", "upload-file=@file:"+path) + if err != nil { + glog.Fatalf(ctx, `%+v`, err) + } + defer result.Close() + fmt.Println(result.ReadAllString()) +} +``` + +注意到了吗?文件上传参数格式使用了 `参数名=@file:文件路径` ,HTTP客户端将会自动解析 **文件路径** 对应的文件内容并读取提交给服务端。原本复杂的文件上传操作被 `gf` 进行了封装处理,用户只需要使用 `@file:+文件路径` 来构成参数值即可。其中, `文件路径` 请使用本地文件绝对路径。 + +首先运行服务端程序之后,我们再运行这个上传客户端(注意修改上传的文件路径为本地真实文件路径),执行后可以看到文件被成功上传到服务器的指定路径下。 + +### 多文件上传 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + var ( + ctx = gctx.New() + path1 = "/Users/john/Pictures/logo1.png" + path2 = "/Users/john/Pictures/logo2.png" + ) + result, err := g.Client().Post( + ctx, + "http://127.0.0.1:8199/upload", + fmt.Sprintf(`upload-file=@file:%s&upload-file=@file:%s`, path1, path2), + ) + if err != nil { + glog.Fatalf(ctx, `%+v`, err) + } + defer result.Close() + fmt.Println(result.ReadAllString()) +} +``` + +可以看到,多个文件上传提交参数格式为 `参数名=@file:xxx&参数名=@file:xxx...`,也可以使用 `参数名[]=@file:xxx&参数名[]=@file:xxx...` 的形式。 + +首先运行服务端程序之后,我们再运行这个上传客户端(注意修改上传的文件路径为本地真实文件路径),执行后可以看到文件被成功上传到服务器的指定路径下。 + +## 自定义文件名称 + +很简单,修改 `FileName` 属性即可。 + +```go +s := g.Server() +s.BindHandler("/upload", func(r *ghttp.Request) { + file := r.GetUploadFile("TestFile") + if file == nil { + r.Response.Write("empty file") + return + } + file.Filename = "MyCustomFileName.txt" + fileName, err := file.Save(gfile.TempDir()) + if err != nil { + r.Response.Write(err) + return + } + r.Response.Write(fileName) +}) +s.SetPort(8999) +s.Run() +``` + +## 规范路由接收上传文件 + +服务端如果通过规范路由方式,那么可以通过结构化的参数获取上传文件: + +- 参数接收的数据类型使用 `*ghttp.UploadFile` +- 如果需要接口文档也支持文件类型,那么参数的标签中设置 `type` 为 `file` 类型 + +![](/markdown/3146c1c8d37ca3745a3519f96361de6a.png) + +![](/markdown/57f441f1e4666f73cc320a4e3d47f50b.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..0d63793a3d9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\347\233\221\346\216\247\346\214\207\346\240\207.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/web/http-client-metrics' +title: 'HTTPClient-监控指标' +sidebar_position: 10 +hide_title: true +keywords: [HTTP客户端,监控指标,性能优化,请求时间,GoFrame,连接时间,请求总数,请求大小,GoFrame框架,返回大小] +description: 'HTTP客户端的监控指标功能,默认情况下是关闭状态,以免影响性能。它提供了多种指标供用户参考,如请求执行的时间开销、连接创建时间以及请求的字节总大小等,只有在metric特性全局开启时才会启用这些指标,帮助用户更好地进行性能分析。' +--- + +`HTTP` 客户端支持监控指标能力,默认是关闭的不影响性能,只有在 `metric` 特性全局开启时该组件才会默认开启监控指标计算和生成功能。 + +## 指标列表 + +| **指标名称** | **指标类型** | **指标单位** | **指标描述** | +| --- | --- | --- | --- | +| `http.client.request.duration` | `Histogram` | `ms` | 客户端请求执行的时间开销。 | +| `http.client.request.duration_total` | `Counter` | `ms` | 每个请求使用的总时间开销。 | +| `http.client.connection.duration` | `Histogram` | `ms` | 创建连接所使用的时间开销。 | +| `http.client.request.total` | `Counter` | | 已经执行完毕的请求总数。 | +| `http.client.request.active` | `Gauge` | | 当前正在处理的请求数量。 | +| `http.client.request.body_size` | `Counter` | `bytes` | 请求的字节总大小。 | +| `http.client.response.body_size` | `Counter` | `bytes` | 返回的字节总大小。 | + +## 属性列表 + +| **Label名称** | **Label描述** | **Label示例** | +| --- | --- | --- | +| `server.address` | 请求的目标服务地址。可能是域名、IP地址。 | `goframe.org`
    `10.0.1.132` | +| `server.port` | 请求的目标服务端口。 | `8000` | +| `http.request.method` | 请求的方法名称。 | `GET`; `POST`; `DELETE` | +| `http.response.status_code` | 处理返回的 `HTTP` 状态码。 | `200` | +| `url.schema` | 使用的请求协议。 | `http`
    `https` | +| `network.protocol.version` | 请求协议版本。 | `1.0`
    `1.1` | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" new file mode 100644 index 00000000000..a308f46a486 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211ContentType.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/web/http-client-content-type' +title: 'HTTPClient-自定义ContentType' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTPClient,ContentType,Json请求,Xml请求,自定义ContentType,PostContent,url encode,web请求] +description: '在GoFrame框架中使用HTTPClient自定义请求的ContentType。通过不同的操作方法如ContentJson和ContentXml,可以设置请求的Content-Type分别为application/json和application/xml。同时也提供了自定义ContentType的方法例子,帮助开发者灵活设置请求参数和编码方式,以满足不同的API请求需求。' +--- + +## 示例1,提交 `Json` 请求 + +```go +g.Client().ContentJson().PostContent(ctx, "http://order.svc/v1/order", g.Map{ + "uid" : 1, + "sku_id" : 10000, + "amount" : 19.99, + "create_time" : "2020-03-26 12:00:00", +}) +``` + +通过调用 `ContentJson` 链式操作方法,该请求将会将 `Content-Type` 设置为 `application/json`,并且将提交参数自动编码为 `Json`: + +``` +{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"} +``` + +## 示例2,提交 `Xml` 请求 + +```go +g.Client().ContentXml().PostContent(ctx, "http://order.svc/v1/order", g.Map{ + "uid" : 1, + "sku_id" : 10000, + "amount" : 19.99, + "create_time" : "2020-03-26 12:00:00", +}) +``` + +通过调用 `ContentXml` 链式操作方法,该请求将会将 `Content-Type` 设置为 `application/xml`,并且将提交参数自动编码为 `Xml`: + +``` +19.992020-03-26 12:00:00100001 +``` + +## 示例3,自定义 `ContentType` + +我们可以通过 `ContentType` 方法自定义客户端请求的 `ContentType`。如果是给定的 `string/[]byte` 参数,客户端将会直接将参数提交给服务端;如果是其他数据类型将会自动对参数执行 `url encode` 再提交到服务端。 + +示例1: + +```go +g.Client().ContentType("application/json").PostContent( + ctx, + "http://order.svc/v1/order", + `{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"}`, +) +``` + +或 + +```go +g.Client().ContentType("application/json; charset=utf-8").PostContent( + ctx, + "http://order.svc/v1/order", + `{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"}`, +) +``` + +提交的参数如下: + +``` +{"uid":1,"sku_id":10000,"amount":19.99,"create_time":"2020-03-26 12:00:00"} +``` + +示例2: + +```go +g.Client().ContentType("application/x-www-form-urlencoded; charset=utf-8").GetContent( + ctx, + "http://order.svc/v1/order", + g.Map{ + "category" : 1, + "sku_id" : 10000, + }, +) +``` + +提交的参数如下: + +``` +category=1&sku_id=10000 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" new file mode 100644 index 00000000000..c4fe7900f95 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Cookie.md" @@ -0,0 +1,107 @@ +--- +slug: '/docs/web/http-client-cookie' +title: 'HTTPClient-自定义Cookie' +sidebar_position: 2 +hide_title: true +keywords: [HTTPClient,自定义Cookie,GoFrame,GoFrame框架,SetCookie,SetCookieMap,HTTP客户端,ghttp,Cookie,服务端] +description: '在使用GoFrame框架的HTTP客户端中自定义发送给服务端的Cookie内容,主要通过SetCookie和SetCookieMap方法实现。通过简单的服务端和客户端示例展示了如何设置与接收自定义的Cookie参数,实现HTTP客户端的个性化请求。' +--- + +HTTP客户端发起请求时可以自定义发送给服务端的 `Cookie` 内容,该特性使用 `SetCookie*` 相关方法实现。 + +方法列表: + +```go +func (c *Client) SetCookie(key, value string) *Client +func (c *Client) SetCookieMap(m map[string]string) *Client +``` + +我们来看一个客户端自定义 `Cookie` 的示例。 + +### 服务端 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Write(r.Cookie.Map()) + }) + s.SetPort(8199) + s.Run() +} +``` + +由于是作为示例,服务端的逻辑很简单,直接将接收到的 `Cookie` 参数全部返回给客户端。 + +### 客户端 + +1. 使用 `SetCookie` 方法 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetCookie("name", "john") + c.SetCookie("score", "100") + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +通过 `g.Client()` 创建一个自定义的HTTP请求客户端对象,并通过 `c.SetCookie` 方法设置自定义的 `Cookie`,这里我们设置了两个示例用的 `Cookie` 参数,一个 `name`,一个 `score`。 + +2. 使用 `SetCookieMap` 方法 + +这个方法更加简单,可以批量设置 `Cookie` 键值对。 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetCookieMap(g.MapStrStr{ + "name": "john", + "score": "100", + }) + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +3. 执行结果 + +客户端代码执行后,终端将会打印出服务端的返回结果,如下: + +``` +map[name:john score:100] +``` + +可以看到,服务端已经接收到了客户端自定义的 `Cookie` 参数。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" new file mode 100644 index 00000000000..8ddbb33e229 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Header.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/web/http-client-header' +title: 'HTTPClient-自定义Header' +sidebar_position: 3 +hide_title: true +keywords: [HTTPClient,自定义Header,GoFrame,GoFrame框架,SetHeader,Header方法,Span-Id,Trace-Id,HTTP请求,客户端] +description: '通过GoFrame框架的HTTPClient功能,用户可以自定义HTTP请求的Header信息。本文介绍了如何利用SetHeader、SetHeaderMap和SetHeaderRaw等方法设置和发送Header,从而实现自定义链路跟踪信息,如Span-Id和Trace-Id。通过简单的代码示例,展示了客户端如何与服务端交互并返回结果。' +--- + +HTTP客户端发起请求时可以自定义发送给服务端的 `Header` 内容,该特性使用 `SetHeader*` 相关方法实现。 + +方法列表: + +```go +func (c *Client) SetHeader(key, value string) *Client +func (c *Client) SetHeaderMap(m map[string]string) *Client +func (c *Client) SetHeaderRaw(headers string) *Client +``` + +我们来看一个客户端通过 `Header` 来自定义发送自定义链路跟踪信息 `Span-Id` 及 `Trace-Id` 消息头的示例。 + +### 服务端 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writef( + "Span-Id:%s,Trace-Id:%s", + r.Header.Get("Span-Id"), + r.Header.Get("Trace-Id"), + ) + }) + s.SetPort(8199) + s.Run() +} +``` + +由于是作为示例,服务端的逻辑很简单,直接将接收到的 `Span-Id` 及 `Trace-Id` 参数返回给客户端。 + +### 客户端 + +1. 使用 `SetHeader` 方法 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetHeader("Span-Id", "0.0.1") + c.SetHeader("Trace-Id", "NBC56410N97LJ016FQA") + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +通过 `g.Client()` 创建一个自定义的HTTP请求客户端对象,并通过 `c.SetHeader` 设置自定义的 `Header` 信息。 + +2. 使用 `SetHeaderRaw` 方法 + +这个方法更加简单,可以通过原始的Header字符串来设置客户端请求Header。 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + c := g.Client() + c.SetHeaderRaw(` + Referer: https://localhost + Span-Id: 0.0.1 + Trace-Id: NBC56410N97LJ016FQA + User-Agent: MyTestClient + `) + if r, e := c.Get(gctx.New(), "http://127.0.0.1:8199/"); e != nil { + panic(e) + } else { + fmt.Println(r.ReadAllString()) + } +} +``` + +3. 执行结果 + +客户端代码执行后,终端将会打印出服务端的返回结果,如下: + +``` +Span-Id:0.0.1,Trace-Id:NBC56410N97LJ016FQA +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" new file mode 100644 index 00000000000..6d7089c8b9c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\207\252\345\256\232\344\271\211Transport.md" @@ -0,0 +1,86 @@ +--- +slug: '/docs/web/http-client-transport' +title: 'HTTPClient-自定义Transport' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTPClient,Transport,Unix Socket,自定义Transport,http.Client,gclient.Client,客户端连接池,MaxIdleConnsPerHost] +description: '在GoFrame框架中,通过自定义Transport实现HTTPClient的高级用法。包括使用Unix Socket进行客户端与服务端通信的方法,以及设置客户端连接池大小参数的具体实现。示例提供了大量真实代码片段,帮助开发者更好地理解并应用这些技术。' +--- + +由于 `gclient.Client` 内部封装扩展于标准库的 `http.Client` 对象,因此标准库 `http.Client` 有的特性, `gclient.Client` 也是支持的。我们这里提到的例子是 `Transport` 使用。来看几个示例: + +## 使用 `Unix Socket` + +客户端和服务端使用 `Unix Socket` 通信,使用 `Transport` 来实现。以下代码为真实项目代码摘选,无法独立运行,仅做参考。 + +```go +func (*Guardian) ConvertContainerPathToHostPath( + ctx context.Context, namespace, podName, containerName, containerPath string, +) (string, error) { + var ( + client = g.Client() + url = "http://localhost/api/v1/pod/path" + req = webservice.HostPathInfoReq{ + Namespace: namespace, + PodName: podName, + ContainerName: containerName, + ContainerPath: containerPath, + } + res *webservice.HostPathInfoRes + ) + client.Transport = &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial("unix", serviceSocketPath) + }, + } + err := client.ContentJson().GetVar(ctx, url, req).Scan(&res) + if err != nil { + return "", gerror.Wrapf( + err, + `request guardian failed for url: %s, req: %s`, + url, gjson.MustEncodeString(req), + ) + } + if res == nil { + return "", gerror.Newf( + `nil response from guardian request url: %s, req: %s`, + url, gjson.MustEncodeString(req), + ) + } + return res.HostPath, nil +} +``` + +## 设置客户端连接池大小参数 + +```go +func ExampleNew_MultiConn_Recommend() { + var ( + ctx = gctx.New() + client = g.Client() + ) + + // controls the maximum idle(keep-alive) connections to keep per-host + client.Transport.(*http.Transport).MaxIdleConnsPerHost = 5 + + for i := 0; i < 5; i++ { + go func() { + if r, err := client.Get(ctx, "http://127.0.0.1:8999/var/json"); err != nil { + panic(err) + } else { + fmt.Println(r.ReadAllString()) + r.Close() + } + }() + } + + time.Sleep(time.Second * 1) + + // Output: + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} + //{"id":1,"name":"john"} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..6f2b23e1b31 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient-\350\257\267\346\261\202\344\277\241\346\201\257\346\211\223\345\215\260.md" @@ -0,0 +1,77 @@ +--- +slug: '/docs/web/http-client-raw-request-response' +title: 'HTTPClient-请求信息打印' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTP客户端,请求信息打印,原始请求,调试,响应信息,HTTP请求,Go语言,Web开发] +description: '使用GoFrame框架中的HTTP客户端功能获取和打印HTTP请求的原始输入和输出信息。主要方法包括Raw、RawDump、RawRequest和RawResponse,适用于调试HTTP请求。示例展示了使用GoFrame框架发送POST请求并打印请求和响应的具体方法。' +--- + +## 基本介绍 + +`http` 客户端支持对HTTP请求的输入与输出原始信息获取与打印,方便调试,相关方法如下: + +```go +func (r *Response) Raw() string +func (r *Response) RawDump() +func (r *Response) RawRequest() string +func (r *Response) RawResponse() string +``` + +可以看到,所有的方法均绑定在 `Response` 对象上,也就是说必须要请求结束后才能打印。 + +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + response, err := g.Client().Post( + gctx.New(), + "https://goframe.org", + g.Map{ + "name": "john", + }, + ) + if err != nil { + panic(err) + } + response.RawDump() +} +``` + +执行后,终端输出为: + +``` ++---------------------------------------------+ +| REQUEST | ++---------------------------------------------+ +POST / HTTP/1.1 +Host: goframe.org +User-Agent: GoFrameHTTPClient v2.0.0-beta +Content-Length: 9 +Content-Type: application/x-www-form-urlencoded +Accept-Encoding: gzip + +name=john + ++---------------------------------------------+ +| RESPONSE | ++---------------------------------------------+ +HTTP/1.1 405 Method Not Allowed +Connection: close +Transfer-Encoding: chunked +Allow: GET +Cache-Control: no-store +Content-Security-Policy: frame-ancestors 'self' +Content-Type: text/html;charset=UTF-8 +Date: Fri, 03 Dec 2021 09:43:29 GMT +Expires: Thu, 01 Jan 1970 00:00:00 GMT +Server: nginx +... +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" new file mode 100644 index 00000000000..734da6d023d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/HTTPClient/HTTPClient.md" @@ -0,0 +1,87 @@ +--- +slug: '/docs/web/http-client' +title: 'HTTPClient' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTTP客户端,gclient,HTTP请求,链式操作,HTTP方法,自定义请求,连接池,返回对象] +description: 'GoFrame框架提供的强大HTTP客户端gclient组件,支持HTTP请求的便捷链式操作。客户端支持自定义请求设置及返回对象操作,并详细介绍了超时、Cookie、Header等参数的设置方法。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了强大便捷易用的 `HTTP` 客户端,由 `gclient` 组件实现,对象创建可以通过 `gclient.New()` 包方法,也可以通过 `g.Client()` 方法调用。推荐使用 `g.Client()` 来便捷地创建 `HTTP` 客户端对象。由于 `gclient.Client` 内部封装扩展于标准库的 `http.Client` 对象,因此标准库 `http.Client` 有的特性, `gclient.Client` 也是支持的。 + +**方法列表**:[https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient](https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient) + +**简要说明**: + +1. 我们可以使用 `New` 创建一个自定义的HTTP客户端对象 `Client`,随后可以使用该对象执行请求,该对象底层使用了连接池设计,因此没有 `Close` 关闭方法。 `HTTP` 客户端对象也可以通过 `g.Client()` 快捷方法创建。 +2. 客户端提供了一系列以 `HTTP Method` 命名的方法,调用这些方法将会发起对应的 `HTTP Method` 请求。常用的方法是 `Get` 和 `Post` 方法,同时 `DoRequest` 是核心的请求方法,用户可以调用该方法实现自定义的 `HTTP Method` 发送请求。 +3. 请求返回结果为 `*ClientResponse` 对象,可以通过该结果对象获取对应的返回结果,通过 `ReadAll`/ `ReadAllString` 方法可以获得返回的内容,该对象在使用完毕后需要通过 `Close` 方法关闭,防止内存溢出。 +4. `*Bytes` 方法用于获得服务端返回的二进制数据,如果请求失败返回 `nil`; `*Content` 方法用于请求获得字符串结果数据,如果请求失败返回空字符串; `Set*` 方法用于 `Client` 的参数设置。 +5. `*Var` 方法直接请求并获取HTTP接口结果为泛型类型便于转换。如果请求失败或者请求结果为空,会返回一个空的 `g.Var` 泛型对象,不影响转换方法调用。 +6. 可以看到,客户端的请求参数的数据参数 `data` 数据类型为 `interface{}` 类型,也就是说可以传递任意的数据类型,常见的参数数据类型为 `string`/ `map`,如果参数为 `map` 类型,参数值将会被自动 `urlencode` 编码。 + +:::warning +请使用给定的方法创建 `Client` 对象,而不要使用 `new(ghttp.Client)` 或者 `&ghttp.Client{}` 创建客户端对象,否则,哼哼。 +::: +## 链式操作 + +`GoFrame` 框架的客户端支持便捷的链式操作,常用方法如下(文档方法列表可能滞后于源码,建议查看接口文档或源码 [https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient](https://pkg.go.dev/github.com/gogf/gf/v2/net/gclient)): + +```go +func (c *Client) Timeout(t time.Duration) *Client +func (c *Client) Cookie(m map[string]string) *Client +func (c *Client) Header(m map[string]string) *Client +func (c *Client) HeaderRaw(headers string) *Client +func (c *Client) ContentType(contentType string) *Client +func (c *Client) ContentJson() *Client +func (c *Client) ContentXml() *Client +func (c *Client) BasicAuth(user, pass string) *Client +func (c *Client) Retry(retryCount int, retryInterval time.Duration) *Client +func (c *Client) Prefix(prefix string) *Client +func (c *Client) Proxy(proxyURL string) *Client +func (c *Client) RedirectLimit(redirectLimit int) *Client +func (c *Client) Dump(dump ...bool) *Client +func (c *Client) Use(handlers ...HandlerFunc) *Client +``` + +简要说明: + +1. `Timeout` 方法用于设置当前请求超时时间。 +2. `Cookie` 方法用于设置当前请求的自定义 `Cookie` 信息。 +3. `Header*` 方法用于设置当前请求的自定义 `Header` 信息。 +4. `Content*` 方法用于设置当前请求的 `Content-Type` 信息,并且支持根据该信息自动检查提交参数并自动编码。 +5. `BasicAuth` 方法用于设置 `HTTP Basic Auth` 校验信息。 +6. `Retry` 方法用于设置请求失败时重连次数和重连间隔。 +7. `Proxy` 方法用于设置http访问代理。 +8. `RedirectLimit` 方法用于限制重定向跳转次数。 + +## 返回对象 + +`gclient.Response` 为HTTP对应请求的返回结果对象,该对象继承于 `http.Response`,可以使用 `http.Response` 的所有方法。在此基础之上增加了以下几个方法: + +```go +func (r *Response) GetCookie(key string) string +func (r *Response) GetCookieMap() map[string]string +func (r *Response) Raw() string +func (r *Response) RawDump() +func (r *Response) RawRequest() string +func (r *Response) RawResponse() string +func (r *Response) ReadAll() []byte +func (r *Response) ReadAllString() string +func (r *Response) Close() error +``` + +这里也要提醒的是, `Response` 需要手动调用 `Close` 方法关闭,也就是说,不管你使用不使用返回的 `Response` 对象,你都需要将该返回对象赋值给一个变量,并且手动调用其 `Close` 方法进行关闭(往往使用 `defer r.Close()`),否则会造成文件句柄溢出、内存溢出。 + +## 重要说明 + +1. `ghttp` 客户端默认关闭了 `KeepAlive` 功能以及对服务端 `TLS` 证书的校验功能,如果需要启用可自定义客户端的 `Transport` 属性。 +2. **连接池参数设定**、 **连接代理设置** 等这些高级功能也可以通过自定义客户端的 `Transport` 属性实现,该数据继承于标准库的 `http.Transport` 对象。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" new file mode 100644 index 00000000000..c1ad846eeb7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-File.md" @@ -0,0 +1,63 @@ +--- +slug: '/docs/web/session-file' +title: 'Session-File' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,Session,文件存储,ghttp.Server,StorageFile,gcache,序列化,反序列化,会话管理] +description: '使用GoFrame框架的ghttp.Server实现Session的文件存储。默认情况下,Session存储采用内存和文件结合的方式,通过StorageFile实现持久化管理。得益于gcache模块,Session数据操作高效,特别适合读多写少的场景。同时,演示示例展示了如何在GoFrame项目中设置与获取Session。' +--- + +## 文件存储 + +在默认情况下, `ghttp.Server` 的 `Session` 存储使用了 `内存+文件` 的方式,使用 `StorageFile` 对象实现。具体原理为: + +1. `Session` 的数据操作完全基于内存; +2. 使用 `gcache` 进程缓存模块控制数据过期; +3. 使用文件存储持久化存储管理 `Session` 数据; +4. 当且仅有当 `Session` 被标记为 `dirty` 时(数据有更新)才会执行 `Session` 序列化并执行文件持久化存储; +5. 当且仅当内存中的 `Session` 不存在时,才会从文件存储中反序列化恢复 `Session` 数据到内存中,降低 `IO` 调用; +6. 序列化/反序列化使用的是标准库的 `json.Marshal/UnMarshal` 方法; + +从原理可知,当 `Session` 为读多写少的场景中, `Session` 的数据操作非常高效。 +:::tip +有个注意的细节,由于文件存储涉及到文件操作,为便于降低 `IO` 开销并提高 `Session` 操作性能,并不是每一次 `Session` 请求结束后都会立即刷新对应 `Session` 的 `TTL` 时间。而只有当涉及到更新操作(被标记为 `dirty`)时才会立即刷新其 `TTL`;针对于读取请求,将会每隔 `一分钟` 更新前一分钟内读取操作对应的 `Session` 文件 `TTL` 时间,以便于 `Session` 自动续活。 +::: +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +在该实例中,为了方便观察过期失效,我们将 `Session` 的过期时间设置为 `1分钟`。执行后, + +1. 首先,访问 [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) 设置一个 `Session` 变量; +2. 随后,访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到该 `Session` 变量已经设置并成功获取; +3. 接着,我们停止程序,并重新启动,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get),可以看到 `Session` 变量已经从文件存储中恢复; +4. 等待1分钟后,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到已经无法获取该 `Session`,因为该 `Session` 已经过期; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" new file mode 100644 index 00000000000..2a1c6d83bfb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Memory.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/web/session-memory' +title: 'Session-Memory' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,Session存储,内存存储,Session数据,StorageMemory,gsession,GoFrame框架,Session示例,Session设置,Session持久化] +description: '在GoFrame框架中使用内存存储实现Session功能。内存存储方式简单高效,但不支持持久化,因此在应用程序重启后Session数据会丢失。通过示例代码,详细说明了如何设置Session的过期时间以及如何存储和获取Session数据。' +--- + +## 内存存储 + +内存存储比较简单,性能也很高效,但没有持久化存储 `Session` 数据,因此应用程序重启之后便会丢失 `Session` 数据,可用于特定的业务场景中。 `gsession` 的 `内存` 存储使用 `StorageMemory` 对象实现, + +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageMemory()) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.MustSet("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +在该实例中,为了方便观察过期失效,我们将 `Session` 的过期时间设置为 `1分钟`。执行后, + +1. 首先,访问 [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) 设置一个 `Session` 变量; +2. 随后,访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到该 `Session` 变量已经设置并成功获取; +3. 接着,我们停止程序,并重新启动,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get),可以看到 `Session` 变量已经没有了; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" new file mode 100644 index 00000000000..a8e18e8cb08 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-HashTable.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/web/session-redis-hash-table' +title: 'Session-Redis-HashTable' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,RedisHashTableStorage,Session,Redis存储,Session管理,Go开发,Web开发,Session过期,Session示例] +description: '在GoFrame框架中使用RedisHashTableStorage进行Session管理,区别于RedisKeyValueStorage,该方法直接通过Redis服务进行操作,无需全量拉取。通过示例代码,展示了基本的Session设置、获取和删除操作,以及如何在GoFrame中集成这个功能。' +--- + +## RedisHashTableStorage + +与 `RedisKeyValueStorage` 不同的地方在于 `RedisHashTableStorage` 底层使用 `HashTable` 存储 `Session` 数据,每一次对 `Session` 的增删查改都是直接访问 `Redis` 服务实现(单条数据项操作),不存在像 `RedisKeyValueStorage` 那样初始化全量拉取一次,请求结束后如有修改再全量更新到 `Redis` 服务的操作。 + +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageRedisHashTable(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +在该实例中,为了方便观察过期失效,我们将 `Session` 的过期时间设置为 `1分钟`。执行后, + +1. 首先,访问 [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) 设置一个 `Session` 变量; +2. 随后,访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到该 `Session` 变量已经设置并成功获取; +3. 接着,我们停止程序,并重新启动,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get),可以看到 `Session` 变量已经从 `Redis` 存储中恢复;如果我们手动修改 `Redis` 中的对应键值数据,页面刷新时也会读取到最新的值; +4. 等待1分钟后,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到已经无法获取该 `Session`,因为该 `Session` 已经过期; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" new file mode 100644 index 00000000000..c061f9785ab --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Redis-KeyValue.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/web/session-redis-key-value' +title: 'Session-Redis-KeyValue' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,Redis,Session,KeyValue,多节点部署,StorageRedis,内存+Redis,JSON序列化,HashTable Storage] +description: '在GoFrame框架中使用Redis进行Session的KeyValue存储,以解决多节点部署下Session共享的问题。通过使用StorageRedis对象实现Redis存储,提高执行效率,适合单个用户Session数据量较小的场景,并提供具体的使用示例和说明。在示例中,Session过期时间设为1分钟,展示了设置、获取、删除Session的方法及Redis中Session数据的恢复功能。' +--- + +## Redis KeyValue Storage + +文件存储的方式在单节点的场景下非常不错,但是涉及到对应用进行多节点部署的场景下,各个节点的 `Session` 无法共享,因此需要将 `Session` 存储单独剥离出来管理, `Redis` 服务器是比较常见的一个选择。 + +`gsession` 的 `Redis` 存储使用 `StorageRedis` 对象实现,与文件存储比较类似,为了提高执行效率,也是采用了 `内存+Redis` 的方式。与文件存储唯一不同的是,在每一次请求中如果需要对 `Session` 进行操作时,将会从 `Redis` 中拉取一次最新的 `Session` 数据(而文件存储只会在 `Session` 不存在时读取一次文件)。在每一次请求结束之后,将全量的 `Session` 数据通过 `JSON` 序列化之后通过 `KeyValue` 方式更新到 `Redis` 服务中。 +:::tip +如果单个用户下(以用户维度举例) `Session` 数据量不大的业务场景中,都推荐使用这种 `Storage` 方式。如果单个用户 `Session` 数据量较大(例如 `>10MB`),可以参考 `HashTable` 的 `Storage` 方式: [Session-Redis-HashTable](Session-Redis-HashTable.md) +::: +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gsession" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + s := g.Server() + s.SetSessionMaxAge(time.Minute) + s.SetSessionStorage(gsession.NewStorageRedis(g.Redis())) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/set", func(r *ghttp.Request) { + r.Session.Set("time", gtime.Timestamp()) + r.Response.Write("ok") + }) + group.ALL("/get", func(r *ghttp.Request) { + r.Response.Write(r.Session.Data()) + }) + group.ALL("/del", func(r *ghttp.Request) { + _ = r.Session.RemoveAll() + r.Response.Write("ok") + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +在该实例中,为了方便观察过期失效,我们将 `Session` 的过期时间设置为 `1分钟`。执行后, + +1. 首先,访问 [http://127.0.0.1:8199/set](http://127.0.0.1:8199/set) 设置一个 `Session` 变量; +2. 随后,访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到该 `Session` 变量已经设置并成功获取; +3. 接着,我们停止程序,并重新启动,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get),可以看到 `Session` 变量已经从 `Redis` 存储中恢复;如果我们手动修改 `Redis` 中的对应键值数据,页面刷新时也会读取到最新的值; +4. 等待1分钟后,再次访问 [http://127.0.0.1:8199/get](http://127.0.0.1:8199/get) 可以看到已经无法获取该 `Session`,因为该 `Session` 已经过期; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..b6fc412e099 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session-Storage\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,72 @@ +--- +slug: '/docs/web/session-storage' +title: 'Session-Storage接口开发' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,gsession,Session-Storage,自定义存储,接口开发,Storage接口,TTL,gmap,session管理] +description: '在GoFrame框架中使用gsession组件进行Session-Storage接口开发。通过组件内置的Storage实现,可以满足大部分业务场景的需求。开发者还可以根据特定情况,自定义Session存储。文中详细描述了Storage接口的定义及调用时机,为提高Session性能,建议使用gmap容器类型。本指南将帮助开发者更好地实现和优化存储接口。' +--- + +大部分场景下,通过 `gsession` 组件内置提供的常见 `Storage` 实现已经能够满足需求。如果有特殊的场景需要制定不易开发 `Storage` 当然也是支持的,因为 `gsession` 的功能都采用了接口化设计。 + +## Storage定义 + +[https://github.com/gogf/gf/blob/master/os/gsession/gsession\_storage.go](https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go) + +```go +// Storage is the interface definition for session storage. +type Storage interface { + // New creates a custom session id. + // This function can be used for custom session creation. + New(ctx context.Context, ttl time.Duration) (id string, err error) + + // Get retrieves and returns session value with given key. + // It returns nil if the key does not exist in the session. + Get(ctx context.Context, id string, key string) (value interface{}, err error) + + // GetMap retrieves all key-value pairs as map from storage. + GetMap(ctx context.Context, id string) (data map[string]interface{}, err error) + + // GetSize retrieves and returns the size of key-value pairs from storage. + GetSize(ctx context.Context, id string) (size int, err error) + + // Set sets one key-value session pair to the storage. + // The parameter `ttl` specifies the TTL for the session id. + Set(ctx context.Context, id string, key string, value interface{}, ttl time.Duration) error + + // SetMap batch sets key-value session pairs as map to the storage. + // The parameter `ttl` specifies the TTL for the session id. + SetMap(ctx context.Context, id string, data map[string]interface{}, ttl time.Duration) error + + // Remove deletes key with its value from storage. + Remove(ctx context.Context, id string, key string) error + + // RemoveAll deletes all key-value pairs from storage. + RemoveAll(ctx context.Context, id string) error + + // GetSession returns the session data as `*gmap.StrAnyMap` for given session id from storage. + // + // The parameter `ttl` specifies the TTL for this session. + // The parameter `data` is the current old session data stored in memory, + // and for some storage it might be nil if memory storage is disabled. + // + // This function is called ever when session starts. It returns nil if the TTL is exceeded. + GetSession(ctx context.Context, id string, ttl time.Duration, data *gmap.StrAnyMap) (*gmap.StrAnyMap, error) + + // SetSession updates the data for specified session id. + // This function is called ever after session, which is changed dirty, is closed. + // This copy all session data map from memory to storage. + SetSession(ctx context.Context, id string, data *gmap.StrAnyMap, ttl time.Duration) error + + // UpdateTTL updates the TTL for specified session id. + // This function is called ever after session, which is not dirty, is closed. + UpdateTTL(ctx context.Context, id string, ttl time.Duration) error +} +``` + +每一个方法的调用时机都在注释中详细介绍了,开发者在实现自定义的 `Storage` 时,可以充分参考内置的几种 `Storage` 实现。 + +## 注意事项 + +- `Storage` 接口中,并不是所有的接口方法都需要实现,开发者仅需要根据业务需要,实现特定调用时机的一些接口即可。 +- 为了提高 `Session` 的执行性能,接口有 `gmap.StrAnyMap` 容器类型的使用,开发时可以参考一下章节: [字典类型-gmap](../../组件列表/数据结构/字典类型-gmap/字典类型-gmap.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" new file mode 100644 index 00000000000..e4d402673e4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/Session/Session.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/web/session' +title: 'Session' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,Session管理,gsession,HTTP服务,SessionId,并发安全,ghttp.Request,gsession模块,Session存储] +description: 'GoFrame框架中Session管理的相关功能,包括Session的基本概念、gsession模块的实现方式及其在不同场景中的应用。详细讨论了SessionId的传递方式和Session的初始化、销毁等操作,提供了四种常见的Session存储实现方式及其特点,为开发者在HTTP等多种服务环境下的Session管理提供了丰富的工具支持。' +--- + +`GoFrame` 框架提供了完善的 `Session` 管理能力,由 `gsession` 组件实现。由于 `Session` 机制在 `HTTP` 服务中最常用,因此后续章节中将着重以 `HTTP` 服务为示例介绍 `Session` 的使用。 + +## 基本介绍 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gsession](https://pkg.go.dev/github.com/gogf/gf/v2/os/gsession) + +任何时候都可以通过 `ghttp.Request` 获取 `Session` 对象,因为 `Cookie` 和 `Session` 都是和请求会话相关,因此都属于 `Request` 的成员对象,并对外公开。 `GoFrame` 框架的 `Session` 默认过期时间是 `24小时`。 + +`SessionId` 默认通过 `Cookie` 来传递,并且也支持客户端通过 `Header` 传递 `SessionId`, `SessionId` 的识别名称可以通过 `ghttp.Server` 的 `SetSessionIdName` 进行修改。 `Session` 的操作是支持 `并发安全` 的,这也是框架在对 `Session` 的设计上不采用直接以 `map` 的形式操作数据的原因。在 `HTTP` 请求流程中,我们可以通过 `ghttp.Request` 对象来获取 `Session` 对象,并执行相应的数据操作。 + +此外, `ghttp.Server` 中的 `SessionId` 使用的是客户端的 `RemoteAddr + Header` 请求信息通过 `guid` 模块来生成的,保证随机及唯一性: [https://github.com/gogf/gf/blob/master/net/ghttp/ghttp\_request.go](https://github.com/gogf/gf/blob/master/net/ghttp/ghttp_request.go) + +## `gsession` 模块 + +`Session` 的管理功能由独立的 `gsession` 模块实现,并已完美整合到了 `ghttp.Server` 中。由于该模块是解耦独立的,因此可以应用到更多不同的场景中,例如: `TCP` 通信、 `gRPC` 接口服务等等。在 `gsession` 模块中有比较重要的三个对象/接口: + +1. `gsession.Manager`:管理 `Session` 对象、 `Storage` 持久化存储对象、以及过期时间控制。 +2. `gsession.Session`:单个 `Session` 会话管理对象,用于 `Session` 参数的增删查改等数据管理操作。 +3. `gsession.Storage`:这是一个接口定义,用于 `Session` 对象的持久化存储、数据写入/读取、存活更新等操作,开发者可基于该接口实现自定义的持久化存储特性。 该接口定义详见: [https://github.com/gogf/gf/blob/master/os/gsession/gsession\_storage.go](https://github.com/gogf/gf/blob/master/os/gsession/gsession_storage.go) + +## 存储实现方式 + +`gsession` 实现并为开发者提供了常见的四种 `Session` 存储实现方式: + +| Storage | 支持分布式 | 支持持久化 | 内存占用 | 执行效率 | 简要介绍 | +| --- | --- | --- | --- | --- | --- | +| `StorageFile` | 否 | 是 | 中 | 中 | 基于文件存储(默认)。单节点部署方式下比较高效的持久化存储方式: [Session-File](Session-File.md) | +| `StorageMemory` | 否 | 否 | 高 | 高 | 基于纯内存存储。单节点部署,性能最高效,但是无法持久化保存,重启即丢失: [Session-Memory](Session-Memory.md) | +| `StorageRedis` | 是 | 是 | 中 | 中 | 基于 `Redis` 存储( `Key-Value`)。远程 `Redis` 节点存储 `Session` 数据,支持应用多节点部署: [Session-Redis-KeyValue](Session-Redis-KeyValue.md) | +| `StorageRedisHashTable` | 是 | 是 | 低 | 低 | 基于 `Redis` 存储( `HashTable`)。远程 `Redis` 节点存储 `Session` 数据,支持应用多节点部署: [Session-Redis-HashTable](Session-Redis-HashTable.md) | + +四种方式各有优劣,详细介绍请查看对应章节。 + +## `Session` 的初始化 + +以常见的HTTP请求为例。 `ghttp.Request` 中的 `Session` 对象采用了" **懒初始化( `LazyInitialization`)**"设计方式,默认在 `Request` 中有一个 `Session` 属性对象,但是并未初始化(一个空对象),只有在使用 `Session` 属性对象的方法时才会真正执行初始化。这样的设计既保障了未使用 `Session` 特性的请求执行性能,也保证了组件使用的易用性。 + +## `Session` 的销毁/注销 + +用户 `Session` 不再使用,例如用户注销登录状态,需要从存储中硬删除,那么可以调用 `RemoveAll` 方法。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..ded85c336bf --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/WEB\346\234\215\345\212\241\345\274\200\345\217\221.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/web' +title: 'WEB服务开发' +sidebar_position: 7 +hide_title: true +keywords: [WEB服务开发,GoFrame框架,WEB应用程序,后端开发,RESTful服务,API设计,Go语言,高性能服务器,应用架构,数据处理] +description: '了解如何使用GoFrame框架进行WEB服务开发,涵盖从基本概念到高级应用的各个方面。通过本指南,您将掌握使用Go语言构建高效、可靠的WEB应用程序的技巧和方法。' +--- +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..7e297b842dc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-Ajax\345\210\206\351\241\265.md" @@ -0,0 +1,72 @@ +--- +slug: '/docs/web/paging-ajax' +title: '分页管理-Ajax分页' +sidebar_position: 2 +hide_title: true +keywords: [Ajax分页,分页管理,Javascript分页,GoFrame,GoFrame框架,Golang,页面渲染,前端开发,动态分页,Web开发] +description: '使用Ajax方法实现分页管理的技术细节。区别于传统分页方式,Ajax分页通过Javascript方法动态获取并渲染分页内容,以实现更流畅的用户体验。示例代码展示了如何在GoFrame框架中集成Ajax分页功能,提供一个实用的后端分页解决方案。' +--- + +`Ajax` 分页与其他分页方式的区别在于,分页链接会使用 `Javascript` 方法来实现,该 `Javascript` 方法是分页方法,参数固定为该分页对应的分页 `URL` 地址。该 `Javascript` 方法通过 `Ajax` 获取到 `URL` 连接对应的分页内容后渲染到页面。 + +完整示例如下: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/ajax", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + page.AjaxActionName = "DoAjax" + buffer, _ := gview.ParseContent(` + + + + + + + +
    {{.page1}}
    +
    {{.page2}}
    +
    {{.page3}}
    +
    {{.page4}}
    + + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +在该示例中,我们定义了一个 `DoAjax(url)` 方法用来执行分页操作,为演示需要它逻辑很简单,会加载指定分页页面的内容并覆盖掉当前页面的分页内容。 + +``` +function DoAjax(url) { + $.get(url, function(data,status) { + $("body").html(data); + }); +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" new file mode 100644 index 00000000000..cdd7e59dd52 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-URL\346\250\241\346\235\277.md" @@ -0,0 +1,56 @@ +--- +slug: '/docs/web/paging-template' +title: '分页管理-URL模板' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gpage,分页管理,URL模板,自定义URL,内置变量,页面渲染,代码示例,模板替换] +description: '使用GoFrame框架的gpage进行分页管理,并通过自定义URL模板功能,使用内置变量替换页码内容来实现页面的动态渲染。文章提供了详细的代码示例,展示了通过设置UrlTemplate属性实现分页URL的个性化配置,为开发者提供了灵活高效的解决方案。' +--- + +`gpage` 支持自定义 `URL` 模板,在模板中可以使用 `{.page}` 内置变量替换页码的内容,我们来看一个简单的示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/template/{page}.html", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + page.UrlTemplate = "/order/list/{.page}.html" + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page1}}
    +
    {{.page2}}
    +
    {{.page3}}
    +
    {{.page4}}
    + + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +在代码中,我们可以使用 `UrlTemplate` 属性设置 `URL` 模板,执行后,结果如下: + +![](/markdown/a67f2f6285ed959812f70fd066e7453a.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..9dda92ab42b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\345\212\250\346\200\201\345\210\206\351\241\265.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/web/paging-dynamic' +title: '分页管理-动态分页' +sidebar_position: 0 +hide_title: true +keywords: [动态分页,GoFrame,分页管理,GET参数,QueryString,分页示例,ghttp,gview,GoFrame框架,网页应用] +description: '该文档介绍了如何在GoFrame框架中使用动态分页,通过GET参数传递分页配置,默认参数名为“page”。通过提供的示例代码,用户可以了解如何在网页中集成四种预定义的分页样式,以及实现分页管理的过程。' +--- + +动态分页是通过 `GET` 参数(通过 `QueryString`)传递分页参数,默认分页参数名称为 `page`。 + +示例如下: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/demo", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page1}}
    +
    {{.page2}}
    +
    {{.page3}}
    +
    {{.page4}}
    + + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +该示例中,我们展示了四种预定义的分页样式,并通过 `GET` 方式进行分页传参。执行后,输出的内容如下图所示: + +![](/markdown/4e021b3d29b1d1789b1cb03959833c33.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..871a2aa27b6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\350\207\252\345\256\232\344\271\211\345\210\206\351\241\265.md" @@ -0,0 +1,125 @@ +--- +slug: '/docs/web/paging-customization' +title: '分页管理-自定义分页' +sidebar_position: 4 +hide_title: true +keywords: [自定义分页,分页管理,GoFrame,GoFrame框架,标签替换,分页样式,网页开发,正则匹配,go语言,框架使用] +description: '在GoFrame框架中实现自定义分页样式和标签。通过分页对象公开的属性和方法,开发者可以通过正则匹配替换或自行组织分页内容的方式进行自定义,实现更高的灵活性和个性化。' +--- + +由于分页对象预定义的样式比较有限,有的时候我们想自定义分页的样式或者标签,由于分页对象的的所有属性和方法都是公开的,这便为开发者自定义分页样式提供了非常高的灵活度。开发者可以通过以下方式实现自定义分页内容: + +1. 对输出内容进行正则匹配替换实现自定义。 +2. 根据分页对象公开的属性和方法自行组织分页内容实现自定义。 + +## 自定义标签替换 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/text/gstr" + "github.com/gogf/gf/v2/util/gpage" +) + +// wrapContent wraps each of the page tag with html li and ul. +func wrapContent(page *gpage.Page) string { + content := page.GetContent(4) + content = gstr.ReplaceByMap(content, map[string]string{ + "": "/span>", + "": "/a>", + }) + return "
      " + content + "
    " +} + +func main() { + s := g.Server() + s.BindHandler("/page/custom1/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + content := wrapContent(page) + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page}}
    + + + `, g.Map{ + "page": content, + }) + r.Response.Write(buffer) + }) + s.SetPort(10000) + s.Run() +} +``` + +执行后,页面输出结果为: + +![](/markdown/e3f0fff04f626c752f342e6f37ff88fa.png) + +## 定义分页标签名称 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" + "github.com/gogf/gf/v2/util/gpage" +) + +// pageContent customizes the page tag name. +func pageContent(page *gpage.Page) string { + page.NextPageTag = "NextPage" + page.PrevPageTag = "PrevPage" + page.FirstPageTag = "HomePage" + page.LastPageTag = "LastPage" + pageStr := page.FirstPage() + pageStr += page.PrevPage() + pageStr += page.PageBar() + pageStr += page.NextPage() + pageStr += page.LastPage() + return pageStr +} + +func main() { + s := g.Server() + s.BindHandler("/page/custom2/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page}}
    + + + `, g.Map{ + "page": pageContent(page), + }) + r.Response.Write(buffer) + }) + s.SetPort(10000) + s.Run() +} +``` + +执行后,页面输出结果为: + +![](/markdown/adca49269555fe04d83b277c38c656ef.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" new file mode 100644 index 00000000000..9c386d6426a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206-\351\235\231\346\200\201\345\210\206\351\241\265.md" @@ -0,0 +1,104 @@ +--- +slug: '/docs/web/paging-static' +title: '分页管理-静态分页' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,静态分页,分页管理,路由传参,模糊匹配,命名匹配,字段匹配,分页对象,分页参数] +description: '在GoFrame框架中实现静态分页管理。静态分页通过使用路由传参的方式实现,具有较高的耦合性。通过示例说明了如何利用GoFrame框架中的模糊匹配路由、命名匹配路由以及字段匹配路由来实现分页功能,使得分页对象能够接受路由中的分页参数,从而实现页面的分开显示。' +--- + +静态分页是指页面的分页参数使用的是路由传参,这种场景下分页对象与 `Server` 的路由定义耦合性比较大。路由定义中需要给定一个 `page` 名称的路由参数,可以使用模糊匹配路由 `*page`,也可以使用命名匹配路由 `:page`,也可以使用字段匹配路由 `{page}`。 + +### 示例1,使用模糊匹配路由 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/page/static/*page", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page1}}
    +
    {{.page2}}
    +
    {{.page3}}
    +
    {{.page4}}
    + + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,我们手动访问 [http://127.0.0.1:8199/page/static/6](http://127.0.0.1:8199/page/static/6) 页面的结果如下: + +![](/markdown/e1f6cd68809f5d3b2ceffcd1fb09aa3e.png) + +### 示例2,使用字段匹配路由 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + s := g.Server() + s.BindHandler("/:obj/*action/{page}.html", func(r *ghttp.Request) { + page := r.GetPage(100, 10) + buffer, _ := gview.ParseContent(` + + + + + +
    {{.page1}}
    +
    {{.page2}}
    +
    {{.page3}}
    +
    {{.page4}}
    + + + `, g.Map{ + "page1": page.GetContent(1), + "page2": page.GetContent(2), + "page3": page.GetContent(3), + "page4": page.GetContent(4), + }) + r.Response.Write(buffer) + }) + s.SetPort(8199) + s.Run() +} +``` + +该示例的路由规则更加灵活,其中使用了 `{page}` 字段匹配规则,用于获取当前的分页页码信息。执行后,我们按照路由规则随意访问一个URL如: [http://127.0.0.1:8199/order/list/6.html](http://127.0.0.1:8199/order/list/6.html) ,结果如下图所示: + +![](/markdown/bb96317821692384eb3dd794f3d9170e.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..6952669a474 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\345\210\206\351\241\265\347\256\241\347\220\206/\345\210\206\351\241\265\347\256\241\347\220\206.md" @@ -0,0 +1,83 @@ +--- +slug: '/docs/web/paging' +title: '分页管理' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,分页管理,gpage模块,动态分页,静态分页,HTML分页,MVC开发,Ajax分页,分页样式] +description: 'GoFrame框架中的分页管理功能,主要通过gpage模块实现。gpage模块支持动态和静态分页,并为开发者提供灵活的分页样式自定义方式。文章详细说明了分页对象的创建和使用,支持在Web服务中便捷地获取分页对象。还涵盖了预定义分页样式的使用以及Ajax分页的实现方法,以便于开发人员快速集成和使用。' +--- + +## 基本介绍 +分页管理由 `gpage` 模块实现, `gpage` 提供了强大的动态分页及静态分页功能,并且为开发者自定义分页样式提供了极高的灵活度。 +:::tip +`gpage` 模块主要用于生成分页的HTML代码,常用于 `MVC` 开发场景。 +::: +**使用方式**: + +```go +import "github.com/gogf/gf/v2/util/gpage" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gpage](https://pkg.go.dev/github.com/gogf/gf/v2/util/gpage) + +**分页对象**: + +```go +// Page is the pagination implementer. +// All the attributes are public, you can change them when necessary. +type Page struct { + TotalSize int // Total size. + TotalPage int // Total page, which is automatically calculated. + CurrentPage int // Current page number >= 1. + UrlTemplate string // Custom url template for page url producing. + LinkStyle string // CSS style name for HTML link tag
    . + SpanStyle string // CSS style name for HTML span tag , which is used for first, current and last page tag. + SelectStyle string // CSS style name for HTML select tag +
    + + + + + + +``` + +注意我们这里的服务端连接地址为: `ws://127.0.0.1:8199/ws`。 + +客户端的功能很简单,主要实现了这几个功能: + +- 与服务端 `websocket` 连接状态保持及信息展示; +- 界面输入内容并发送信息到 `websocket` 服务端; +- 接收到 `websocket` 的返回信息后回显在界面上; + +## WebSocket服务端 + +```go +package main + +import ( + "net/http" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gorilla/websocket" +) + +func main() { + var ( + s = g.Server() + logger = g.Log() + wsUpGrader = websocket.Upgrader{ + // CheckOrigin allows any origin in development + // In production, implement proper origin checking for security + CheckOrigin: func(r *http.Request) bool { + return true + }, + // Error handler for upgrade failures + Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { + // Implement error handling logic here + }, + } + ) + + // Bind WebSocket handler to /ws endpoint + s.BindHandler("/ws", func(r *ghttp.Request) { + // Upgrade HTTP connection to WebSocket + ws, err := wsUpGrader.Upgrade(r.Response.Writer, r.Request, nil) + if err != nil { + r.Response.Write(err.Error()) + return + } + defer ws.Close() + + // Get request context for logging + var ctx = r.Context() + + // Message handling loop + for { + // Read incoming WebSocket message + msgType, msg, err := ws.ReadMessage() + if err != nil { + break // Connection closed or error occurred + } + // Log received message + logger.Infof(ctx, "received message: %s", msg) + // Echo the message back to client + if err = ws.WriteMessage(msgType, msg); err != nil { + break // Error writing message + } + } + // Log connection closure + logger.Info(ctx, "websocket connection closed") + }) + + // Configure static file serving + s.SetServerRoot("static") + // Set server port + s.SetPort(8000) + // Start the server + s.Run() +} +``` + +可以看到,服务端的代码相当简单,这里需要着重说明的是这几个地方: + +1. **WebSocket方法** + + `websocket` 服务端的路由注册方式和普通的 `http` 回调函数注册方式一样,但是在接口处理中我们需要通过 `ghttp.Request.WebSocket` 方法(这里直接使用指针对象 `r.WebSocket()`)将请求转换为 `websocket` 操作,并返回一个 `WebSocket对象`,该对象用于后续的 `websocket` 通信操作。当然,如果客户端请求并非为 `websocket` 操作时,转换将会失败,该方法会返回错误信息,使用时请注意判断方法的 `error` 返回值。 + +1. **ReadMessage & WriteMessage** + + 读取消息以及写入消息对应的是 `websocket` 的数据读取以及写入操作( `ReadMessage & WriteMessage`),需要注意的是这两个方法都有一个 `msgType` 的变量,表示请求读取及写入数据的类型,常见的两种数据类型为:字符串数据或者二进制数据。在使用过程中,由于接口双方都会约定统一的数据格式,因此读取和写入的 `msgType` 几乎都是一致的,所以在本示例中的返回消息时,数据类型参数直接使用的是读取到的 `msgType`。 + +## HTTPS的WebSocket + +如果需要支持 `HTTPS` 的 `WebSocket` 服务,只需要依赖的 `WebServer` 支持 `HTTPS` 即可,访问的 `WebSocket` 地址需要使用 `wss://` 协议访问。以上客户端 `HTML5` 页面中的 `WebSocket` 访问地址需要修改为: `wss://127.0.0.1:8199/ws`。服务端示例代码: + +```go +package main + +import ( + "net/http" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gorilla/websocket" +) + +func main() { + var ( + s = g.Server() + logger = g.Log() + wsUpGrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + // In production, you should implement proper origin checking + return true + }, + Error: func(w http.ResponseWriter, r *http.Request, status int, reason error) { + // Error callback function. + }, + } + ) + + s.BindHandler("/ws", func(r *ghttp.Request) { + ws, err := wsUpGrader.Upgrade(r.Response.Writer, r.Request, nil) + if err != nil { + r.Response.Write(err.Error()) + return + } + defer ws.Close() + + var ctx = r.Context() + for { + msgType, msg, err := ws.ReadMessage() + if err != nil { + break + } + logger.Infof(ctx, "received message: %s", msg) + if err = ws.WriteMessage(msgType, msg); err != nil { + break + } + } + logger.Info(ctx, "websocket connection closed") + }) + s.EnableHTTPS("certs/server.crt", "certs/server.key") + s.SetServerRoot("static") + s.SetPort(8000) + s.Run() +} +``` + +## 示例结果展示 + +我们首先执行示例代码 `main.go`,随后访问页面 [http://127.0.0.1:8199/](http://127.0.0.1:8199/),随意输入请求内容并提交,随后在服务端关闭程序。可以看到,页面会回显提交的内容信息,并且即时展示 `websocket` 的连接状态的改变,当服务端关闭时,客户端也会即时地打印出关闭信息。 + +![](/markdown/670be5bdaae78e5cd183fade39dc20e7.png) + +## Websocket安全校验 + +`GoFrame` 框架的 `websocket` 模块并不会做同源检查( `origin`),也就是说,这种条件下的`websocket`允许完全跨域。 + +安全的校验需要由业务层来处理,安全校验主要包含以下几个方面: + +1. `origin` 的校验: 业务层在执行 `r.WebSocket()` 之前需要进行 `origin` 同源请求的校验;或者按照自定义的处理对请求进行校验(如果请求提交参数);如果未通过校验,那么调用 `r.Exit()` 终止请求。 +2. `websocket` 通信数据校验: 数据通信往往都有一些自定义的数据结构,在这些通信数据中加上鉴权处理逻辑; + +## WebSocket Client 客户端 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gorilla/websocket" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + ) + + // Connect to WebSocket server using default dialer + ws, _, err := websocket.DefaultDialer.Dial("ws://127.0.0.1:8000/ws", nil) + if err != nil { + logger.Fatalf(ctx, "dial failed: %+v", err) + } + // Ensure connection is closed when function returns + defer ws.Close() + + // Send a test message to the server + err = ws.WriteMessage(websocket.TextMessage, []byte("hello")) + if err != nil { + logger.Fatalf(ctx, "ws.WriteMessage failed: %+v", err) + } + + // Read the server's response + _, msg, err := ws.ReadMessage() + if err != nil { + logger.Fatalf(ctx, "ws.ReadMessage failed: %+v", err) + return + } + + logger.Infof(ctx, `received message: %s`, msg) + + // Cleanly close the connection by sending a close message + // This is important for proper connection cleanup + err = ws.WriteMessage(websocket.CloseMessage, []byte("going to close")) + if err != nil { + logger.Fatalf(ctx, "ws.WriteMessage failed: %+v", err) + } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..b8186a3e301 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\345\271\263\346\273\221\351\207\215\345\220\257\347\211\271\346\200\247.md" @@ -0,0 +1,243 @@ +--- +slug: '/docs/web/senior-hot-reload' +title: '平滑重启特性' +sidebar_position: 3 +hide_title: true +keywords: [平滑重启,热重启,GoFrame,WebServer,服务管理,框架支持,命令行终端,特性开启,多服务管理,HTTPS支持] +description: '在使用GoFrame框架时启用和管理平滑重启特性。通过该功能,WebServer在更新版本或者重启时,无需中断已有请求,从而提高服务稳定性和用户体验。支持在*nix系统下的平滑重启和Windows系统下的完整重启,并提供Web和命令行的管理方法。还展示了具体的使用示例,包括基本使用、HTTPS支持和多服务多端口场景的平滑管理。' +--- + +`平滑重启`( `热重启`)是指 `WebServer` 在重启的时候不会中断已有请求的执行。该特性在不同的项目版本发布的时候特别有用,例如,当需要先后发布两个版本: `A`、 `B`,那么在 `A` 执行的过程当中,我们可以将 `B` 的程序发布 **直接覆盖** `A` 的程序, 并使用平滑重启特性(使用 `Web` 或者 `命令行`)无缝地将请求过渡到新版本的服务中。 + +`GoFrame` 框架支持非常方便的 `Web管理功能`,也就是说我们可以通过Web页面/接口直接进行 `Server` 的重启/关闭等管理操作。同时,框架也支持通过 `命令行终端指令`(仅限 `*nix` 系统)的形式进行 `Server` 的重启/关闭等管理操作。 + +## 特性开启 + +默认情况下平滑重启特性是关闭的,可以通过 `graceful` 配置选项打开,具体请查看 `WebServer` 的配置管理章节: [服务配置-配置文件模板](../服务配置/服务配置-配置文件模板.md) +:::tip +目前平滑重启特性需要在本地随机打开一个端口的 `tcp` 监听服务用于新老进程通信交换状态信息。 +::: +## 注意事项 + +- 该特性仅限于 `*nix` 系统( `Linux/Unix/FreeBSD` 等等),在 `Windows` 下仅支持完整重启功能(请求无法平滑过渡)。 +- 测试平滑重启特性时请不要使用 `IDE run`(例如 `Goland`)或者 `go run` 命令来运行进程,因为这两种方式本身会创建父进程来管理运行的 `Go` 进程,会引起平滑重启时子父进程状态交换的失败。 +- 后续示例中的 `SetGraceful` 配置方法在 `v2.7.4` 版本后新增,低于 `v2.7.4` 版本请使用配置管理方式启用平滑重启特性。 + +## 管理方法 + +我们先来看一下WebServer中涉及到管理操作方法有哪些: + +```go + + func (s *Server) Restart + (newExeFilePath... + string + ) error +func (s *Server) Shutdown + ( + ) error + +func (s *Server) EnableAdmin + (pattern ... + string + ) + + +``` + +`Restart` 用于重启服务( `*nix` 系统下为平滑重启, `windows` 下为完整重启), `Shutdown` 用于关闭服务, `EnableAdmin` 用于将管理页面注册到指定的路由规则上,默认地址是 `/debug/admin`(我们可以指定一个私密的管理地址,也可以使用中间件来对该页面进行鉴权)。 + +以下对其中两个方法做详细说明。 + +### Restart + +`Restart` 的参数可指定自定义重启的可执行文件路径( `newExeFilePath`),不传递时默认为原可执行文件路径。特别是在windows系统下,当可执行文件正在使用时,无法对其进行文件替换更新(新版本文件替换老版本文件)。当指定自定义的可执行文件路径后, `Server` 重启时将会执行新版本的可执行文件,不再使用老版本文件,这种特性简化了在某些系统上的版本更新流程。 + +### EnableAdmin + +- 首先,该方法为用户管理 `Server` 提供了简便的页面和接口,在单 `Server` 下管理非常方便,直接访问管理页面点击对应链接即可。需要注意的是,由于带有管理功能,如果是在生产环境上,建议自定义该管理地址为一个私密地址。 +- 同时, `EnableaAdmim` 提供的 `restart` 接口也支持自定义可执行文件路径,直接通过GET参数往restart接口传递 `newExeFilePath` 变量即可,例如: [http://127.0.0.1/debug/admin/restart?newExeFilePath=xxxxxxx](http://127.0.0.1/debug/admin/restart?newExeFilePath=xxxxxxx) +- 此外,在大多数时候, `Server` 往往不只有1个节点,因此大多数服务管理运维中,例如:重启操作,当然不是直接访问每个 `Server` 的 `admin` 页面手动执行重启操作。而是充分利用 `admin` 页面提供的功能接口,通过接口控制来实现统一的 `Server` 管理控制。 + +### 示例1:基本使用 + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writeln("哈喽!") + }) + s.BindHandler("/pid", func(r *ghttp.Request) { + r.Response.Writeln(gproc.Pid()) + }) + s.BindHandler("/sleep", func(r *ghttp.Request) { + r.Response.Writeln(gproc.Pid()) + time.Sleep(10 * time.Second) + r.Response.Writeln(gproc.Pid()) + }) + s.SetGraceful(true) + s.EnableAdmin() + s.SetPort(8199) + s.Run() +} +``` + +我们通过以下几个步骤来测试平滑重启: + +1. 访问 [http://127.0.0.1:8199/pid](http://127.0.0.1:8199/pid) 查看当前进程的pid + +![](/markdown/7b28a1e44a96f3410e3e9a6d53a59523.png) + +2. 访问 [http://127.0.0.1:8199/sleep](http://127.0.0.1:8199/sleep),这个页面将会执行10秒,用于测试重启时该页面请求执行是否会断掉 + +![](/markdown/1f7d22a5a51104abc7ad80b2181e3fc6.png) + +3. 访问 [http://127.0.0.1:8199/debug/admin](http://127.0.0.1:8199/debug/admin),这是 `s.EnableAdmin` 后默认注册的一个WebServer管理页面 + +![](/markdown/8e54c1d520f7856a88e951e3391e1f3b.png) + +4. 随后我们点击 `restart` 管理链接,WebServer将会立即平滑重启( `*nix` 系统下) + +![](/markdown/67eeff6f5cd3e726e24dfc17a5128db4.png) + +同时在终端也会输出以下信息: + +```shell + 2018-05-18 11:02:04.812 11511: http server started listening on [:8199] + 2018-05-18 11:02:09.172 11511: server reloading + 2018-05-18 11:02:09.172 11511: all servers shutdown + 2018-05-18 11:02:09.176 16358: http server restarted listening on [:8199] + + +``` + +5. 我们可以发现在整个操作中, `sleep` 页面的执行并没有被中断,继续等待几秒,当 `sleep` 执行完成后,页面输出内容为: + +![](/markdown/e21dfd1e4d9c4cd0a8e4ce042dc5dcaf.png) + +6. 可以发现, `sleep` 页面输出的进程pid和之前的不一样了,代表请求的执行被新的进程平滑接管,旧的服务进程也随之销毁; + + +### 示例2:HTTPS支持 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("哈罗!") + }) + s.SetGraceful(true) + s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") + s.EnableAdmin() + s.SetPort(8200) + s.Run() +} +``` + +`GoFrame` 框架的平滑重启特性对于HTTPS的支持也是相当友好和简便,操作步骤如下: + +1. 访问 [https://127.0.0.1:8200/debug/admin/restart](https://127.0.0.1:8200/debug/admin/restart) 平滑重启HTTPS服务; +2. 访问 [https://127.0.0.1:8200/debug/admin/shutdown](https://127.0.0.1:8200/debug/admin/shutdown) 平滑关闭WebServer服务; + +在命令行终端可以看到以下输出信息: + +```shell + 2018-05-18 11:13:05.554 17278: https server started listening on [:8200] +2018-05-18 11:13:21.270 17278: server reloading +2018-05-18 11:13:21.270 17278: all servers shutdown +2018-05-18 11:13:21.278 17319: https server reloaded listening on [:8200] +2018-05-18 11:13:34.895 17319: server shutting down +2018-05-18 11:13:34.895 17269: all servers shutdown + + +``` + +### 示例3:多服务及多端口 + +`GoFrame` 框架的平滑重启特性相当强大及稳定,不仅仅支持单一服务单一端口监听管理,同时也支持多服务多端口等复杂场景的监听管理。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + s1 := g.Server("s1") + s1.SetGraceful(true) + s1.EnableAdmin() + s1.SetPort(8100, 8200) + s1.Start() + + s2 := g.Server("s2") + s2.SetGraceful(true) + s2.EnableAdmin() + s2.SetPort(8300, 8400) + s2.Start() + + g.Wait() +} +``` + +以上示例演示的是两个WebServer `s1` 及 `s2`,分别监听 `8100`, `8200` 及 `8300`, `8400`。我们随后访问 [http://127.0.0.1:8100/debug/admin/reload](http://127.0.0.1:8100/debug/admin/reload) 平滑重启服务,然后再通过 [http://127.0.0.1:8100/debug/admin/shutdown](http://127.0.0.1:8100/debug/admin/shutdown) 平滑关闭服务,最终在终端打印出的信息如下: + +```html +2018-05-18 11:26:54.729 18111: http server started listening on [:8400] +2018-05-18 11:26:54.729 18111: http server started listening on [:8100] +2018-05-18 11:26:54.729 18111: http server started listening on [:8300] +2018-05-18 11:26:54.729 18111: http server started listening on [:8200] +2018-05-18 11:27:08.203 18111: server reloading +2018-05-18 11:27:08.203 18111: all servers shutdown +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8300] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8400] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8200] +2018-05-18 11:27:08.207 18124: http server reloaded listening on [:8100] +2018-05-18 11:27:19.379 18124: server shutting down +2018-05-18 11:27:19.380 18102: all servers shutdown +``` + +## 命令行管理 + +`GoFrame` 框架除了提供 `Web` 方式的管理能力以外,也支持命令行方式来进行管理,由于命令行采用了 `信号` 进行管理。 + +### 重启服务 + +使用 `SIGUSR1` 信号量实现,使用方式: + +```shell + + kill -SIGUSR1 进程ID + +``` + +### 关闭服务 + +使用 `SIGINT/SIGQUIT/SIGKILL/SIGHUP/SIGTERM` 其中任意一个信号量来实现,使用方式: + +```shell + + kill -SIGTERM 进程ID + +``` + +## 其他管理方式 + +由于 `GoFrame` 框架的 `WebServer` 采用了单例设计,因此任何地方都可以通过 `g.Server(名称)` 来获得对应 `Server` 的单例对象,随后通过 `Restart` 和 `Shutdown` 方法可以实现对该 `Server` 的管理。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..0cb7fbd7d80 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\346\234\215\345\212\241\346\227\245\345\277\227\347\256\241\347\220\206.md" @@ -0,0 +1,117 @@ +--- +slug: '/docs/web/senior-logging' +title: '服务日志管理' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,Server日志管理,access log,error log,日志配置,日志格式,错误日志,请求日志,自定义日志处理,glog] +description: '使用GoFrame框架进行服务日志管理,包括access log和error log的配置与使用。详细解释了日志配置对象及属性,如Logger、LogPath、ErrorStack等,并提供了详细的日志格式说明和自定义日志处理方法。同时,还涉及如何通过配置文件和代码方法进行日志设置,以及日志格式和错误日志的记录方式和示例。' +--- + +`GoFrame` 框架提供了完善的 `Server` 日志管理功能,包括 `access log` 以及 `error log`,推荐使用配置文件的方式统一配置管理。 + +## 日志配置 + +### 配置对象 + +请查看API文档: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig](https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#ServerConfig) + +### 配置属性 + +日志相关配置属性如下: + +```go +Logger *glog.Logger // Logger for server. +LogPath string // Directory for storing logging files. +LogStdout bool // Printing logging content to stdout. +ErrorStack bool // Logging stack information when error. +ErrorLogEnabled bool // Enable error logging files. +ErrorLogPattern string // Error log file pattern like: error-{Ymd}.log +AccessLogEnabled bool // Enable access logging files. +AccessLogPattern string // Error log file pattern like: access-{Ymd}.log +``` + +简要说明: + +1. 默认情况下,日志不会输出到文件中,而是直接打印到终端。默认情况下的 `access` 日志终端输出是关闭的,仅有 `error` 日志默认开启。 +2. 所有的选项均可通过 `Server.Set*` 方法设置,大部分选项可以通过 `Server.Get*` 方法获取。 +3. `Logger` 是一个自定义的日志管理对象,开发者也可以传递一个完整的日志管理对象,忽略其他日志选项配置。 +4. `LogPath` 属性用于设置日志目录,只有在设置了日志目录的情况下才会输出日志到日志文件中。 +5. `ErrorLogPattern` 及 `AccessLogPattern` 用于配置日志文件名称格式,默认为 `error-{Ymd}.log` 及 `access-{Ymd}.log`,例如: `error-20191212.log`, `access-20191212.log`。 +6. 其他配置选项说明请参考注释和API文档。 + +### 配置文件 + +官方推荐使用配置文件的方式来管理服务配置以及日志日志配置。 一个参考的日志配置内容示例(以 `yaml` 格式为例): + +```yaml +server: + LogPath: "/var/log/gf-demos/server" + LogStdout: false + ErrorStack: true + ErrorLogEnabled: true + ErrorLogPattern: "error.{Ymd}.log" + AccessLogEnabled: true + AccessLogPattern: "access.{Ymd}.log" +``` + +当 `Server` 启动时将会自动去读取默认配置文件 `config.yaml` 中的 `server` 节点配置。 + +### 配置方法 + +日志的配置也可以通过 `Server` 对象的 `Set*` 方法来进行配置,参考 [服务配置](../服务配置/服务配置.md) 章节。 + +## 日志格式 + +配置文件的方式比较简单,这里不再示例说明。以下示例通过配置方法的方式进行对 `Server` 进行配置。 + +### 请求日志 + +请求日志: + +```html +2018-04-20 18:11:57.344 200 "GET http 127.0.0.1:8199 /log/access HTTP/1.1" 0.120, 127.0.0.1, "", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/53.0.2785.143 Chrome/53.0.2785.143 Safari/537.36" +``` + +日志格式: + +```html +请求时间(精确到毫秒) HTTP状态码 "请求方式 请求前缀 请求地址 请求协议" 执行时间(秒) 客户端IP "来源URL", "UserAgent" +``` + +其中, `请求前缀` 为 `http` 或者 `https`, `请求协议` 往往为 `HTTP/1.0` 或者 `HTTP/1.1`。 +:::warning +注意,日志中记录的 `执行时间` 单位为 `秒`,绝大多数情况下看到的时间几乎都是 `0.xxx` 秒时间,也就是说执行时间都是毫秒级不到1秒。 +::: +### 错误日志 + +错误日志: + +```html +2019-12-20 20:10:56.484 [ERRO] 500, "GET http 127.0.0.1:8199 /log/error HTTP/1.1" 0.210, 127.0.0.1, "", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36" +Stack: +1. OMG + 1). main.main.func1 + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/net/ghttp/server/log/log_error.go:10 + +``` + +错误信息会打印出对应错误产生的堆栈信息(堆栈信息中不包含框架内部调用信息),以便于错误定位以及开发者分析问题原因。 +:::tip +`Server` 产生的任何 `panic` 错误都将会被自动捕获到错误日志中,因此对于业务端程序来讲,无论是在控制器中、业务封装层、数据模型中,如果产生了错误想要直接退出业务请求处理,直接 `panic` 即可。 +::: +## 自定义日志处理 + +开发者可以自定义处理 `Server` 的请求日志,方法有两种: + +1. 可以通过日志配置项传递自定义的 `*glog.Logger` 对象。 +2. 可以通过中间件来统一捕获处理,参考 [路由管理-中间件/拦截器](../%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86/%E8%B7%AF%E7%94%B1%E7%AE%A1%E7%90%86-%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%8B%A6%E6%88%AA%E5%99%A8/%E4%B8%AD%E9%97%B4%E4%BB%B6%E6%8B%A6%E6%88%AA%E5%99%A8-%E5%9F%BA%E6%9C%AC%E4%BB%8B%E7%BB%8D.md) 章节。 + +## `Server` 日志与业务日志 + +这是一个 `FAQ`。 + +我们需要注意的是,这里提到的日志都是 `Server` 的日志,类似于 `nginx`, `apache`, `tomcat` 等等一系列 `Web Server` 服务的日志,只允许 `Server` 输出内容,开发者无法往 `Server` 的日志文件中写入日志内容,并且日志类型和格式是完全固定的。 + +`GoFrame` 框架也提供了日志模块,由 `glog` 日志组件实现,开发者通过 `glog` 组件打印的日志属于业务日志,程序业务代码可以决定输出什么内容,输出到哪里,输出格式是什么样等。并且常用 `g.Log()` 方法来输出业务日志,该方法支持自动读取配置文件中的 `logger` 配置项。具体请参考 [日志组件](../../核心组件/日志组件/日志组件.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..76726b82fc2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\350\207\252\345\256\232\344\271\211\347\212\266\346\200\201\347\240\201\345\244\204\347\220\206.md" @@ -0,0 +1,100 @@ +--- +slug: '/docs/web/senior-status-handler' +title: '自定义状态码处理' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,状态码处理,自定义状态码,WebServer,错误页面,页面跳转,状态码回调,r.RedirectTo,BindStatusHandler] +description: '在GoFrame框架中实现自定义状态码处理。通过使用BindStatusHandler方法,开发者可以为WebServer指定的状态码如404、403、500等进行自定义处理,包括展示自定义错误信息或页面内容,以及实现错误页面的重定向。示例代码演示了如何进行基本设置和批量状态码处理。' +--- + +我们可以对WebServer指定的状态码进行自定义处理,例如针对常见的 `404/403/500` 等错误,我们可以展示自定义的错误信息、页面内容,或者跳转到一个特定的页面。 + +相关方法如下: + +```go +func (s *Server) BindStatusHandler(status int, handler HandlerFunc) +func (s *Server) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) + +func (d *Domain) BindStatusHandler(status int, handler HandlerFunc) +func (d *Domain) BindStatusHandlerByMap(handlerMap map[int]HandlerFunc) +``` + +可以看到,我们可以使用 `BindStatusHandler` 或者 `BindStatusHandlerMap` 来实现针对指定的状态码进行自定义的回调函数处理,并且该特性也支持针对特定的域名绑定。我们来看几个简单的示例。 + +## 基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request){ + r.Response.Writeln("halo 世界!") + }) + s.BindStatusHandler(404, func(r *ghttp.Request){ + r.Response.Writeln("This is customized 404 page") + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,我们访问没有绑定的路由页面,例如 [http://127.0.0.1:8199/test](http://127.0.0.1:8199/test) ,可以看到,页面显示了我们期望的返回结果: `This is customized 404 page`。 + +此外,常见的Web页面请求错误状态码处理方式,是引导用户跳转到指定的错误页面,因此,在状态码回调处理函数中,我们可以使用 `r.RedirectTo` 方法来进行页面跳转,示例如下: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/status/:status", func(r *ghttp.Request) { + r.Response.Write("woops, status ", r.Get("status"), " found") + }) + s.BindStatusHandler(404, func(r *ghttp.Request){ + r.Response.RedirectTo("/status/404") + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,我们手动通过浏览器访问一个不存在的页面,例如 [http://127.0.0.1:8199/test](http://127.0.0.1:8199/test) ,可以看到,页面被引导跳转到了 [http://127.0.0.1:8199/status/404](http://127.0.0.1:8199/status/404) 页面,并且可以看到页面返回内容: `woops, status 404 found` + +## 批量设置 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindStatusHandlerByMap(map[int]ghttp.HandlerFunc { + 403 : func(r *ghttp.Request){r.Response.Writeln("403")}, + 404 : func(r *ghttp.Request){r.Response.Writeln("404")}, + 500 : func(r *ghttp.Request){r.Response.Writeln("500")}, + }) + s.SetPort(8199) + s.Run() +} +``` + +可以看到,我们可以通过 `BindStatusHandlerByMap` 方法对需要自定义的状态码进行批量设置。该示例程序执行后,当服务接口返回的状态码为 `403/404/500` 时,接口将会返回对应的状态码数字。 + +## 注意事项 + +在自定义状态码处理方法中如果涉及到内容的输出,往往需要使用 `r.Response.ClearBuffer()` 方法将原本缓冲区的输出内容清空。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" new file mode 100644 index 00000000000..08ae568e794 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\235\231\346\200\201\346\226\207\344\273\266\346\234\215\345\212\241.md" @@ -0,0 +1,175 @@ +--- +slug: '/docs/web/senior-static-server' +title: '静态文件服务' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,静态文件服务,静态文件配置,文件服务开启,静态文件目录,静态文件映射,URI重写,跨域支持,静态目录优先级,文件列表展示] +description: '了解如何在GoFrame框架中配置和使用静态文件服务,包括静态文件目录设置、静态文件服务开启条件、URI与静态目录映射、自定义URI重写规则及跨域配置示例,帮助开发者有效管理和优化项目中的静态资源访问。' +--- + +## 静态文件服务配置 + +默认情况下, `GoFrame Server` 关闭了静态文件服务的功能,如果开发者配置了 **静态文件目录**,那么静态文件服务将会自动开启。 + +静态文件服务涉及到的常用配置方法如下: + +```go +// 设置http server参数 - ServerRoot +func (s *Server) SetServerRoot(root string) + +// 添加静态文件搜索目录,必须给定目录的绝对路径 +func (s *Server) AddSearchPath(path string) + +// 设置http server参数 - IndexFiles,默认展示文件,如:index.html, index.htm +func (s *Server) SetIndexFiles(index []string) + +// 是否允许展示访问目录的文件列表 +func (s *Server) SetIndexFolder(enabled bool) + +// 添加URI与静态目录的映射 +func (s *Server) AddStaticPath(prefix string, path string) + +// 静态文件服务总开关:是否开启/关闭静态文件服务 +func (s *Server) SetFileServerEnabled(enabled bool) + +// 设置URI重写规则 +func (s *Server) SetRewrite(uri string, rewrite string) + +// 设置URI重写规则(批量) +func (s *Server) SetRewriteMap(rewrites map[string]string) +``` + +简要介绍: + +1. `IndexFiles` 为当访问目录时默认检索的文件名称列表(按照slice先后顺序进行检索),当检索的文件存在时则返回文件内容,否则展示目录列表( `SetIndexFolder` 为 `true` 时),默认的 `IndexFiles` 为: `index.html, index.htm`; +2. `SetIndexFolder` 为设置是否在用户访问文件目录,且没有在目录下检索到 `IndexFiles` 时,则展示目录下的文件列表,默认为关闭; +3. `SetServerRoot` 为设置默认提供服务的静态文件目录,该目录会被自动添加到 `SearchPath` 中的第一个搜索路径; +4. `AddSearchPath` 为添加静态文件检索目录,可以有多个,按照文件目录添加的先后顺序执行优先级检索; +5. `AddStaticPath` 为添加 `URI` 与目录路径的映射关系,可以自定义静态文件目录的访问URI规则; +6. `SetRewrite`/ `SetRewriteMap` 为重写规则设置(类似于 `nginx` 的 `rewrite`),严格上来讲不仅仅是静态文件服务,当然也支持动态的路由注册的 `rewrite`; +:::tip +设置静态文件服务的目录路径时,可以使用绝对路径,也可以使用相对路径,例如设置当前运行目录提供静态文件服务可以使用 `SetServerRoot(".")`。 + +开发者可以设置多个文件目录来提供静态文件服务,并且可以设置目录及URI的优先级,但是一旦通过 `SetFileServerEnabled` 关闭了静态服务,所有静态文件/目录的访问都将失效。 +::: +## 示例1, 基本使用 + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// 静态文件服务器基本使用 +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.SetPort(8199) + s.Run() +} +``` + +## 示例2,静态目录映射 + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// 静态文件服务器,支持自定义静态目录映射 +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.SetPort(8199) + s.Run() +} +``` + +## 示例3,静态目录映射,优先级控制 + +静态目录映射的优先级按照绑定的 `URI` 精准度进行控制,绑定的URI越精准(深度优先匹配),那么优先级越高。 + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +// 静态文件服务器,支持自定义静态目录映射 +func main() { + s := g.Server() + s.SetIndexFolder(true) + s.SetServerRoot("/Users/john/Temp") + s.AddSearchPath("/Users/john/Documents") + s.AddStaticPath("/my-doc", "/Users/john/Documents") + s.AddStaticPath("/my-doc/test", "/Users/john/Temp") + s.SetPort(8199) + s.Run() +} +``` + +其中,访问 `/my-doc/test` 的优先级会比 `/my-doc` 高,因此假如 `/Users/john/Documents` 目录下存在 `test` 目录(与自定义的 `/my-doc/test` 冲突),将会无法被访问到。 + +## 示例4, `URI` 重写 + +`GoFrame` 框架的静态文件服务支持将任意的 `URI` 重写,替换为制定的 `URI`,使用 `SetRewrite/SetRewriteMap` 方法。 + +示例,在 `/Users/john/Temp` 目录下只有两个文件 `test1.html` 及 `test2.html`。 + +```go +package main + +import "github.com/gogf/gf/v2/frame/g" + +func main() { + s := g.Server() + s.SetServerRoot("/Users/john/Temp") + s.SetRewrite("/test.html", "/test1.html") + s.SetRewriteMap(g.MapStrStr{ + "/my-test1": "/test1.html", + "/my-test2": "/test2.html", + }) + s.SetPort(8199) + s.Run() +} +``` + +``` + +``` + +执行后, + +1. 当我们访问 `/test.html`,其实最终被重写到了 `test1.html`,返回的是该文件内容; +2. 当我们访问 `/my-test1`,其实最终被重写到了 `test1.html`,返回的是该文件内容; +3. 当我们访问 `/my-test2`,其实最终被重写到了 `test2.html`,返回的是该文件内容; + +## 示例5,跨域 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/glog" +) + +func beforeServeHook(r *ghttp.Request) { + glog.Debugf(r.GetCtx(), "beforeServeHook [is file:%v] URI:%s", r.IsFileRequest(), r.RequestURI) + r.Response.CORSDefault() +} + +// 利用hook注入跨域配置 +func main() { + s := g.Server() + s.BindHookHandler("/*", ghttp.HookBeforeServe, beforeServeHook) + s.SetServerRoot(".") + s.SetFileServerEnabled(true) + s.SetAddr(":8080") + s.Run() +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..473438a7c1b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/WEB\346\234\215\345\212\241\345\274\200\345\217\221/\351\253\230\347\272\247\347\211\271\346\200\247/\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/web/senior' +title: '高级特性' +sidebar_position: 11 +hide_title: true +keywords: [高级特性,GoFrame,GoFrame框架,Web开发,后端框架,编程技巧,API设计,性能优化,模块化,代码复用] +description: 'GoFrame框架中的高级特性,适用于有一定编程经验的开发者。通过学习这些特性,您将掌握更灵活的API设计技巧,提高代码的性能和可维护性,实现模块化和代码复用的最佳实践。' +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" new file mode 100644 index 00000000000..629a4b8b836 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\205\266\344\273\226\350\265\204\346\226\231.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/other' +title: '其他资料' +hide_title: true +sidebar_position: 999 +keywords: [其他资料,GoFrame,GoFrame框架,文档,资料,开发,指南,编程,技术,参考] +description: '与GoFrame框架相关的各种其他资料,为开发人员提供了全面的使用指南和技术参考,帮助开发者深入理解并高效运用GoFrame框架进行编程开发。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" new file mode 100644 index 00000000000..43a0bdc9b95 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/Go Module.md" @@ -0,0 +1,105 @@ +--- +slug: '/docs/install-go/go-module' +title: 'Go Module' +sidebar_position: 1 +hide_title: true +keywords: [Go Module,GoFrame,包管理工具,依赖管理,go.mod,Goland IDE,vgo,package,GOPROXY,go get] +description: 'Go Module这一包管理工具的使用方法,涵盖了如何通过Goland IDE和命令行进行依赖管理,并提供了设置go.mod文件及使用代理下载GoFrame框架的实用指导。通过开启Go Module特性和选择适当的Proxy地址,能够高效管理项目包依赖,从而提升开发效率。' +--- + +`Go Module` 是从Go版本 `1.11.1` 开始官方提供的包管理工具,用于解决`Go`项目的包管理及依赖,类似于`PHP`的 `composer`、`Nodejs`的 `npm`。本章节会对 `Go Module` 的一些常用的实用的命令/设置进行介绍,更详细的介绍请查看官方文档: [https://github.com/golang/go/wiki/Modules](https://github.com/golang/go/wiki/Modules) + +## 关于 `go.mod` + +`go.mod` 是Go项目的依赖描述文件,该文件主要用来描述两个事情: + +1. 当前项目名( `module`)是什么。每个项目都应该设置一个名称,当前项目中的包( `package`)可以使用该名称进行相互调用。 +2. 当前项目依赖的第三方包名称。项目运行时会自动分析项目中的代码依赖,生成 `go.sum` 依赖分析结果,随后go编译器会去下载这些第三方包,然后再编译运行。 + +我们可以看到之前的 `hello world` 项目下有一个自动生成的 `go.mod` 文件,其内容如下: + +```go +module hello +``` + +其中, `hello` 为当前项目的名称,在我们初始化项目的时候`Goland IDE`自动帮助我们生成了该文件,默认情况下该`module`的名称为目录的名称,该名称可以随意设置。 + +## 使用 `go.mod` + + +### 使用Goland IDE + +1. 设置 `Goland` 启用 `Go Module`特性 + + ![alt text](QQ_1733020920028.png) + + 在下载第三方依赖包时,您需要科学上网。笔者本地设置了一个环境变量`GOPROXY`用于科学上网拉取依赖: + + ```bash + GOPROXY=https://goproxy.cn + ``` + + 如果您本地环境已经有 `VPN` 功能,那么可以忽略 `GOPROXY` 的设置,可以添加`direct` 后缀表示不使用代理。 + ```bash + GOPROXY=https://goproxy.cn,direct + ``` + + + 其中 `GOPROXY` 请输入代理地址下载依赖包,常见的`GOPROXY`反向代理地址有: + + - `https://goproxy.cn` + - `https://goproxy.io` + - `https://mirrors.aliyun.com/goproxy/` + + 详见Go官网说明: [https://github.com/golang/go/wiki/Modules#are-there-always-on-module-repositories-and-enterprise-proxies](https://github.com/golang/go/wiki/Modules#are-there-always-on-module-repositories-and-enterprise-proxies) + + +2. 手动修改 `go.mod` 文件如下: + + ```go + module hello + + require github.com/gogf/gf/v2 latest + ``` + + 增加 `GoFrame` 框架的依赖,其中 `latest` 表示使用`github.com/gogf/gf/v2`最新版本,`IDE`将会立即去更新下载框架代码。成功后,`IDE`将会修改 `go.mod` 文件并生成 `go.sum` 依赖分析文件。该`go.sum`文件为该项目所有的第三方依赖,通常也应该推送到版本管理仓库中。 + +3. 随后 `go.mod` 文件被自动更新为: + + ```go + module hello + + require github.com/gogf/gf/v2 v2.8.1 + ``` + + 其中 `v2.8.1` 表示`Go Module`检测到的最新框架版本。 + + +### 使用命令行 + +1. 打开 `Terminal`,在项目根目录下执行: + + ```bash + export GO111MODULE=on GOPROXY=https://goproxy.cn; go get -u github.com/gogf/gf/v2 + ``` + + 该命令将会立即下载最新稳定版本的 `GoFrame` 框架。其中 `export GO111MODULE=on;` 表示开启 `Go Module` 特性(Go `1.11.x` 版本默认关闭,需要手动开启), `export GOPROXY=https://goproxy.cn` 表示使用代理下载,原因你懂的,并且也能极大提高依赖包下载速度。代理地址也可使用: + + + - `https://goproxy.cn` + - `https://goproxy.io` + - `https://mirrors.aliyun.com/goproxy` + + +2. 随后 `go.mod` 文件内容被自动更新为: + + ```go + module hello + + + require github.com/gogf/gf/v2 v2.8.1 + ``` + + 且生成了新的 `go.sum` 依赖分析文件。 + + diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020124554.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020124554.png" new file mode 100644 index 00000000000..33ce2c0b51e Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020124554.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020331260.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020331260.png" new file mode 100644 index 00000000000..d03889f8013 Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020331260.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020422698.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020422698.png" new file mode 100644 index 00000000000..005cd5e74ae Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020422698.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020474019.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020474019.png" new file mode 100644 index 00000000000..99f2b51479b Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020474019.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020511081.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020511081.png" new file mode 100644 index 00000000000..d75b6b4e1ee Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020511081.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020920028.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020920028.png" new file mode 100644 index 00000000000..2422c9ba929 Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733020920028.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733023014586.png" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733023014586.png" new file mode 100644 index 00000000000..02adef8f46a Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/QQ_1733023014586.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" new file mode 100644 index 00000000000..e11004315aa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/install-go' +title: '准备开发环境' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,安装GoFrame,GoFrame快速开始,GoFrame教程,GoFrame文档,开发环境配置,安装指南,Go语言,GoFrame开发] +description: 'GoFrame框架的准备工作,包括如何安装GoFrame和配置开发环境。该指南为新手提供快速开始的步骤和GoFrame的基础知识,帮助您快速搭建GoFrame应用程序开发环境。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..57ba5d52489 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\345\274\200\345\217\221\347\216\257\345\242\203\351\205\215\347\275\256.md" @@ -0,0 +1,128 @@ +--- +slug: '/docs/install-go/config-env' +title: '开发环境配置' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,开发环境配置,Go环境变量,Golang,go build,Goland,IDE工具配置,golint,golangci-lint] +description: '在GoFrame框架下开发环境的配置,主要包括Go语言的环境变量设置以及Goland中工具的配置。详细讲解了如何设置GOROOT、GOPATH和PATH等环境变量,并提供了在*nix和Windows系统下的具体配置步骤。同时,还介绍了go fmt、golangci-lint、goimports和golint工具在Goland中的集成和配置方法,帮助开发者提高代码质量和开发效率。' +--- + +## Go环境变量 + +为方便开发,在开发环境往往需要设置三个环境变量: + +1. `$GOROOT`: `go` 的安装目录,配置后不会再更改; +2. `$GOPATH`: `go` 项目在本地的开发环境的的项目根路径(以便项目编译, `go build`, `go install`),不同的项目在编译的时候该环境变量可以不同; +3. `$PATH`(重要):需要将 `go` 的 `bin` 目录添加到系统 `$PATH` 中以便方便使用go的相关命令,配置后也不会再更改; + +`Go`的环境变量在官方文档中也有详情的说明,请参考链接: [https://golang.google.cn/doc/install/source](https://golang.google.cn/doc/install/source) + +:::tip +环境变量中的 `$GOOS` 和 `$GOARCH` 是比较实用的两个变量,可以用在不同平台的交叉编译中,只需要在 `go build` 之前设置这两个变量即可,这也是`Go`语言的优势之一:可以编译生成跨平台运行的可执行文件。例如,在 `Linux amd64` 架构下编译 `Windows x86` 的可执行文件,可以使用如下命令: + +``` +CGO_ENABLED=0 GOOS=windows GOARCH=386 go build hello.go +``` + +遗憾的是交叉编译暂不支持 `cgo` 方式,因此需要将环境变量 `$CGO_ENABLED` 设置为`0`,这样执行之后会在当前目录生成一个 `hello.exe` 的 `windows x86` 架构的可执行文件。 +::: + +### 环境变量设置 + +除了 `$PATH` 环境外,其他环境变量都是可选的。 + +为什么说这个步骤可选呢?因为未来的 `Go` 版本慢慢开始移除对 `$GOPATH`/ `$GOROOT` 的支持。此外,在 `Goland` 这个`IDE`中集成有 `Terminal` 功能,直接使用这个功能中已经设置好了环境变量。 + +![alt text](QQ_1733023014586.png) + +### `*nix` 下设置环境变量 + +在 `*nix` 系统下(`Linux/Unix/MacOS/*BSD` 等等),需要在 `/etc/profile` 中增加以下环境变量设置后,执行命令 `#source /etc/profile` 重新加载profile配置文件(或重新登录),将以下变量添加到用户的环境变量中: + +```bash +export GOROOT=/usr/local/go +export GOPATH=/Users/john/Workspace/Go/GOPATH +export PATH=$GOPATH/bin:$GOROOT/bin:$PATH +``` + +### `Windows` 下设置环境变量 + +`Windows`如何修改系统环境变量,以及修改环境变量 `PATH`,请参考网上教程( [百度](https://www.baidu.com/s?wd=Windows%20%E4%BF%AE%E6%94%B9%E7%B3%BB%E7%BB%9F%E7%8E%AF%E5%A2%83%E5%8F%98%E9%87%8F%20PATH) 或 [Google](https://www.google.com/search?q=Windows+修改系统环境变量+PATH))。 + +## IDE工具配置 + +本文以 `Goland` 开发工具为基础,介绍在该IDE下的常用工具配置。 + +常用的工具包括: + +1. `go fmt` : 统一的代码格式化工具(必须)。 +2. `golangci-lint` : 静态代码质量检测工具,用于包的质量分析(推荐)。 +3. `goimports` : 自动 `import` 依赖包工具(可选)。 +4. `golint` : 代码规范检测,并且也检测单文件的代码质量,比较出名的Go质量评估站点 [Go Report](https://goreportcard.com) 在使用(可选)。 + +### `go fmt`, `goimports`, `golangci-lint` + +由于这三个工具是 `Goland` 自带的,因此配置比较简单,参考以下图文操作示例: + +1. 在 `Goland` 的设置中,选择 `Tools` - `File Watchers`,随后选择添加 + + ![](/markdown/beffdaf59725b7091d27c05db1cbef06.jpg) + +2. 依次点击添加这3个工具,使用默认的配置即可 + + ![](/markdown/23d9056527febe75f82dcc8117f086fd.jpg) + +3. 随后在撸代码的过程中保存代码文件时将会自动触发这3个工具的自动检测。 + + +### `golint` 工具的安装及配置(可选) + +#### `golint` 的安装 + +由于 `Goland` 没有自带 `golint` 工具,因此首先要自己去下载安装该工具。 + +**如果有goproxy配置了**,可以直接 `go install golang.org/x/lint/golint@latest` 安装,就不需要使用下方命令了。 + +使用以下命令安装: + +```bash +mkdir -p $GOPATH/src/golang.org/x/ +cd $GOPATH/src/golang.org/x/ +git clone https://github.com/golang/lint.git +git clone https://github.com/golang/tools.git +cd $GOPATH/src/golang.org/x/lint/golint +go install +``` + +安装成功之后将会在 `$GOPATH/bin` 目录下看到自动生成了 `golint` 二进制工具文件。 + +#### `golint` 的配置 + +1. 随后在 `Goland` 的 `Tools` - `File Watchers` 配置下,通过复制 `go fmt` 的配置 + + ![](/markdown/d6e625d79c63024347705acfc013463c.jpg) + +2. 修改 `Name`, `Program`, `Arguments` 三项配置,其中 `Arguments` 需要加上 `-set_exit_status` 参数,如图所示: + + ![](/markdown/219fe697e559aa6980100557996686a0.jpg) + +3. 保存即可,随后在代码编写中执行保存操作时将会自动触发 `golint` 工具检测。 + + +### `golangci-lint` 的配置(可选) + +1.  随后在 `Goland` 的 `Tools` \- `File Watchers` 配置下,通过复制 `go fmt` 的配置 + ![](/markdown/267c777a8db90758dd8bad6013f60d7e.png) +2. 修改 `Name`, `Program`, `Arguments` 三项配置,其中 `Arguments` 需要加上 `run $FileDir$` 参数, `注意:` 如 `Advanced Options`的选项可以在机器比较慢的时取消选择,如图所示: + ![](/markdown/5bf774ae9e6d123efa9010dd223a618a.png) + +3. 保存即可,随后在代码编写中执行保存操作时将会自动触发 `golangci-lint` 工具检测。 + +4. 通过 `go Linter` 插件管理 `golangci-lint` 工具的配置,如下是 `go Linter` 的安装以及配置。 + ![](/markdown/0f0fbd2a3a937573cdc317bbe005a6cf.png) + + ![](/markdown/1c10390d9bfca56c0528f43f122b9ebd.png) + +## IDE代码风格配置 + +![](/markdown/b63649f6d3ac9d3a9eaadb2c94d00cb8.png)![](/markdown/1f470b73547b12cd36ff1d4c7328f847.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" new file mode 100644 index 00000000000..bb9a338ff95 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\216\257\345\242\203\345\256\211\350\243\205.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/install-go/index' +title: '环境安装' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,Go开发,Golang安装,Go环境配置,Goland,VSCode,IDE设置,JetBrains,Go程序,GoFrame框架] +description: '手把手的Golang开发环境和IDE配置教程,适合Golang初学者。详细介绍了如何下载和安装Go开发包,并推荐使用JetBrains的Goland作为开发IDE,支持GoFrame框架开发。此外,还包括VSCode的安装和使用步骤,帮助用户快速构建第一个Go程序。' +--- + +该章节为手摸手安装 `Golang` 开发环境及 `IDE` 配置教程,仅针对 `Golang` 萌新,老司机请忽略。 + +## Go开发环境安装 + +### Step1 - 下载Go开发包 + +访问`Go`国内镜像站下载页面 [https://golang.google.cn/dl/](https://golang.google.cn/dl/),并在页面最上方的版本中选择你当前的系统版本,会下载最新版本的`Go`开发包: + +![](/markdown/d3ce7f0e43ebf678adea8db4c46662d5.png) + +### Step2 - 安装引导 + +访问官方安装介绍页面 [https://golang.google.cn/doc/install](https://golang.google.cn/doc/install),按照当前系统版本执行对应的安装流程即可。 + +Windows( `msi`)和MacOS( `pkg`)推荐使用安装包的方式来安装。作者当前MacOS安装包( `pkg`)安装过程如下图所示: + +![](/markdown/80729ac6360ac646a39b696d32778d66.png) + +![](/markdown/afc21d8598a0bef86c1a53c8e6784bb6.png) + +![](/markdown/f3f59daf118e34e16a920bcdcf6391de.png) + +`Go`的开发包升级也是同样的过程。 + +## IDE开发环境安装 + +目前 `Go` 的 `IDE` 有两款比较流行,一款是 `VSCode+Plugins`(免费),另一款是 `JetBrains` 公司的 `Goland`(收费)。由于 `JetBrains` 也是 `GoFrame` 框架的赞助商,因此我们这里优先推荐使用 `Goland` 来作为开发IDE,下载及注册请参考网上教程( [百度](https://www.baidu.com/s?wd=goland%20安装) 或 [Google](https://www.google.com/search?q=goland+安装))。 + +`JetBrains` 的官方网站为: [https://www.jetbrains.com](https://www.jetbrains.com/?from=GoFrame) + +### Goland的使用 + +我们来创建第一个 `Go` 程序吧,老规矩,上 `hello world`。 + +#### Step1. 打开IDE + +![](/markdown/53e952d14b92225b865b2bca6aab7cd2.png) + +#### Step2. 创建项目 + +这里需要注意的是 `Go` 安装文件的路径( `SDK`), [官方安装文档](https://golang.google.cn/doc/install) 有详细说明,请仔细阅读。 + +其中的 `Location` 随意选择一个本地路径即可。 + +![alt text](QQ_1733020124554.png) + +#### Step3. 创建程序 + +编辑 `main.go`,并输入以下代码: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("hello world!") +} +``` + +![alt text](QQ_1733020331260.png) + +#### Step4. 执行运行 + +菜单栏 `Run` - `Run` - `go build hello.go`。 + +![alt text](QQ_1733020422698.png) + +![alt text](QQ_1733020474019.png) + +![alt text](QQ_1733020511081.png) + +恭喜你,第一个 `Go` 程序便成功了! + +### VSCode的使用 + +#### [Step1 - 下载安装](https://code.visualstudio.com/) + +#### [Step2 - 安装Go扩展](https://docs.microsoft.com/zh-cn/learn/modules/go-get-started/4-install-visual-studio-code?ns-enrollment-type=learningpath&ns-enrollment-id=learn.languages.go-first-steps) + +#### [Step3 - Hello World](https://docs.microsoft.com/zh-cn/learn/modules/go-get-started/5-hello-world) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..70ef7dc1268 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\207\206\345\244\207\345\274\200\345\217\221\347\216\257\345\242\203/\347\247\201\346\234\211\344\276\235\350\265\226\347\256\241\347\220\206.md" @@ -0,0 +1,42 @@ +--- +slug: '/docs/install-go/private-go-module' +title: '私有依赖管理' +sidebar_position: 3 +hide_title: true +keywords: [私有依赖管理,Go Modules,最小版本选择算法,GoFrame,第三方包,GOPRIVATE,模块依赖,私有库,Go Module 安装,Golang] +description: '在使用GoFrame框架进行项目开发时管理私有依赖的方法。它解释了如何解决私有库下载困难、版本不一致等常见问题,并提供了通过GOPRIVATE设置私有包有效域名的解决方案。这对于使用Go Modules进行依赖管理的开发者尤其重要。' +--- + +## 版本选择算法 + +当项目中存在同一个第三方包依赖,并且依赖版本不一致时, `Go Modules` 使用的“最小版本选择算法”( `The minimal version selection algorithm`: [https://github.com/golang/go/wiki/Modules#version-selection](https://github.com/golang/go/wiki/Modules#version-selection) )。 + +例如,如果您的模块依赖于具有 `require D v1.0.0` 的模块A,并且您的模块还依赖于具有 `require D v1.1.1` 的模块B,则最小版本选择将会选择D的 `v1.1.1` 版本用以构建(使用最高版本)。 + +> 请不要问我为什么这个算法名字叫“最小版本选择算法”,然而内容却是“最高版本选择算法”,若有纠结于此的同学欢迎向官方提issue: [https://github.com/golang/go/issues](https://github.com/golang/go/issues) + +## 私有依赖管理 + +如果你可以通过 `go.mod` 完美地管理当前的项目包依赖,那么可以忽略该章节。如果你在处理项目的包依赖管理中遇到了问题,那么建议你继续阅读该章节,可以找到解决问题的灵感。 + +前面章节我们非常详细、图文并茂地介绍了基本的开发环境安装/配置、 `Go Module` 安装使用,在实际的项目开发中,你会发现更多的问题,常见的: + +1. 虽然 `GoFrame` 足够强大,但多数时候依赖的包不仅仅是 `GoFrame`,还包括一些额外的第三方包,特别是 `golang.org` 的包,需要自带梯子翻墙下载,即使本地方便处理,但是在自动部署系统上可能会稍麻烦; +2. 一些自研开发的第三方包,特别是一些业务依赖包,是不允许公开下载的(私有库),并且版本库也可能不支持 `HTTPS` 协议,因此无法使用 `go get` 或者 `go.mod` 进行下载和管理; +3. 等等; + +如果你遇到了上面所提到的问题,我们建议的解决方案:通过 `GOPRIVATE` 的方式设置私有包有效域名。 + +例如以下命令行方式: + +```bash +export GOPROXY=https://goproxy.cn +export GOPRIVATE=git.xxx.com +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go +``` + +> 该特性需要 `Go v1.13` 以上版本支持。 + +在 `Goland` 中这么设置: + +![](/markdown/9bab70ea1f17890c926592e79ca4a929.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" new file mode 100644 index 00000000000..016ab40835d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\345\246\202\344\275\225\344\273\216v1\346\204\211\345\277\253\345\215\207\347\272\247\345\210\260v2.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/other/happy-upgrading-from-v1-to-v2' +title: '如何从v1愉快升级到v2' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,升级指导,兼容性,import路径,gredis,v1,v2,工程目录,CLI工具] +description: 'GoFrame框架发布了v2版本,推荐从v1升级至v2以获取新功能和稳定性提升。注意调整import路径并处理可能的代码修改。gredis组件集群化支持带来配置变化,CLI工具亦有重要更新和精简,建议参考指南实现顺利过渡。' +--- + +## 写在前面 + +`GoFrame` 框架发布了 `v2` 版本,这是一个里程碑版本,包含了很多新功能特性和大量改进,并且发布了一些开创性的特性。 + +如果是新用户,请直接使用 `v2` 起飞。如果是老用户,墙裂推荐从 `v1` 升级为 `v2`,更加稳定可靠。 + +`v2` 升级的几点重要说明: + +1. 为保证兼容性,按照 `Golang` 官方 `module` 管理的规范,我们将 `import` 路径变化了,因此需要全局替换 `import` 路径。 +2. 由于是大版本升级,因此存在部分方法删减、更新的情况,大家放心一切都有更好的方案提供啦。 +3. 一般来说升级后重新编译,根据编译错误提示修改代码即可完成升级。 +4. `gredis` 组件由于支持集群化,因此配置发生了变化,这块需要注意。 + +我们没有提供升级工具,因为我们觉得提供升级指导即可。 + +## v1与v2的兼容性 + +为了保证项目兼容性,能够同时依赖 `v1` 和 `v2` 版本,因此我们发布了 `v1` 最后一个版本 `v1.16.7`,大家有需要可以升级。并且解决了 `client_tracing.go:73:3: undefined: attribute.Any` 的常见问题。但同时依赖两个版本的 `GoFrame` 可能会降低项目维护性,因此建议大家尽快升级到 `v2` 版本。 + +## 将依赖替换为v2 + +全局替换源代码即可,规则如下: + +```go +"github.com/gogf/gf/ => "github.com/gogf/gf/v2/ +``` + +像这样: + +![](/markdown/6e0a32d42cc581bd2f4220d721714f41.png) + +## 下载最新v2版本 + +```bash +go get -u github.com/gogf/gf/v2@latest +``` + +## 工程目录的调整 + +如果您使用的是 `GoFrame` 官方推荐的工程目录结构,可以参考最新的工程目录结构手动调整即可: [工程目录设计🔥](../框架设计/工程开发设计/工程目录设计.md) + +需要注意的是,最新的 `cli` 工具不再支持旧版工程目录的项目创建。 + +## 编译运行修改 + +运行您的项目,如果遇到编译问题,根据错误提示进行手动修改,如此循环。 + +如果您不知道如何修改,请在本文档评论,我们的社区团队小伙伴将会及时给与升级帮助。 + +## CLI的重要变化 + +1. 去掉了 `swagger` 命令。 `v1` 版本的 `gf swagger` 命令时通过自动安装第三方的 `swag` 工具,解析源代码中的注释生成的 `swagger` 文档。这种文档的管理维护方式会有一些问题:仅支持 `Swagger2.0` 协议、使用体验很差、注释难以和代码同步维护,造成接口文档与代码不一致的问题。新版本有了规范路由,该命令即废弃掉了。如果需要继续使用该命令的功能,可以手动安装使用第三方 `swag` 工具: [https://github.com/swaggo/swag](https://github.com/swaggo/swag) +2. 去掉了 `update` 命令。 `v2` 版本开始, `CLI` 工具的安装下载统一走 `github`,以减少 `CLI` 工具的维护工作量。后续可能会重新增加该命令。 + +## 一些重要说明 + +1. `gf-cli` 的仓库已经搬迁到了 `gf` 主库维护,便于保证工具与框架版本同步。原有仓库不再维护。具体请查看说明: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) +2. 框架核心组件采用了接口化设计,为保证接口通用性,对外暴露的方法有个别删减。 +3. 框架核心组件大量使用了 `gvar` 泛型,以提高易用性、屏蔽底层的具体类型实现。为保证稳定性和易用性,框架在未来 `2-3` 年内不会考虑使用 `Golang` 官方泛型。官方泛型在框架部分组件的部分特性下有改造价值。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" new file mode 100644 index 00000000000..4f8bcd84f12 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\346\240\274\345\274\217\345\214\226\346\211\223\345\215\260\345\215\240\344\275\215\347\254\246.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/other/printing-format' +title: '格式化打印占位符' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,格式化打印,printf,占位符,数据类型,Go语法,布尔型,整型,浮点数] +description: '在使用GoFrame框架进行格式化打印时,如何通过printf系列函数的占位符根据数据类型输出不同格式,包括通用占位符、布尔型、整型、浮点数、字符串、指针等,帮助开发者更高效地进行Go编程和格式化输出。' +--- + +`*printf` 系列函数都支持 `format` 格式化参数,在这里我们按照占位符将被替换的变量类型划分,方便查询和记忆。 + +## 占位符列表 + +| 分类 | 占位符 | 说明 | +| --- | --- | --- | +| **通用占位符** | `%v` | 值的默认格式表示 | +| `%+v` | 类似 `%v`,但输出结构体时会添加字段名 | +| `%#v` | 值的Go语法表示 | +| `%T` | 打印值的类型 | +| `%%` | 百分号 | +| **布尔型** | `%t` | `true` 或 `false` | +| **整型** | `%b` | 表示为二进制 | +| `%c` | 该值对应的 `Unicode` 码值 | +| `%d` | 表示为十进制 | +| `%o` | 表示为八进制 | +| `%x` | 表示为十六进制,使用 `a-f` | +| `%X` | 表示为十六进制,使用 `A-F` | +| `%U` | 表示为 `Unicode` 格式: `U+1234`,等价于 `U+%04X` | +| `%q` | 该值对应的单引号括起来的Go语法字符字面值,必要时会采用安全的转义表示 | +| **浮点数与复数** | `%b` | 无小数部分、二进制指数的科学计数法,如: `-123456p-78` | +| `%e` | 科学计数法,如: `-1234.456e+78` | +| `%E` | 科学计数法,如: `-1234.456E+78` | +| `%f` | 有小数部分但无指数部分,如: `123.456` | +| `%F` | 等价于 `%f` | +| `%g` | 根据实际情况采用 `%e` 或 `%f` 格式(以获得更简洁、准确的输出) | +| `%G` | 根据实际情况采用 `%E` 或 `%F` 格式(以获得更简洁、准确的输出) | +| **字符串和\[\]byte** | `%s` | 直接输出字符串或者 `[]byte` | +| `%q` | 该值对应的双引号括起来的Go语法字符串字面值,必要时会采用安全的转义表示 | +| `%x` | 每个字节用两字符十六进制数表示(使用 `a-f`) | +| `%X` | 每个字节用两字符十六进制数表示(使用 `A-F`) | +| **指针** | `%p` | 表示为十六进制,并加上前导的 `0x` | +| **宽度标识符** | `%f` | 默认宽度,默认精度 | +| `%9f` | 宽度9,默认精度 | +| `%.2f` | 默认宽度,精度2 | +| `%9.2f` | 宽度9,精度2 | +| `%9.f` | 宽度9,精度0 | +| **占位修饰符** | `+` | 总是输出数值的正负号;对 `%q`( `%+q`)会生成全部是ASCII字符的输出(通过转义) | +| ` ` | 空格,对数值,正数前加空格而负数前加负号;对字符串采用 `%x` 或 `%X` 时( `% x` 或 `% X`)会给各打印的字节之间加空格 | +| `-` | 在输出右边填充空白而不是默认的左边(即从默认的右对齐切换为左对齐) | +| `#` | 八进制数前加 `0`( `%#o`),十六进制数前加 `0x`( `%#x`)或 `0X`( `%#X`),指针去掉前面的 `0x`( `%#p`)对 `%q`( `%#q`),对 `%U`( `%#U`)会输出空格和单引号括起来的Go字面值 | +| `0` | 使用 `0` 而不是空格填充,对于数值类型会把填充的 `0` 放在正负号后面 | +| | | | + +## 参考资料 + +- [https://pkg.go.dev/fmt](https://pkg.go.dev/fmt) +- [https://www.liwenzhou.com/posts/Go/go\_fmt/](https://www.liwenzhou.com/posts/Go/go_fmt/) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..f7f5bb1f002 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\205\266\344\273\226\350\265\204\346\226\231/\351\231\204\345\275\225\357\274\232\347\263\273\347\273\237\344\277\241\345\217\267\345\210\227\350\241\250.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/other/system-signals' +title: '附录:系统信号列表' +sidebar_position: 7 +hide_title: true +keywords: [系统信号,Linux信号,Windows信号,信号处理,SIGHUP,SIGINT,SIGTERM,GoFrame,信号编程,信号列表] +description: '列举了Linux和Windows系统中的常见信号及其含义,包括如何处理这些信号。特别是对于开发者,理解各个信号的作用和反应机制是至关重要的,尤其是在使用GoFrame框架进行编程时。本文提供了一个方便的参考,可帮助快速定位并理解信号的用途。' +--- + +## Linux + +| 信号 | 取值 | 默认动作 | 含义(发出信号的原因) | +| --- | --- | --- | --- | +| `SIGHUP` | 1 | Term | 终端的挂断或进程死亡 | +| `SIGINT` | 2 | Term | 来自键盘的中断信号 | +| `SIGQUIT` | 3 | Core | 来自键盘的离开信号 | +| `SIGILL` | 4 | Core | 非法指令 | +| `SIGABRT` | 6 | Core | 来自 abort 的异常信号 | +| `SIGFPE` | 8 | Core | 浮点例外 | +| `SIGKILL` | 9 | Term | 杀死 | +| `SIGSEGV` | 11 | Core | 段非法错误 (内存引用无效) | +| `SIGPIPE` | 13 | Term | 管道损坏:向一个没有读进程的管道写数据 | +| `SIGALRM` | 14 | Term | 来自 alarm 的计时器到时信号 | +| `SIGTERM` | 15 | Term | 终止 | +| `SIGUSR1` | 30,10,16 | Term | 用户自定义信号 1 | +| `SIGUSR2` | 31,12,17 | Term | 用户自定义信号 2 | +| `SIGCHLD` | 20,17,18 | Ign | 子进程停止或终止 | +| `SIGCONT` | 19,18,25 | Cont | 如果停止,继续执行 | +| `SIGSTOP` | 17,19,23 | Stop | 非来自终端的停止信号 | +| `SIGTSTP` | 18,20,24 | Stop | 来自终端的停止信号 | +| `SIGTTIN` | 21,21,26 | Stop | 后台进程读终端 | +| `SIGTTOU` | 22,22,27 | Stop | 后台进程写终端 | +| `SIGBUS` | 10,7,10 | Core | 总线错误(内存访问错误) | +| `SIGPOLL` | | Term | Pollable 事件发生 (Sys V),与 SIGIO 同义 | +| `SIGPROF` | 27,27,29 | Term | 统计分布图用计时器到时 | +| `SIGSYS` | 12,-,12 | Core | 非法系统调用 (SVr4) | +| `SIGTRAP` | 5 | Core | 跟踪 / 断点自陷 | +| `SIGURG` | 16,23,21 | Ign | socket 紧急信号 (4.2BSD) | +| `SIGVTALRM` | 26,26,28 | Term | 虚拟计时器到时 (4.2BSD) | +| `SIGXCPU` | 24,24,30 | Core | 超过 CPU 时限 (4.2BSD) | +| `SIGXFSZ` | 25,25,31 | Core | 超过文件长度限制 (4.2BSD) | +| `SIGIOT` | 6 | Core | IOT 自陷,与 SIGABRT 同义 | +| `SIGEMT` | 7,-,7 | | Term | +| `SIGSTKFLT` | -,16,- | Term | 协处理器堆栈错误 (不使用) | +| `SIGIO` | 23,29,22 | Term | 描述符上可以进行 I/O 操作 | +| `SIGCLD` | -,-,18 | Ign | 与 SIGCHLD 同义 | +| `SIGPWR` | 29,30,19 | Term | 电力故障 (System V) | +| `SIGINFO` | 29,-,- | | 与 SIGPWR 同义 | +| `SIGLOST` | -,-,- | Term | 文件锁丢失 | +| `SIGWINCH` | 28,28,20 | Ign | 窗口大小改变 (4.3BSD, Sun) | +| `SIGUNUSED` | -,31,- | Term | 未使用信号 (will be SIGSYS) | + +## Window + +| 信号 | 说明 | +| --- | --- | +| **`SIGABRT`** | 异常终止 | +| **`SIGFPE`** | 浮点错误 | +| **`SIGILL`** | 非法指令 | +| **`SIGINT`** | Ctrl+C 信号 | +| **`SIGSEGV`** | 非法存储区访问 | +| **`SIGTERM`** | 终止请求 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" "b/versioned_docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" new file mode 100644 index 00000000000..bf16a34ffc9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\270\270\350\247\201\351\227\256\351\242\230-FAQ.md" @@ -0,0 +1,195 @@ +--- +slug: '/docs/faq' +title: '常见问题(FAQ)' +sidebar_position: 10 +hide_title: true +description: '使用GoFrame框架和Golang进行开发时的常见问题解答,包括如何处理程序异常、新goroutine的创建方案、屏蔽json输出字段、解决兼容性问题、配置环境及glog错误修复,通过这些信息帮助开发者优化调试应用程序。' +keywords: [GoFrame,GoFrame框架,Golang,程序异常处理,goroutine,json输出,兼容性问题,环境配置,glog错误,多环境配置] +--- + +寄语: + +- 请使用页面右上角的搜索功能,全文快速检索常见问题。 +- 欢迎大家积极参与编辑,把自己遇到的坑怎么填的记录起来。 **众人拾柴火焰高** + +## 一、Golang基础 + +### 1、程序产生异常,但是程序直接崩溃未被框架自动捕获 + +使用 `GoFrame` 框架是严谨和安全的,如果程序产生了异常,会默认被框架捕获。如果未被自动捕获,那么可能是由于程序逻辑自行开了新的 `goroutine`,在新的 `goroutine` 中产生了异常。因此这里有两个方案可供大家选择: + +- 不建议在请求中再开 `goroutine` 来处理请求,这样或使得 `goroutine` 快速膨胀,当 `goroutine` 多了之后也会在 `Go` 引擎层面影响程序的整体调度。 +- 如果实在有必要新开 `goroutine` 的场景下,可以考虑使用 `grpool.AddWithRecover` 来创建新的 `goroutine`,见名知意,它会自动捕获异常。更详细的介绍请参考: [协程管理-grpool](组件列表/系统相关/协程管理-grpool.md) + +### 2、 `json` 输出时屏蔽掉一些字段 + +可以通过结构体嵌套的方式实现,通过使用 `*struct{}` 类型不占用空间以及 `omitempty` 字段为空不输出字段的特性 + +```go +type User struct { + Pwd string `json:"pwd"` + Age int `json:"age"` +} + +type UserOut struct { + User + Pwd *struct{} `json:"pwd,omitempty"`// 这里的字段json名需要和嵌套的字段json名一致,否则无效 +} + +func TestJson(t *testing.T) { + u := User{Pwd: "123", Age: 1} + bb := UserOut{User: u} + b, _ := json.MarshalIndent(bb, "", " ") + t.Log(string(b)) +} +``` + +## 二、兼容性相关 + +### 1、 `client_tracing.go:73:3: undefined: attribute.Any` + +以下错误: + +```bash +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing.go:73:3: undefined: attribute.Any +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing_tracer.go:150:3: undefined: attribute.Any +D:\Program Files\Go\bin\pkg\mod\github.com\gogf\gf@v1.16.6\net\ghttp\internal\client\client_tracing_tracer.go:151:3: undefined: attribute.Any +``` + +导致该错误的原因在于目前您正在使用的 `goframe` 依赖的 `otel` 包版本过低( `otel` 包是 `OpenTelemetry` 使用 `Golang` 实现的第三方包,比较常用,很多第三方基础组件都会依赖),而项目中其他的第三方依赖的 `otel` 包过高,按照 `Golang module` 的管理策略,项目将会使用最新的 `otel` 包,于是导致了版本不兼容。 + +根因还是在于 `otel` 的包在迭代中出现了不兼容升级导致,不过目前 `otel` 包已经较稳定,出现不兼容的可能性降低。 + +解决的办法是只有升级 `goframe` 的版本, `goframe` 最新版本已经更新使用了稳定的 `otel` 包。如果您使用的已经是 `v1` 的最新版本( `v1.16`),那么请升级为 `v2` 版本解决。 + +### 2、使用 `gf` 依赖 `v1.16.2` 时 `go mod tidy` 失败 + +`found (v0.36.0), but does not contain package go.opentelemetry.io/otel/metric/registry` + +![](/markdown/08e4b24634f2819f4e6439c9cf9e08a8.png) + +解决办法,升级 `gf` 依赖到 `v1.16.9` 再 `go mod tidy` + +## 三、数据库相关 + +请参考章节: [ORM常见问题](核心组件/数据库ORM/ORM常见问题.md) + +## 四、使用相关 + +### 1、不同环境如何,加载不同的配置文件? + +不同环境指的是:开发环境/测试环境/预发环境/生产环境等。 + +- 首先,在一些互联网项目中,特别是分布式或者微服务化的架构下,一般会使用配置管理中心,不同的环境会对应不同的配置管理中心,所以这样的场景不会存在这样的问题。 +- 其次,如果是传统的项目管理方式下,可能会将配置文件放到代码仓库中共同管理,这样的方式是不推荐的。如果您仍然想要这么做,您可以通过系统环境变量或者命令行启动参数,让程序自动选择配置文件或者指定配置目录,参考 [配置管理](核心组件/配置管理/配置管理.md) 章节。例如: `./app --gf.gcfg.file config-prod.toml ` 则通过命令行启动参数的方式将默认读取的配置文件修改为了 `config-prod.toml` 文件。 + +我们不建议您在程序中通过代码逻辑来区分和读取不同环境的配置文件。 + + +### 2、 `glog with "ERROR: logging before flag.Parse"` + +`Golang` 官方有个简单的日志库包名也叫做 `glog`,检查你文件顶部 `import` 的包名,将 `github.com/golang/glog` 修改为框架的日志组件即可,日志组件使用请参考: [日志组件](核心组件/日志组件/日志组件.md) + +### 3、 `gcron` 与 `http` 如何同时使用? + +```go +func main() { + //定时任务1 + gcron.AddSingleton("*/5 * * * * *", func() { + task.Test() + glog.Debug("gcron1") + }) + + //定时任务2 + gcron.AddSingleton("*/10 * * * * *", func() { + glog.Debug("gcron2") + }) + + //接收http请求 + g.Server().Run() +} +``` + +注意, `gcron` 一定要在 `g.Server().Run` 的前面。 + +### 4、 `GoFrame` 的 `struct tag`(标签) 有哪些? + +参数请求、数据校验、 `OpenAPIv3`、命令管理、数据库ORM。 + +| Tag(简写) | 全称 | 描述 | 相关文档 | +| --- | --- | --- | --- | +| `v` | `valid` | 数据校验标签。 | [Struct校验-基本使用](核心组件/数据校验/数据校验-参数类型/数据校验-Struct校验/Struct校验-基本使用.md) | +| `p` | `param` | 自定义请求参数匹配。 | [请求输入-对象处理](WEB服务开发/请求输入/请求输入-对象处理.md) | +| `d` | `default` | 请求参数默认值绑定。 | [请求输入-默认值绑定](WEB服务开发/请求输入/请求输入-默认值绑定.md) | +| `orm` | `orm` | ORM标签,用于指定表名、关联关系。 | [数据规范-gen dao](开发工具/代码生成-gen/数据规范-gen%20dao.md)
    [模型关联-静态关联-With特性](核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) | +| `dc` | `description` | 通用结构体属性描述,ORM和接口都用到。属于框架默认的属性描述标签。 | | + +其他: + +- 命令行结构化管理参数: [命令管理-结构化参数](核心组件/命令管理/命令管理-结构化参数.md) +- 框架常用标签标签集中管理到了 `gtag` 组件下: [https://github.com/gogf/gf/blob/master/util/gtag/gtag.go](https://github.com/gogf/gf/blob/master/util/gtag/gtag.go) +- 在接口文档章节,由于采用了标签形式生成 `OpenAPI` 文档,因此标签比较多,具体请参考章节: [接口文档](WEB服务开发/接口文档/接口文档.md) + +### 5、 `HTTP Server` 出现 `context cancel` 报错 + +从框架 `v2.5` 版本开始,框架的 `HTTP Server` 的 `Request` 对象将会直接继承与标准库的 `http.Request` 对象, +其中就包括其中的 `context` 上下文对象。当客户端例如浏览器、 `HTTP Client` 取消请求时, +服务端会接收到 `context cancel` 操作( `context.Done`),但是服务端并不会直接报出 `context cancel` 的错误。 +这种错误往往在业务逻辑调用了底层的数据库、消息组件等组件时,由这些组件识别到 `context cancel` 操作, +将会停止执行并往上抛出 `context cancel` 错误提醒上层已经终止执行。 + +这是符合标准库设计的行为,客户端终止请求后,服务端也没有继续执行下去的必要。 + +[服务端频繁出现contextcancel错误](../docs/WEB服务开发/常见问题.md) + +## 五、环境相关 + +### 1、 `Linux` 下执行 `go build main.go` 提示连接超时 `connection timed out` + +```bash +go: github.com/gogf/gf@v1.14.6-0.20201214132204-c685876e6f67: Get "https://proxy.golang.org/github.com/gogf/gf/@v/v1.14.6-0.20201214132204-c685876e6f67.mod": +dial tcp 172.217.160.113:443: +connect: connection timed out +``` + +解决办法: + +```bash +export GO111MODULE=on +export GOPROXY=https://goproxy.cn +``` + +具体请看: + +- [Go Module](其他资料/准备开发环境/Go%20Module.md) +- [https://goproxy.cn](https://goproxy.cn) + +### 2、 `Linux` 下安装 `gf` 提示命令不存在 `command not found` + +```bash +./gf install +安装后 +执行gf -v +提示gf: command not found +且/usr/bin目录下并没有gf文件 + +解决方法: +拷贝sh文件到 /usr/bin目录 +cp gf /usr/bin + +然后执行 +gf -v + +就会看到 +GoFrame CLI Tool v1.15.4, https://goframe.org +Install Path: /bin/gf +Build Detail: +Go Version: go1.16.2 +GF Version: v1.15.3 +Git Commit: 22011e76dc3e14006936164cc89e2d4c9190a36d +Build Time: 2021-03-30 15:43:22 +``` + +### 3、 `Win10` 提示 `gf` 命令不存在 + +解决办法:安装 `gf.exe` 参考: [开发工具](开发工具/开发工具.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" new file mode 100644 index 00000000000..cb0f8a771ac --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\272\244\345\217\211\347\274\226\350\257\221-build.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/cli/build' +title: '交叉编译-build' +sidebar_position: 4 +hide_title: true +keywords: [交叉编译,GoFrame,编译变量,编译配置,内置变量,构建信息,gf build,编译选项,内置编译,项目生态] +description: '使用GoFrame框架进行交叉编译。通过gf build命令,可以快速生成带有当前Go版本、GoFrame版本、Git Commit等信息的可执行文件。支持同时从命令行和配置文件指定参数,满足不同操作系统和平台的编译需求,为开发者提供便捷的构建解决方案。' +--- + +## 使用方式 + +具体参数,使用 `gf build -h` 查看帮助 + +仅限于交叉编译使用到 `GoFrame` 框架的项目,支持绝大部分常见系统的直接交叉编译。 + +## 内置编译变量 + +`build` 命令自动嵌入编译变量,这些变量用户可自定义,并且在运行时通过 `gbuild` 组件获取。使用 `gf build` 的项目将会默认嵌入以下变量(参考 `gf -v`): + +- 当前 `Go` 版本 +- 当前 `GoFrame` 版本 +- 当前 `Git Commit`(如果存在) +- 当前编译时间 + +## 编译配置文件 + +`build` 支持同时从命令行以及配置文件指定编译参数、选项。 `GoFrame` 框架的所有组件及所有生态项目都是使用的同一个配置管理组件,默认的配置文件以及配置使用请参考章节 [配置管理](../核心组件/配置管理/配置管理.md)。以下是一个简单的配置示例供参考: + +```yaml +gfcli: + build: + name: "gf" + arch: "all" + system: "all" + mod: "none" + packSrc: "resource,manifest" + version: "v1.0.0" + output: "./bin" + extra: "" +``` + +配置选项的释义同命令行同名选项。 + +| 名称 | 默认值 | 含义 | 示例 | +| --- | --- | --- | --- | +| `name` | 与程序入口 `go` 文件同名 | 生成的可执行文件名称。如果是 `windows` 平台,那么默认会加上 `.exe` 后缀 | `gf` | +| `arch` | 当前系统架构 | 编译架构,多个以 `,` 号分隔,如果是 `all` 表示编译所有支持架构 | `386,amd64,arm` | +| `system` | `当前系统平台` | 编译平台,多个以 `,` 号分隔,如果是 `all` 表示编译所有支持平台 | `linux,darwin,windows` | +| `path` | `./bin` | 编译可执行文件存储的 **目录地址** | `./bin` | +| `mod` | | 同 `go build -mod` 编译选项,不常用 | `none` | +| `cgo` | `false` | 是否开启 `CGO`,默认是关闭的。如果开启,那么交叉编译可能会有问题。 | | +| `packSrc` | | 需要打包的目录,多个以 `,` 号分隔,生成到 `internal/packed/build_pack_data.go` | `public,template,manifest` | +| `packDst` | `internal/packed/build_pack_data.go` | 打包后生成的 `Go` 文件路径,一般使用相对路径指定到本项目目录中 | | +| `version` | | 程序版本,如果指定版本信息,那么程序生成的路径中会多一层以版本名称的目录 | `v1.0.0` | +| `output` | | 输出的可执行文件路径,当该参数指定时, `name` 和 `path` 参数失效,常用于编译单个可执行文件。 | `./bin/gf.exe` | +| `extra` | | 额外自定义的编译参数,会直接传递给 `go build` 命令 | | +| `varMap` | | 自定义的内置变量键值对,构建的二进制中可以通过 `gbuild` 包获取编译信息。 | ```
    gfcli:
    build:
    name: "gf"
    arch: "all"
    system: "all"
    mod: "none"
    cgo: 0
    varMap:
    k1: v1
    k2: v2
    ``` | +| `exitWhenError` | `false` | 当编译发生错误时,立即停止后续执行,并退出编译流程(使用 `os.Exit(1)`) | | +| `dumpEnv` | `false` | 每次编译之前在终端打印当前编译环境的环境变量信息 | | +:::tip +编译时的内置变量可以在运行时通过 `gbuild` 包 [构建信息-gbuild](../组件列表/系统相关/构建信息-gbuild.md) 获取。 +::: +## 使用示例 + +```text +$ gf build +2020-12-31 00:35:25.562 start building... +2020-12-31 00:35:25.562 go build -o ./bin/darwin_amd64/gf main.go +2020-12-31 00:35:28.381 go build -o ./bin/freebsd_386/gf main.go +2020-12-31 00:35:30.650 go build -o ./bin/freebsd_amd64/gf main.go +2020-12-31 00:35:32.957 go build -o ./bin/freebsd_arm/gf main.go +2020-12-31 00:35:35.824 go build -o ./bin/linux_386/gf main.go +2020-12-31 00:35:38.082 go build -o ./bin/linux_amd64/gf main.go +2020-12-31 00:35:41.076 go build -o ./bin/linux_arm/gf main.go +2020-12-31 00:35:44.369 go build -o ./bin/linux_arm64/gf main.go +2020-12-31 00:35:47.352 go build -o ./bin/linux_ppc64/gf main.go +2020-12-31 00:35:50.293 go build -o ./bin/linux_ppc64le/gf main.go +2020-12-31 00:35:53.166 go build -o ./bin/linux_mips/gf main.go +2020-12-31 00:35:55.840 go build -o ./bin/linux_mipsle/gf main.go +2020-12-31 00:35:58.423 go build -o ./bin/linux_mips64/gf main.go +2020-12-31 00:36:01.062 go build -o ./bin/linux_mips64le/gf main.go +2020-12-31 00:36:03.502 go build -o ./bin/netbsd_386/gf main.go +2020-12-31 00:36:06.280 go build -o ./bin/netbsd_amd64/gf main.go +2020-12-31 00:36:09.332 go build -o ./bin/netbsd_arm/gf main.go +2020-12-31 00:36:11.811 go build -o ./bin/openbsd_386/gf main.go +2020-12-31 00:36:14.140 go build -o ./bin/openbsd_amd64/gf main.go +2020-12-31 00:36:17.859 go build -o ./bin/openbsd_arm/gf main.go +2020-12-31 00:36:20.327 go build -o ./bin/windows_386/gf.exe main.go +2020-12-31 00:36:22.994 go build -o ./bin/windows_amd64/gf.exe main.go +2020-12-31 00:36:25.795 done! +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" new file mode 100644 index 00000000000..b1eff1daf7b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-ctrl-watchers.xml" @@ -0,0 +1,50 @@ + + + + + + + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" new file mode 100644 index 00000000000..c48288b1d52 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/gen-service-watchers.xml" @@ -0,0 +1,50 @@ + + + + + + + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" new file mode 100644 index 00000000000..b600b17906f Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/image2023-6-15_16-29-12.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" new file mode 100644 index 00000000000..31c5b9687a6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\344\273\243\347\240\201\347\224\237\346\210\220-gen.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/cli/gen' +title: '代码生成-gen(🔥重点🔥)' +sidebar_position: 5 +hide_title: true +keywords: [代码生成,GoFrame,CLI工具,项目开发,企业级项目,代码规范,团队协作,开发效率,ORM模型,protobuf文件] +description: '从v2版本开始,CLI工具结合GoFrame框架最新版本,为开发者提供代码生成功能,以规范化项目代码编写和简化开发复杂度。特别在企业级和团队项目中,CLI工具能明显提升开发效率,使得开发者专注于业务逻辑。' +--- +:::info +从 `v2` 版本开始,最新的 `CLI` 工具版本功能会随着 `GoFrame` 框架的最新版本编译,引入如果本地的 `CLI` 工具自动化生成的代码与项目的 `GoFrame` 框架版本出现兼容性问题时,建议升级项目框架版本,或者自定义安装旧版本的 `CLI` 工具。旧版本CLI工具安装方式参考仓库首页介绍: [https://github.com/gogf/gf-cli](https://github.com/gogf/gf-cli) +::: +## 重要说明🔥 + +- `CLI` 工具提供的代码生成功能,目的是 **规范化项目代码编写**、 **简化项目开发复杂度**, **让开发者能够把精力聚焦于业务逻辑本身**。 +- `CLI` 工具本身会需要有一定前置的学习和理解成本(尽量理解为什么),但在熟练之后,大家的开发工作将会事半功倍。 +- `CLI` 工具的代码生成功能针对于企业级项目、多成员的团队性项目中收益会非常高。但针对于单人小型项目,开发者可根据个人意愿评估是否选择使用。 `GoFrame` 框架本身只是提供了基础组件,采用了组件化的灵活设计,不会对项目代码做严格的要求;但 `CLI` 工具会有一定的条框限制,目的是使得团队中每个成员的步调和风格一致,不会使得开发者的代码编写过于随意。 + +## 使用方式 + +```text +$ gf gen -h +USAGE + gf gen COMMAND [OPTION] + +COMMAND + ctrl parse api definitions to generate controller/sdk go files + dao automatically generate go files for dao/do/entity + enums parse go files in current project and generate enums go file + pb parse proto files and generate protobuf go files + pbentity generate entity message files in protobuf3 format + service parse struct and associated functions from packages to generate service go file + +DESCRIPTION + The "gen" command is designed for multiple generating purposes. + It's currently supporting generating go files for ORM models, protobuf and protobuf entity files. + Please use "gf gen dao -h" for specified type help. +``` + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" new file mode 100644 index 00000000000..9adc6f38a6d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\345\215\217\350\256\256\347\274\226\350\257\221-gen pb.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/cli/gen-pb' +title: '协议编译-gen pb' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,协议编译,protobuf,GoFrame gen pb,协议文件,控制器文件,命令行工具,生成Go文件,CLI工具] +description: '使用GoFrame框架中的命令行工具来编译proto文件,生成相应的protobuf Go文件和控制器文件。通过gf gen pb命令,用户可以设置不同的路径以存储生成的接口和控制器文件,满足项目工程的需求。同时,本文还列出了该命令的使用指南和注意事项,以便于开发者更好地使用这个功能。' +--- +:::tip +该功能特性从 `v2.4` 版本开始提供。 +::: +## 基本介绍 + +该命令用于编译 `proto` 文件,生成对应的 `protobuf go` 文件以及对应的控制器文件。 + +## 命令使用 + +```text +$ gf gen pb -h +USAGE + gf gen pb [OPTION] + +OPTION + -p, --path protobuf file folder path + -a, --api output folder path storing generated go files of api + -c, --ctrl output folder path storing generated go files of controller + -h, --help more information about this command + +EXAMPLE + gf gen pb + gf gen pb -p . -a . -p . +``` +:::tip +如果使用框架推荐的项目工程脚手架,并且系统安装了 `make` 工具,也可以使用 `make pb` 快捷指令。 +::: +参数说明: + +| 名称 | 必须 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `path` | 否 | `manifest/protobuf` | 指向 `proto` 协议定义文件 | +| `api` | 否 | `api` | 指向生成的接口文件存放目录 | +| `ctrl` | 否 | `internal/controller` | 指向生成的控制器文件存放目录 | + +## 注意事项 + +- 在生成控制器文件时,会自动识别是否已经存在对应的接口实现方法,如果已经存在则不再重复生成对应的接口方法,防止覆盖。 +- 如果在 `proto` 目录执行该命令,并且指定的 `path` 目录不存在时,那么将会自动编译本地 `proto` 文件,且编译后的文件生成到当前目录,并自动关闭控制器文件的生成功能。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" new file mode 100644 index 00000000000..2a5f5030a48 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\216\245\345\217\243\350\247\204\350\214\203-gen ctrl.md" @@ -0,0 +1,160 @@ +--- +slug: '/docs/cli/gen-ctrl' +title: '接口规范-gen ctrl' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,API接口,HTTP开发,GRPC,控制器生成,SDK自动化,代码规范,多人协作,代码生成,项目管理] +description: '使用GoFrame生成API接口的控制器和SDK代码,从而帮助开发者减少重复性的代码工作,规范API与控制器的代码结构,提高多人协作开发的效率。此外,还提供了生成HTTP SDK代码的功能,方便内部和外部服务的调用。使用命令行模式和自动生成模式两种方式进行代码生成,并提供了详细的命令参数说明和使用示例。' +--- +:::tip +该功能特性从 `v2.5` 版本开始提供。该命令目前仅支持 `HTTP` 接口开发, `GRPC` 部分请参考 `gen pb` 命令。未来会考虑 `HTTP` 及 `GRPC` 统一使用该命令生成控制器及 `SDK` 源代码。 +::: +## 基本介绍 + +### 解决痛点 + +在开发项目的时候,往往需要先根据业务需求和场景设计 `API` 接口,使用 `proto` 或者 `golang struct` 来设计 `API` 的输入和输出,随后再创建与 `API` 相对应的控制器实现,最后也有可能会提供 `SDK`(同为 `Golang` 语言条件下)供内/外部服务调用。在开发过程中会遇到以下痛点: + +- **重复性的代码工作较繁琐**。在 `API` 中创建输入输出定义文件后还需要在控制器目录下创建对应的文件、创建对应的控制器初始化代码、从 `API` 代码中反复拷贝各个输入输出结构名称,在这过程重复性的操作比较繁琐。 +- **API与控制器之间的关联没有可靠规范约束**。除了 `API` 有一定的命名约束外,控制器的创建和方法命名并没有约束,灵活度较高, `API` 的结构名称与控制器方法名称难以约束对应,当接口越来越多时会有一定维护成本。 +- **团队开发多人协作时代码文件冲突概率大**。多人开发协作都往一个文件执行变更时,出现文件冲突的概率就会变大,团队协作开发中处理这种文件冲突的精力开销毫无意义。 +- **缺少API的HTTP SDK自动生成工具**。当开发完 `API` 后,往往需要立即给内部或者外部调用,缺少便捷的 `SDK` 生成,需要手动来维护这部分 `SDK` 代码,那么对于调用端来说成本非常高。 + +### 命令特性 + +- 规范了 `API` 定义与控制器文件命名、控制器实现方法命名。 +- 规范了 `API` 定义与控制器代码之间的关联关系,便于快速定位 `API` 实现。 +- 根据 `API` 定义自动生成控制器接口、控制器初始化文件及代码、接口初始化代码。 +- 根据 `API` 定义自动生成易于使用的 `HTTP SDK` 代码。该功能可配置,默认关闭。 +- 支持 `File Watch` 自动化生成模式:当某个 `API` 结构定义文件发生变化时,自动增量化更新对应的控制器、 `SDK` 代码。 + +## 前置约定 + +### 重要的规范🔥 + +该命令的目的之一是规范化 `api` 代码的编写,那么我们应该有一些重要的规范需要了解(否则生成不了代码哦): + +- `api` 层的接口定义文件路径需要满足 `/api/模块/版本/定义文件.go`,例如: `/api/user/v1/user.go`、 `/api/user/v1/user_delete.go`、etc. + - 这里的 **模块** 指的是 `API` 的模块划分,我们可以将 `API` 按照不同的 **业务属性** 进行拆分方便聚合维护。你也可以将模块认为是具体的业务资源。 + - 这里的 **版本** 通常使用 `v1`/ `v2`..这样的形式来定义,用以 `API` 兼容性的版本控制。当相同的 `API` 出现兼容性更新时,需要通过不同版本号来区分。默认使用 `v1` 来管理第一个版本。 + - 这里的 **定义文件** 指的是 `API` 的输入输出定义文件,通常每个 `API` 需要单独定义一个 `go` 文件来独立维护。当然也支持将多个 `API` 放到一个 `go` 文件中统一维护。 +- `api` 定义的结构体名称需要满足 `操作+Req` 及 `操作+Res` 的命名方式。例如: `GetOneReq/GetOneRes`、 `GetListReq/GetListRes`、 `DeleteReq/DeleteRes`、etc. + - 这里的操作是当前 `API` 模块的操作名称,通常对应 `CRUD` 是: `Create`、 `Read`、 `Update`、 `Delete`。 + +以下是项目工程模板中的 `Hello` 接口示例: + +![](/markdown/71be1e0ac8d8eaa7794a476086c110c2.png) + +### 建议性的命名 + +我们对一些常用的接口定义做了一些建议性的命名,供大家参考: + +| 操作名称 | 建议命名 | 备注 | +| --- | --- | --- | +| **查询列表** | `GetListReq/Res` | 通常是从数据库中分页查询数据记录 | +| **查询详情** | `GetOneReq/Res` | 通常接口需要传递主键条件,从数据库中查询记录详情 | +| **创建资源** | `CreateReq/Res` | 通常是往数据表中插入一条或多条数据记录 | +| **修改资源** | `UpdateReq/Res` | 通常是按照一定条件修改数据表中的一条或多条数据记录 | +| **删除资源** | `DeleteReq/Res` | 通常是按照一定条件删除数据表中的一条或多条数据记录 | + +## 命令使用 + +该命令通过分析给定的 `api` 接口定义目录下的代码,自动生成对应的控制器/ `SDK Go` 代码文件。 + +### 手动模式 + +如果是手动执行命令行,直接在项目根目录下执行 `gf gen ctrl` 即可,她将完整扫描 `api` 接口定义目录,并生成对应代码。 + +```text +$ gf gen ctrl -h +USAGE + gf gen ctrl [OPTION] + +OPTION + -s, --srcFolder source folder path to be parsed. default: api + -d, --dstFolder destination folder path storing automatically generated go files. default: internal/controller + -w, --watchFile used in file watcher, it re-generates go files only if given file is under srcFolder + -k, --sdkPath also generate SDK go files for api definitions to specified directory + -v, --sdkStdVersion use standard version prefix for generated sdk request path + -n, --sdkNoV1 do not add version suffix for interface module name if version is v1 + -c, --clear auto delete generated and unimplemented controller go files if api definitions are missing + -m, --merge generate all controller files into one go file by name of api definition source go file + -h, --help more information about this command + +EXAMPLE + gf gen ctrl + +``` +:::tip +如果使用框架推荐的项目工程脚手架,并且系统安装了 `make` 工具,也可以使用 `make ctrl` 快捷指令。 +::: +参数说明: + +| 名称 | 必须 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `srcFolder` | 否 | `api` | 指向 `api` 接口定义文件目录地址 | +| `dstFolder` | 否 | `internal/controller` | 指向生成的控制器文件存放目录 | +| `watchFile` | 否 | | 用在IDE的文件监控中,用于根据当文件发生变化时自动执行生成操作 | +| `sdkPath` | 否 | | 如果需要生成 `HTTP SDK`,该参数用于指定生成的SDK代码目录存放路径 | +| `sdkStdVersion` | 否 | `false` | 生成的 `HTTP SDK` 是否使用标准的版本管理。标准的版本管理将自动根据 `API` 版本增加请求的路由前缀。例如 `v1` 版本的API将会自动增加 `/api/v1` 的请求路由前缀。 | +| `sdkNoV1` | 否 | `false` | 生成的 `HTTP SDK` 中,当接口为 `v1` 版本时,接口模块名称是否不带 `V1` 后缀。 | +| `clear` | 否 | `false` | 是否删除 `controller` 中与 `api` 层定义不存在的控制器接口文件。 | +| `merge` | 否 | `false` | **用以控制生成的 `ctrl` 控制器代码文件按照 `api` 层的文件生成,而不是默认按照 `api` 接口拆分为不同的接口实现文件。** | + +### 自动模式(推荐) + +如果您是使用的 `GolandIDE`,那么可以使用我们提供的配置文件:[watchers.xml](gen-ctrl-watchers.xml)  自动监听代码文件修改时自动生成接口文件。使用方式,如下图: + +![](/markdown/7d15b228b1ee57f8f34254a0413f4fc0.png) + +## 使用示例 + +### 自动生成的接口定义文件 + +![](/markdown/636aedc34da9bad1f84545dcfbeb38e6.png) + +### 自动生成的控制器代码文件 + +![](/markdown/cff8e2509fc89f6f4c4c0c82bb753334.png) + +![](/markdown/e2219959e53c38a80d37254cd3e9e9de.png) + +### 自动生成的 `HTTP SDK` 代码文件 + +![](/markdown/f2f5c6793e4aef5ea3c2004ce67edf7b.png) + +![](/markdown/dd2dac2338ebf838ba317f64b32f5a5f.png) + +## 常见问题 + +### 为什么每一个 `api` 接口生成一个 `controller` 文件而不是合并到一个 `controller` 文件中 + +![](image2023-6-15_16-29-12.png) + +当然,针对小型项目或者个人简单项目、一个 `api` 模块只有几个接口的项目而言,管理的方式并不会成为什么问题,可以根据个人喜好维护代码文件即可。我们这里以较复杂的业务项目,或者企业级项目,在一个 `api` 模块的接口比较多的场景来展开描述一下。 + +- 首先,开发 `api` 接口时,查找 `api` 接口实现更加清晰,而不是在一个动则上千行的代码文件中查找。 +- 其次,在多人协作的项目中,如果多人同时修改同一个 `controller` 文件在版本管理中容易出现文件冲突。一个 `api` 接口对应一个 `controller` 实现文件的维护方式能最大减少代码协作时的文件冲突概率,大部分开发者也不希望花费自己宝贵的时间一次又一次地解决文件冲突上。 +- 最后, `controller` 层的代码有它自身的职责: + - 校验输入参数:客户端提交的参数都是不可信任的,大部分场景下都需要做数据校验。 + - 实现接口逻辑:直接在 `controller` 中实现接口逻辑,或者调用一个或多个 `service` 接口、第三方服务接口来实现接口逻辑。注意事项,不能在 `service` 层的接口中去实现 `api` 接口逻辑,因为 `api` 接口是与具体的业务场景绑定的,无法复用。💀 **大部分常见的错误是 `controller` 直接把请求透传给 `service` 接口来实现 `api` 接口逻辑,造成了 `controller` 看起来可有可无、 `service` 层的实现越来越重且无法复用。** 💀 + - 生成返回数据:组织内部产生的结果数据,生成接口定义的返回数据接口。 +- 这些职责也就意味着 `controller` 的代码也是比较复杂,分开维护能减少开发者心智负担、易于清晰维护 `api` 接口实现逻辑。 + +**一些建议**: + +如果一个 `api` 模块下的接口文件太多,建议将复杂的 `api` 模块进一步划分为子模块。这样可以对复杂的 `api` 模块进行解耦,也能通过多目录的方式来维护 `api` 接口定义和 `controller` 接口实现文件。目录结构会更清晰,更利于多人协作和版本管理。 + +_看完以上关于对此的设计后,如果您仍然想使用单源码文件来管理所有接口,可以参考 `merge` 参数。_ + +### 根据 `api` 模块生成对应的 `controller` 模块中为何存在一个空的 `go` 文件 + +**例如**: + +![](/markdown/a5b84cce8be1a8b3d563102e7a4c81dd.png) + +**说明**: + +每个 `api` 模块会生成一个空的该模块 `controller` 下的 `go` 文件,该文件只会生成一次,用户可以在里面填充必要的预定义代码内容,例如,该模块 `controller` 内部使用的变量、常量、数据结构定义,或者包初始化 `init` 方法定义等等。 _我们提倡好的代码管理习惯,模块下的 **预定义内容** 尽量统一维护到该模块下以模块名称命名的 `go` 文件中( `模块.go`),而不是分散到各个 `go` 文件中,以便于更好地维护代码。_ + +如果该 `controller` 目前没有需要自定义填充的代码内容,那么保留该文件为空即可,为未来预留扩展能力。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" new file mode 100644 index 00000000000..b4ae0eaa316 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\241\250PB-gen pbentity.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/cli/gen-pbentity' +title: '数据表PB-gen pbentity' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,Golang实体对象,proto数据结构,GRPC服务,数据库配置,GF命令行工具,数据表生成,entity文件生成,pbentity,命名格式] +description: '使用GoFrame框架的命令行工具gf来根据数据库表生成proto数据结构文件pbentity。包括命令使用方法、选项配置及其说明,以及与gen dao模块中生成的entity文件的区别。适用于HTTP和GRPC服务的数据实体结构生成,支持多种数据库的生成规则配置。' +--- +:::tip +该功能特性从 `v2.4` 版本开始提供。 +::: +## 基本介绍 + +该命令用于读取配置的数据库,根据数据表生成对应的 `proto` 数据结构文件。 + +## 命令使用 + +```text +$ gf gen pbentity -h +USAGE + gf gen pbentity [OPTION] + +OPTION + -p, --path directory path for generated files storing + -k, --package package path for all entity proto files + -l, --link database configuration, the same as the ORM configuration of GoFrame + -t, --tables generate models only for given tables, multiple table names separated with ',' + -f, --prefix add specified prefix for all entity names and entity proto files + -r, --removePrefix remove specified prefix of the table, multiple prefix separated with ',' + -rf, --removeFieldPrefix remove specified prefix of the field, multiple prefix separated with ',' + -n, --nameCase case for message attribute names, default is "Camel": + | Case | Example | + |---------------- |--------------------| + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | default + | Snake | any_kind_of_string | + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -j, --jsonCase case for message json tag, cases are the same as "nameCase", default "CamelLower". + set it to "none" to ignore json tag generating. + -o, --option extra protobuf options + -h, --help more information about this command + +EXAMPLE + gf gen pbentity + gf gen pbentity -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + gf gen pbentity -p ./protocol/demos/entity -t user,user_detail,user_login + gf gen pbentity -r user_ -k github.com/gogf/gf/example/protobuf + gf gen pbentity -r user_ + +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gf.gen.pbentity", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + path: "protocol/demos/entity" + tables: "order,products" + package: "demos" + - pbentity: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "protocol/demos/entity" + prefix: "primary_" + tables: "user, userDetail" + package: "demos" + option: | + option go_package = "protobuf/demos"; + option java_package = "protobuf/demos"; + option php_namespace = "protobuf/demos"; + typeMapping: + json: + type: google.protobuf.Value + import: google/protobuf/struct.proto + jsonb: + type: google.protobuf.Value + import: google/protobuf/struct.proto +``` +:::tip +如果使用框架推荐的项目工程脚手架,并且系统安装了 `make` 工具,也可以使用 `make pbentity` 快捷指令。 +::: +参数说明: + +| 名称 | 默认值 | 含义 | 示例 | +| --- | --- | --- | --- | +| `gfcli.gen.pbentity` | | 代码生成配置项,可以有多个配置项构成数组,支持多个数据库生成。不同的数据库可以设置不同的生成规则,例如可以生成到不同的位置或者文件。 | - | +| `path` | `manifest/protobuf/pbentity` | 生成 `proto` 文件的存储 **目录** 地址。 | `protobuf/pbentity` | +| `package` | 自动识别 `go.mod` | 生成的 `proto` 文件中的 `go_package` 路径,并自动识别 `package` 名称 | - | +| `link` | | 分为两部分,第一部分表示你连接的数据库类型 `mysql`, `postgresql` 等, 第二部分就是连接数据库的 `dsn` 信息。具体请参考 [ORM使用配置](../../核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) 章节。 | - | +| `prefix` | | 生成数据库对象及文件的前缀,以便区分不同数据库或者不同数据库中的相同表名,防止数据表同名覆盖。 | `order_`
    `user_` | +| `removePrefix` | | 删除数据表的指定前缀名称。多个前缀以 `,` 号分隔。 | `gf_` | +| `removeFieldPrefix` | | 删除字段名称的指定前缀名称。多个前缀以 `,` 号分隔。 | `f_` | +| `tables` | | 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 | `user, user_detail` | +| `nameCase` | `CamelLower` | 生成的 `message` 属性字段名称格式。参数可选为: `Camel`、 `CamelLower`、 `Snake`、 `SnakeScreaming`、 `SnakeFirstUpper`、 `Kebab`、 `KebabScreaming`。具体介绍请参考命名行帮助示例。 | `Snake` | +| `option` | | 额外的 `proto option` 配置列表 | | +| `typeMapping` | | 用于自定义数据表字段类型到生成的Go文件中对应属性类型映射 | | +| `fieldMapping` | | 用于自定义数据表具体字段到生成的Go文件中对应属性类型映射 | | + +## 与 `gen dao` 中的 `entity` 差别 + +### 相同之处 + +- 两者生成的内容都是 `entity` 内容,即从数据集合(数据库表)中生成对应的 `Golang` 实体对象供程序方便使用。并且都是单向生成,即只能从数据集合生成实体对象代码,以保证实体对象数据结构的同步。 +- `gen dao` 生成的 `entity` 数据实体对象是对于 `Golang` 语言来说是通用的,但目前主要为 `HTTP` 协议服务。在 `HTTP` 服务中, `gen dao` 中生成的 `entity` 虽然是在 `internal` 目录下,但最终也会作为 `HTTP API` 返回的一部分服务客户端。 + +### 不同之处 + +- 在 `GRPC` 服务中, `gen dao` 生成的 `entity` 数据结构无法提供给 `GRPC` 接口使用,因为 `GRPC` 的数据结构需要使用 `proto` 文件来定义。因此,在 `GRPC` 服务中就需要使用到 `gen pbentity` 中生成的 `pbentity proto` 文件。同时,在 `GRPC` 微服务开发中, `gen dao` 生成的 `entity` 已经没有具体作用。 +- 取名 `pbentity` 而不是 `entity` 的名称,是为了防止和 `gen dao` 中的 `entity` 含义冲突。 diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" new file mode 100644 index 00000000000..9eaa15bbbce --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\225\260\346\215\256\350\247\204\350\214\203-gen dao.md" @@ -0,0 +1,227 @@ +--- +slug: '/docs/cli/gen-dao' +title: '数据规范-gen dao' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,gen dao,数据访问对象,数据转化模型,Go代码生成,配置文件管理,命令行工具,数据库连接,ORM配置,项目工程规范] +description: 'GoFrame框架中gen dao命令的使用方法与参数配置。gen dao命令是生成数据访问对象、数据转化模型及实体数据模型的关键工具,支持通过命令行参数和配置文件进行详细配置,适用于多种数据库类型。通过对命令选项的灵活使用,可以满足不同项目的代码生成需求,确保工程设计规范的落地执行。' +--- + +`gen dao` 命令是 `CLI` 中最频繁使用、也是框架设计的工程规范能否准确落地的关键命令。该命令用于生成 `dao` 数据访问对象、 `do` 数据转化模型及 `entity` 实例数据模型 `Go` 代码文件。由于该命令的参数、选项较多,我们推荐使用配置文件来管理生成规则。 +:::tip +关于框架项目工程规范介绍请查看 [代码分层设计](../../框架设计/工程开发设计/代码分层设计.md) 章节。 +::: +## 使用方式 + +大部分场景下,进入项目根目录执行 `gf gen dao` 即可。以下为命令行帮助信息。 + +```text +$ gf gen dao -h +USAGE + gf gen dao [OPTION] + +OPTION + -p, --path directory path for generated files + -l, --link database configuration, the same as the ORM configuration of GoFrame + -t, --tables generate models only for given tables, multiple table names separated with ',' + -x, --tablesEx generate models excluding given tables, multiple table names separated with ',' + -g, --group specifying the configuration group name of database for generated ORM instance, + it's not necessary and the default value is "default" + -f, --prefix add prefix for all table of specified link/database tables + -r, --removePrefix remove specified prefix of the table, multiple prefix separated with ',' + -rf, --removeFieldPrefix remove specified prefix of the field, multiple prefix separated with ',' + -j, --jsonCase generated json tag case for model struct, cases are as follows: + | Case | Example | + |---------------- |--------------------| + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | default + | Snake | any_kind_of_string | + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -i, --importPrefix custom import prefix for generated go files + -d, --daoPath directory path for storing generated dao files under path + -o, --doPath directory path for storing generated do files under path + -e, --entityPath directory path for storing generated entity files under path + -t1, --tplDaoIndexPath template file path for dao index file + -t2, --tplDaoInternalPath template file path for dao internal file + -t3, --tplDaoDoPath template file path for dao do file + -t4, --tplDaoEntityPath template file path for dao entity file + -s, --stdTime use time.Time from stdlib instead of gtime.Time for generated time/date fields of tables + -w, --withTime add created time for auto produced go files + -n, --gJsonSupport use gJsonSupport to use *gjson.Json instead of string for generated json fields of + tables + -v, --overwriteDao overwrite all dao files both inside/outside internal folder + -c, --descriptionTag add comment to description tag for each field + -k, --noJsonTag no json tag will be added for each field + -m, --noModelComment no model comment will be added for each field + -a, --clear delete all generated go files that do not exist in database + -y, --typeMapping custom local type mapping for generated struct attributes relevant to fields of table + -fm, --fieldMapping custom local type mapping for generated struct attributes relevant to specific fields of + table + -/--genItems + -h, --help more information about this command + +EXAMPLE + gf gen dao + gf gen dao -l "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + gf gen dao -p ./model -g user-center -t user,user_detail,user_login + gf gen dao -r user_ + +CONFIGURATION SUPPORT + Options are also supported by configuration file. + It's suggested using configuration file instead of command line arguments making producing. + The configuration node name is "gfcli.gen.dao", which also supports multiple databases, for example(config.yaml): + gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "order,products" + jsonCase: "CamelLower" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "./my-app" + prefix: "primary_" + tables: "user, userDetail" + typeMapping: + decimal: + type: decimal.Decimal + import: github.com/shopspring/decimal + numeric: + type: string + fieldMapping: + table_name.field_name: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` +:::tip +如果使用框架推荐的项目工程脚手架,并且系统安装了 `make` 工具,也可以使用 `make dao` 快捷指令。 +::: +## 配置示例 + +文件配置示例: + +```yaml title="hack/config.yaml" +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "order,products" + jsonCase: "CamelLower" + + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/primary" + path: "./my-app" + prefix: "primary_" + tables: "user, userDetail" + + # sqlite需要自行编译带sqlite驱动的gf,下载库代码后修改路径文件(gf\cmd\gf\internal\cmd\cmd_gen_dao.go)的import包,取消注释即可。sqlite驱动依赖了gcc + - link: "sqlite:./file.db" +``` + +## 参数说明 + +| 名称 | 默认值 | 含义 | 示例 | +| --- | --- | --- | --- | +| `gfcli.gen.dao` | | `dao` 代码生成配置项,可以有多个配置项构成数组,支持多个数据库生成。不同的数据库可以设置不同的生成规则,例如可以生成到不同的位置或者文件。 | - | +| `link`| | **必须参数**。分为两部分,第一部分表示你连接的数据库类型 `mysql`, `postgresql` 等, 第二部分就是连接数据库的 `dsn` 信息。具体请参考 [ORM使用配置](../../核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) 章节。 | - | +| `path` | `internal` | 生成 `dao` 和 `model` 文件的存储 **目录** 地址。 | `./app` | +| `group` | `default` | 在数据库配置中的数据库分组名称。只能配置一个名称。数据库在配置文件中的分组名称往往确定之后便不再修改。 | `default`
    `order`
    `user` | +| `prefix` | | 生成数据库对象及文件的前缀,以便区分不同数据库或者不同数据库中的相同表名,防止数据表同名覆盖。 | `order_`
    `user_` | +| `removePrefix` | | 删除数据表的指定前缀名称。多个前缀以 `,` 号分隔。 | `gf_` | +| `removeFieldPrefix` | | 删除字段名称的指定前缀名称。多个前缀以 `,` 号分隔。 | `f_` | +| `tables` | | 指定当前数据库中需要执行代码生成的数据表。如果为空,表示数据库的所有表都会生成。 | `user, user_detail` | +| `tablesEx` | | `Tables Excluding`,指定当前数据库中需要排除代码生成的数据表。 | `product, order` | +| `jsonCase` | `CamelLower` | 指定 `model` 中生成的数据实体对象中 `json` 标签名称规则,参数不区分大小写。参数可选为: `Camel`、 `CamelLower`、 `Snake`、 `SnakeScreaming`、 `SnakeFirstUpper`、 `Kebab`、 `KebabScreaming`。具体介绍请参考命名行帮助示例。 | `Snake` | +| `stdTime` | `false` | 当数据表字段类型为时间类型时,代码生成的属性类型使用标准库的 `time.Time` 而不是框架的 `*gtime.Time` 类型。 | `true` | +| `withTime` | `false` | 为每个自动生成的代码文件增加生成时间注释 | | +| `gJsonSupport` | `false` | 当数据表字段类型为 `JSON` 类型时,代码生成的属性类型使用 `*gjson.Json` 类型。 | `true` | +| `overwriteDao` | `false` | 每次生成 `dao` 代码时是否重新生成覆盖 `dao/internal` 目录外层的文件。注意 `dao/internal` 目录外层的文件可能由开发者自定义扩展了功能,覆盖可能会产生风险。 | `true` | +| `importPrefix` | 通过 `go.mod` 自动检测 | 用于指定生成 `Go` 文件的 `import` 路径前缀。特别是针对于不是在项目根目录下使用 `gen dao` 命令,或者想要将代码文件生成到自定义的其他目录,这个时候配置该参数十分必要。 | `github.com/gogf/gf` | +| `descriptionTag` | `false` | 用于指定是否为数据模型结构体属性增加 `desription` 的标签,内容为对应的数据表字段注释。 | `true` | +| `noJsonTag` | `false` | 生成的数据模型中,字段不带有json标签 | | +| `noModelComment` | `false` | 用于指定是否关闭数据模型结构体属性的注释自动生成,内容为数据表对应字段的注释。 | `true` | +| `clear` | `false` | 自动删除数据库中不存在对应数据表的本地 `dao/do/entity` 代码文件。请谨慎使用该参数! | | +| `daoPath` | `dao` | 代码生成的 `DAO` 文件存放目录 | | +| `doPath` | `model/do` | 代码生成 `DO` 文件存放目录 | | +| `entityPath` | `model/entity` | 代码生成的 `Entity` 文件存放目录 | | +| `tplDaoIndexPath` | | 自定义 `DAO Index` 代码生成模板文件路径,使用该参数请参考源码 | | +| `tplDaoInternalPath` | | 自定义 `DAO Internal` 代码生成模板文件路径,使用该参数请参考源码 | | +| `tplDaoDoPath` | | 自定义 `DO` 代码生成模板文件路径,使用该参数请参考源码 | | +| `tplDaoEntityPath` | | 自定义 `Entity` 代码生成模板文件路径,使用该参数请参考源码 | | +| `typeMapping` | | **从版本v2.5开始支持**。用于自定义数据表字段类型到生成的Go文件中对应属性类型映射。 | | +| `fieldMapping` | | **从版本v2.8开始支持**。用于自定义数据表具体字段到生成的Go文件中对应属性类型映射。| | + +### 参数:`typeMapping` + +参数`typeMapping`支持配置数据库字段类型对应的`Go`数据类型,默认值为: +```yaml +decimal: + type: float64 +money: + type: float64 +numeric: + type: float64 +smallmoney: + type: float64 +``` +该配置支持通过`import`配置项引入第三方包,例如: +```yaml +decimal: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` + +### 参数:`fieldMapping` + +参数`fieldMapping`提供细粒度的字段类型映射配置,支持配置指定数据库字段生成的`Go`数据类型。除了配置名称不一样外,配置内容与`typeMapping`一致。配置示例: +```yaml +paid_orders.amount: + type: decimal.Decimal + import: github.com/shopspring/decimal +``` +示例中,`paid_orders`为表名称,`amount`为字段名称,`type`表示生成的`Go`代码中对应的数据类型名称,`import`表示生成的代码中需要引入第三方包。 + +## 使用示例 + +仓库地址: [https://github.com/gogf/focus-single](https://github.com/gogf/focus-single) + +![](/markdown/a02af38b70bb31224361565570e40789.png) + +1、以下 `3` 个目录的文件由 `dao` 命令生成: + +| 路径 | 说明 | 详细介绍 | +| --- | --- | --- | +| `/internal/dao` | 数据操作对象 | 通过对象方式访问底层数据源,底层基于 `ORM` 组件实现。往往需要结合 `entity` 和 `do` 共同使用。该目录下的文件开发者可扩展修改。 | +| `/internal/model/do` | 数据转换模型 | 数据转换模型用于业务模型到数据模型的转换,由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。关于 `do` 文件的介绍请参考:
    - [数据模型与业务模型](../../框架设计/工程开发设计/数据模型与业务模型.md)
    - [DAO-工程痛点及改进](../../框架设计/工程开发设计/DAO封装设计/DAO-工程痛点及改进.md)
    - [利用指针属性和do对象实现灵活的修改接口](../../核心组件/数据库ORM/ORM最佳实践/利用指针属性和do对象实现灵活的修改接口.md) | +| `/internal/model/entity` | 数据模型 | 数据模型由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。 | + +2、 `model` 中的模型分为两类: **数据模型** 和 **业务模型**。 + +**数据模型:** 通过 `CLI` 工具自动生成 `model/entity` 目录文件,数据库的数据表都会生成到该目录下,这个目录下的文件对应的模型为数据模型。数据模型即与数据表一一对应的数据结构,开发者往往不需要去修改并且也不应该去修改,数据模型只有在数据表结构变更时通过 `CLI` 工具自动更新。数据模型由 `CLI` 工具生成及统一维护。 + +**业务模型:** 业务模型即是与业务相关的数据结构,按需定义,例如 `service` 的输入输出数据结构定义、内部的一些数据结构定义等。业务模型由开发者根据业务需要自行定义维护,定义到 `model` 目录下。 + +3、 `dao` 中的文件按照数据表名称进行命名,一个数据表一个文件及其一个对应的 `DAO` 对象。操作数据表即是通过 `DAO` 对象以及相关操作方法实现。 `dao` 操作采用规范化设计,必须传递 `ctx` 参数,并在生成的代码中必须通过 `Ctx` 或者 `Transaction` 方法创建对象来链式操作数据表。 + +![](/markdown/f0da330685c6cfd82ba1c0254dfdbe39.png) + +## 注意事项 + +### 需要手动编译的数据库类型 + +`gen dao` 命令涉及到数据访问相关代码生成时,默认支持常用的若干类型数据库。如果需要 `Oracle` 数据库类型支持,需要开发者自己修改源码文件后自行本地手动编译生成 `CLI` 工具随后安装,因为这两个数据库的驱动需要 `CGO` 支持,无法预编译生成给大家直接使用。 + +![](/markdown/7f849959c13d224393b93d6b371e8ae0.png) + +### 关于 `bool` 类型对应的数据表字段 + +由于大部分数据库类型都没有 `bool` 类型的数据表字段类型, +我们推荐使用`bit(1)`来表示字段的`bool`类型,而非`tinyint(1)`或者`int(1)`。因为`tinyint(1)/int(1)`字段类型表示的范围是`-127~127`,通常可能会被用作状态字段类型。而`bit(1)`的类型范围为`0/1`,可以很好的表示`bool`类型的两个值`false/true`。 + +例如,表字段: + +![](/markdown/50992d00a792555d2946d294975e9ec4.png) + +生成的属性: + +![](/markdown/4bb766d64e607a33c1a6fbf20c742924.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" new file mode 100644 index 00000000000..de893f23e70 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\236\232\344\270\276\347\273\264\346\212\244-gen enums.md" @@ -0,0 +1,99 @@ +--- +slug: '/docs/cli/gen-enums' +title: '枚举维护-gen enums' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,枚举维护,OpenAPIv3,API文档,枚举值,源码分析,命令行工具,开发效率,数据校验] +description: '使用GoFrame命令行工具来维护和生成枚举值信息,特别是针对OpenAPIv3文档中的枚举值参数。通过解析源代码自动生成和加载枚举值,降低手工维护的成本,提高开发效率,并增强后端与前端的协作。' +--- +:::tip +该功能特性为 **实验性特性**,从 `v2.4` 版本开始提供。 +::: +## 基本介绍 + +该命令用于分析指定代码目录源码,按照规范生成枚举值信息以及 `Go` 代码文件, **主要用以完善 `OpenAPIv3` 文档中的枚举值维护**。 + +## 解决痛点 + +### 痛点描述 + +- `API` 文档中枚举值类型参数不展示枚举值可选项的问题。 +- `API` 文档中的枚举值维护困难的问题,代码与文档脱离维护的问题。降低了与调用端,特别是前后端的协作效率 + +> 例如,以下接口定义中,任务包含多种状态,这些状态都是枚举值,如果后端来维护成本比较高,并且容易遗漏状态的维护,造成状态枚举值不完整。 + +![](/markdown/3e2d58612c094dcf26ed2f17371ae482.png) + +### 痛点解决 + +通过工具解析源码,将枚举值解析生成到启动包 `Go` 文件中,在服务运行时自动加载枚举值,降低手工维护成本,避免枚举值遗漏维护问题。 + +> 例如,在以下接口定义中,通过工具来维护枚举值,提高了开发效率。 + +![](/markdown/4f5b0d82a3fa65b8c83fcd3f93a8c02a.png) + +## 命令使用 + +```text +$ gf gen enums -h +USAGE + gf gen enums [OPTION] + +OPTION + -s, --src source folder path to be parsed + -p, --path output go file path storing enums content + -x, --prefixes only exports packages that starts with specified prefixes + -h, --help more information about this command + +EXAMPLE + gf gen enums + gf gen enums -p internal/boot/boot_enums.go + gf gen enums -p internal/boot/boot_enums.go -s . + gf gen enums -x github.com/gogf +``` + +参数说明: + +| 名称 | 必须 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `src` | 否 | `.` | 指定分析的源码目录路径,默认为当前项目根目录 | +| `path` | 否 | `internal/boot/boot_enums.go` | 指定生成的枚举值注册Go代码文件路径 | +| `prefixes` | 否 | - | 只会生成包名称前缀的带有指定关键字的枚举值,支持多个前缀配置 | + +:::info +该命令底层使用了`AST`解析实现,将会递归分析所有依赖包的`enums`定义。如果业务项目依赖较复杂,生成`enums`可能会比较多,但绝大部分时候我们只关心自身项目或者个别依赖包的`enums`定义,那么这个时候可以使用`prefixes`参数来控制只生成特定包名前缀的`enums`。 +::: + +工具配置项`yaml`格式示例: +```yaml title="hack/config.yaml" +gfcli: + gen: + enums: + src: "api" + path: "internal/boot/boot_enums.go" + prefixes: + - github.com/gogf + - myexample/project +``` + + +## 生成文件的使用 + +执行 `gf gen enums` 命令生成枚举分析文件 `internal/boot/boot_enums.go`,生成文件之后,需要在项目入口文件匿名引入: + +```go +import ( + _ "项目模块名/internal/boot" +) +``` + +## 扩展阅读 + +### 如何规范定义枚举值 + +请参考章节: [Golang枚举值管理](../../框架设计/Golang枚举值管理.md) + +### 如何对枚举值进行校验 + +如果规范化定义了枚举值,并且通过命令生成了枚举值维护文件,那么在参数校验中可以使用 `enums` 规则对枚举值字段进行校验,具体规则介绍请参考章节: [数据校验-校验规则](../../核心组件/数据校验/数据校验-校验规则.md) + diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" new file mode 100644 index 00000000000..9c9aa39670c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\344\273\243\347\240\201\347\224\237\346\210\220-gen/\346\250\241\345\235\227\350\247\204\350\214\203-gen service.md" @@ -0,0 +1,190 @@ +--- +slug: '/docs/cli/gen-service' +title: '模块规范-gen service' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,Golang,业务逻辑,模块管理,接口定义,代码生成,循环依赖,服务注册,编译器,微服务] +description: '在GoFrame框架中对业务逻辑进行封装管理,通过生成模块接口定义和注册代码来简化业务逻辑与接口分离的实现过程。通过逻辑与接口的结构化编码提高模块透明性,并避免循环依赖。提供手动与自动两种实现模式,适用于不同开发环境。' +--- +:::warning +该功能特性为 **实验性特性**。建议开发者以 `logic` 下的模块划分为主,梳理模块间关联关系,避免循环依赖,充分利用 `Golang` 编译器的循环依赖检测特性编写更高质量的项目代码。 +::: +:::tip +该功能特性从 `v2.1` 版本开始提供。 +::: + + +## 基本介绍 + +### 设计背景 + +在业务项目实践中,业务逻辑封装往往是最复杂的部分,同时,业务模块之间的依赖十分复杂、边界模糊,较难采用 `Golang` 包管理的形式。如何有效管理项目中的业务逻辑封装部分,对于每个采用 `Golang` 开发的项目都是必定会遇到的难题。 + +在标准的软件设计流程中,模块与模块之间的依赖会先明确接口定义,在软件开发的实施过程中再通过代码来具体实现。但在大部分高节奏的互联网工程下,并没有严谨的软件设计流程,甚至开发人员的质量水平也参差不齐,大部分开发人员首先关心的是如何去实现需求场景对应的功能逻辑,尽可能地提高开发效率。 + +### 设计目标 + +1. 提供一种代码管理方式,可以通过具体模块实现直接生成模块接口定义、模块注册代码。 +2. 简化业务逻辑实现与接口分离的实现,降低模块方法与接口定义的重复操作,提高模块与模块之间的透明度与调用便捷性。 + +### 设计实现 + +1. 增加 `logic` 分类目录,将所有业务逻辑代码迁移到 `logic` 分类目录下,采用包管理形式来管理业务模块。 +2. 业务模块之间的依赖通过接口化解耦,将原有的 `service` 分类调整为接口目录。这样每个业务模块将会各自维护、更加灵活。 +3. 可以按照一定的编码规范,从 `logic` 业务逻辑代码生成 `service` 接口定义代码。同时,也允许人工维护这部分 `service` 接口。 + +## 注意事项 +:::warning +再次提醒,通过 `logic` 实现去生成 `service` 接口 **并不是一个代码管理的标准化做法**,只是提供另一个 **可供选择的**、便捷的代码管理方式。这种管理方式有优点也有缺点,优点是针对微服务场景的业务模块的接口自动生成比较方便;缺点是无法识别语法继承关系、无法生成父级嵌套类型的方法、抛弃了 `Golang` 编译时检测循环依赖的特性。 +::: +**框架的工程管理当然也支持标准的接口代码管理方式**,即支持先定义 `service` 接口,再编码 `logic` 具体实现。需要注意的是,这个 `service` 的源代码中不能出现顶部工具的注释信息(工具依靠这个注释来判断该文件是否可覆盖😈),很多同学复制粘贴的时候把文件顶部注释保留了,就会引起手动维护接口文件失效。具体见截图注释: + +![](/markdown/f4b70fc856dfcb17c4680839e32bb78b.png) + +## 命令使用 + +该命令通过分析给定的 `logic` 业务逻辑模块目录下的代码,自动生成 `service` 目录接口代码。 +:::info +需要注意: + +1. 由于该命令是根据业务模块生成 `service` 接口,因此只会解析二级目录下的 `go` 代码文件,并不会无限递归分析代码文件。以 `logic` 目录为例,该命令只会解析 `logic/xxx/*.go` 文件。因此,需要 `logic` 层代码结构满足一定规范。 +2. 不同业务模块中定义的结构体名称在生成的 `service` 接口名称时可能会重复覆盖,因此需要在设计业务模块时保证名称不能冲突。 +::: +该命令的示例项目请参考: [https://github.com/gogf/gf-demo-user](https://github.com/gogf/gf-demo-user) + +### 手动模式 + +如果是手动执行命令行,直接在项目根目录下执行 `gf gen service` 即可。 + +```text +$ gf gen service -h +USAGE + gf gen service [OPTION] + +OPTION + -s, --srcFolder source folder path to be parsed. default: internal/logic + -d, --dstFolder destination folder path storing automatically generated go files. default: internal/service + -f, --dstFileNameCase destination file name storing automatically generated go files, cases are as follows: + | Case | Example | + |---------------- |--------------------| + | Lower | anykindofstring | + | Camel | AnyKindOfString | + | CamelLower | anyKindOfString | + | Snake | any_kind_of_string | default + | SnakeScreaming | ANY_KIND_OF_STRING | + | SnakeFirstUpper | rgb_code_md5 | + | Kebab | any-kind-of-string | + | KebabScreaming | ANY-KIND-OF-STRING | + -w, --watchFile used in file watcher, it re-generates all service go files only if given file is under + srcFolder + -a, --stPattern regular expression matching struct name for generating service. default: ^s([A-Z]\\w+)$ + -p, --packages produce go files only for given source packages + -i, --importPrefix custom import prefix to calculate import path for generated importing go file of logic + -l, --clear delete all generated go files that are not used any further + -h, --help more information about this command + +EXAMPLE + gf gen service + gf gen service -f Snake +``` +:::tip +如果使用框架推荐的项目工程脚手架,并且系统安装了 `make` 工具,也可以使用 `make service` 快捷指令。 +::: +参数说明: + +| 名称 | 必须 | 默认值 | 含义 | +| --- | --- | --- | --- | +| `srcFolder` | 是 | `internal/logic` | 指向logic代码目录地址 | +| `dstFolder` | 是 | `internal/service` | 指向生成的接口文件存放目录 | +| `dstFileNameCase` | 否 | `Snake` | 生成的文件名名称格式 | +| `stPattern` | 否 | `s([A-A]\w+)` | 使用正则指定业务模块结构体定义格式,便于解析业务接口定义名称。在默认的正则下,所有小写 `s` 开头,大写字母随后的结构体都将被当做业务模块接口名称。例如: + +| logic结构体名称 | service接口名称 | +| --- | --- | +| `sUser` | `User` | +| `sMetaData` | `MetaData` | | +| `watchFile` | | | 用在代码文件监听中,代表当前改变的代码文件路径 | +| `packages` | | | 仅生成指定包名的接口文件,给定字符串数组,通过命令行传参则给定 `JSON` 字符串,命令行组件自动转换数据类型 | +| `importPrefix` | | | 指定生成业务引用文件中的引用包名前缀 | +| `overwrite` | | `true` | 生成代码文件时是否覆盖已有文件 | +| `clear` | | `false` | 自动删除 `logic` 中不存在的接口文件(仅删除自动维护的文件) | + +### 自动模式 + +#### `Goland/Idea` + +如果您是使用的 `GolandIDE`,那么可以使用我们提供的配置文件: [watchers.xml](gen-service-watchers.xml)  自动监听代码文件修改时自动生成接口文件。使用方式,如下图: + +![](/markdown/447830160c7c3f14c1ce09b34906047f.png) + +#### `Visual Studio Code` + +如果您是使用的 `Visual Studio Code`,那么可以安装插件 [RunOnSave](https://marketplace.visualstudio.com/items?itemName=emeraldwalk.RunOnSave) 随后配置插件: + +```json +"emeraldwalk.runonsave": { + "commands": [ + { + "match": ".*logic.*go", + "isAsync": true, + "cmd": "gf gen service" + } + ] +} +``` + +## 具体使用手摸手 + +### Step1:引入我们提供的配置 + +我们建议您在使用 `Goland IDE` 时,使用我们提供的配置文件: [watchers.xml](gen-service-watchers.xml) + +### Step2:编写您的业务逻辑代码 + +![](/markdown/84a59977f8a236410b20573a9377ed9b.png) + +### Step3:生成接口及服务注册文件 + +如果您已经按照 `Step1` 做好了配置,那么这一步可以忽略。因为在您编写代码的时候, `service` 便同时生成了接口定义文件。 + +否则,每一次在您开发/更新完成 `logic` 业务模块后,您需要手动执行一下 `gf gen service` 命令。 + +![](/markdown/8f5ee2dc2c553ee9dd169930ff50003d.png) + +### Step4:注意服务的实现注入部分(仅一次) + +只有在生成完成接口文件后,您才能在每个业务模块中加上接口的具体实现注入。该方法每个业务模块加一次即可。 + +![](/markdown/aebae0b3b3055119b3818da0515e0c28.png) + +### Step5:在启动文件中引用接口实现注册(仅一次) + +可以发现,该命令除了生成接口文件之外,还生成了一个接口实现注册文件。该文件用于在程序启动时,将接口的具体实现在启动时执行注册。 + +![](/markdown/ceddac49d9a4585f334902157d542e0d.png) + +该文件的引入需要在 `main` 包的最顶部引入,需要注意 `import` 的顺序,放到最顶部,后面加一个空行。如果同时存在 `packed` 包的引入,那么放到 `packed` 包后面。像这样: + +![](/markdown/864c4ad138cca78ac03d7e2d3fbf7a02.png) + +### Step6:Start&Enjoy + +启动 `main.go` 即可。 + +## 常见问题FAQ + +### 当 `logic` 中的结构体存在嵌套时,无法自动生成嵌套类型的方法 + +这种场景建议手动维护 `service` 接口定义,不使用工具的自动生成。手动维护的接口定义文件不会被工具覆盖,手动和自动可以同时使用。 + +### 快速定位接口的具体实现 + +**项目业务模块采用接口化解耦后体验非常棒!但是我在开发和调试过程中,想要快速找到指定接口的具体实现有点困难,能给点指导思路吗?** + +\> 这里我推荐使用 `Goland IDE`,有个很棒的接口实现定位功能,具体如图。找到接口定义后,点击左边的小图标可快速定位具体的实现。如果Goland不显示小图标,可以尝试升级使用最新版本的 `Goland` 哈。 + +![](/markdown/bbcc72eb46954b60c49be42a8ecebe35.png) + +或者在左侧没有小图标的时候,可以右键选择 `Go To → Implementation(s)` + +![](/markdown/4168ae8d0afee067e885e603eda37ccf.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" new file mode 100644 index 00000000000..18503d915f2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\205\274\345\256\271\344\277\256\345\244\215-fix.md" @@ -0,0 +1,32 @@ +--- +slug: '/docs/cli/fix' +title: '兼容修复-fix' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,兼容修复,CLI使用,代码更新,版本升级,向下兼容,gf fix,命令行工具,自动修正] +description: 'GoFrame框架提供的兼容修复命令gf fix,帮助在框架升级过程中解决向下兼容性问题。该命令自v2.3版本起提供,通过自动更新本地代码,处理较小兼容性问题,并可重复执行以确保无副作用。' +--- +:::tip +该命令从框架 `v2.3` 版本开始提供。 +::: +## 使用场景 + +当官方框架版本在升级过程中,会尽最大可能保证向下兼容性。但确实遇到十分困难的场景,难以保证完全向下兼容性的时候,并且是较小的兼容性问题,考虑到新增大版本号的成本较高,那么官方会通过该命令提供自动修正兼容问题。并且官方会保证该指令可重复执行,无副作用。 + +## 使用方式 + +```text +$ gf fix -h +USAGE + gf fix + +OPTION + -/--path directory path, it uses current working directory in default + -h, --help more information about this command +``` + +用以低版本(当前 `go.mod` 中的 `GoFrame` 版本)升级高版本(当前 `CLI` 使用的 `GoFrame` 版本)自动更新本地代码不兼容变更。 + +## 注意事项 + +命令执行前请 `git` 提交本地修改内容或执行目录备份。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" new file mode 100644 index 00000000000..b9e30f3807f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\256\211\350\243\205-install.md" @@ -0,0 +1,89 @@ +--- +slug: '/docs/cli/install' +title: '工具安装-install' +sidebar_position: 0 +hide_title: true +keywords: [工具安装,GoFrame,GoFrame框架,gf工具,命令行工具,预编译二进制,系统环境变量,MacOS,Windows安装,go install] +description: '在不同操作系统上安装GoFrame工具,包括MacOS和Windows系统的安装方法。提供了预编译二进制文件的下载地址以及通过go install命令进行安装的方法,确保gf工具能够正确安装并在系统环境变量中使用。' +--- + +该命令仅针对于预编译二进制下载安装。如果通过 `go install` 命名安装的工具的话,不需要手动再使用 `install` 命令安装 `gf` 工具。 + +## 下载安装 + +### 最新版下载 + +#### `Mac`&`Linux`  快捷下载命令 + +```bash +wget -O gf https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH) && chmod +x gf && ./gf install -y && rm ./gf +``` + +#### Windows 需手动下载 + +确定自己当前项目的 `goframe` 依赖版本,查看自己的系统信息: + +```bash +go env GOOS +go env GOARCH +``` + +下载地址: [releases](https://github.com/gogf/gf/releases) + +### 通过 `go install` 安装 + +注意:需要将 `$GOPATH/bin` 加入到系统环境变量中,通过 `go env GOPATH` 查看。 + +#### 最新版本 + +```bash +go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +#### 指定版本(版本需要 >= v2.5.5) + +```bash +go install github.com/gogf/gf/cmd/gf/v2@v2.5.5 +``` + +### 其它版本下载 + +#### v2 版本 + +预编译二进制下载: [releases](https://github.com/gogf/gf/releases) + +源码:[gf/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +#### v1 版本 + +预编译二进制下载: [releases](https://github.com/gogf/gf-cli/releases) + +源码: [gogf/gf-cli](https://github.com/gogf/gf-cli) + +## 使用方式 + +项目地址: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +使用方式: `./gf install` + +该命令往往是在 `gf` 命令行工具下载到本地后执行(注意执行权限),用于将 `gf` 命令安装到系统环境变量默认支持的目录路径中,以便于在系统任何的地方直接可以使用 `gf` 工具。 + +:::note +部分系统需要管理员权限支持。 + +如果是 `MacOS` 下使用 `zsh` 的小伙伴可能会遇到别名冲突问题,可以通过 `alias gf=gf` 来解决,运行一次之后 `gf` 工具会自动修改 `profile` 中的别名设置,用户重新登录(或者重开终端)就好了。 +::: + +## 使用示例 + +```bash +$ ./gf_darwin_amd64 install +I found some installable paths for you(from $PATH): + Id | Writable | Installed | Path + 0 | true | true | /usr/local/bin + 1 | true | false | /Users/john/Workspace/Go/GOPATH/bin + 2 | true | false | /Users/john/.gvm/bin + 4 | true | false | /Users/john/.ft +please choose one installation destination [default 0]: +gf binary is successfully installed to: /usr/local/bin +``` diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" new file mode 100644 index 00000000000..f04667e6ede --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\267\245\345\205\267\345\270\256\345\212\251-help.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/cli/help' +title: '工具帮助-help' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,CLI工具,gf命令,帮助文档,命令行工具,软件帮助,工具使用,sidebar位置] +description: '使用GoFrame框架的CLI工具的帮助命令,通过输入gf -h或gf [COMMAND] -h来获取帮助信息。如果您在使用过程中遇到问题,可随时使用help命令查询相关帮助。在这里,您还可以了解到具体的sidebar位置的相关信息。' +--- + +使用方式: + +- `gf -h` +- `gf [COMMAND] -h` + +任何不懂的,就用 `help` 看看吧。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" new file mode 100644 index 00000000000..f56eedc6d69 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\345\274\200\345\217\221\345\267\245\345\205\267.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/cli' +title: '开发工具' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf命令行工具,开发工具,CLI工具,框架设计规范,命令行参数,配置文件,调试模式,代码生成] +description: 'GoFrame框架提供的gf命令行开发工具,涵盖了工具职责、注意事项、配置支持、工具调试和命令总览等方面的详细信息。gf工具旨在简化工程开发和提高效率,并支持通过命令行及配置文件方式进行参数配置,以增强工具的易用性。' +--- + +`GoFrame` 框架提供了功能强大的 `gf` 命令行开发辅助工具,是框架发展的一个重要组成部分,工具地址: + +- [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +工具安装请参考仓库页面。工具安装成功后,可以通过 `gf` 或者 `gf -h` 查看所有支持的命令。复杂的命令可以通过 `gf COMMAND -h` 查看更详细的使用帮助信息,例如: `gf gen -h`。 + +## 工具职责 + +1. 简化工程开发,提高开发效率 +2. 支持框架工程设计规范准确落地 + +## 注意事项 + +1. 部分命令需要您先安装好 `Golang` 基础的开发环境,环境安装具体请参考 [环境安装](../其他资料/准备开发环境/环境安装.md) 章节。 +2. 最新的 `CLI` 工具版本会随着最新的框架版本走。 + +## 配置支持 + +**工具的所有命令均同时支持命令行及配置文件配置参数,以提高易用性。当给定命令行参数时优先读取命令行参数,如果命令行参数不存在时,自动读取配置文件中对应的参数名称。** + +配置文件路径优先查找当前目录下的 `hack` 目录( `hack/config.yaml`),其次按照框架默认的配置路径检索配置文件。框架默认的配置文件检索路径请参考章节: [配置管理-文件配置](../核心组件/配置管理/配置管理-文件配置.md) + +配置文件的格式示例: + +```yaml +# GoFrame CLI tool configuration. +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + tables: "user" + removePrefix: "gf_" + descriptionTag: true + noModelComment: true + + docker: + build: "-a amd64 -s linux -p temp" + tagPrefixes: + - ccr.ccs.tencentyun.com/xxx + - hkccr.ccs.tencentyun.com/xxx + - sgccr.ccs.tencentyun.com/xxx +``` + +注意以上配置示例仅供参考,具体配置项请参考具体命令帮助。 + +## 工具调试 + +当在工具的使用中遇到问题时,可以尝试打开工具的调试模式获得更详细的工具执行日志信息,打开工具调试模式可以通过 `debug` 命令行选项开启,例如: + +```bash +gf build main.go --debug +``` + +由于 `gf` 工具也是使用 `GoFrame` 框架开发,因此调试信息的开启也是同框架方式一致,更详细的介绍请参考框架介绍文档: [调试模式](../核心组件/调试模式.md) + +## 命令总览 + +当前帮助文档以 `gf cli v2.0.0` 版本为例进行简单的介绍,详细的介绍信息请查看命令行帮助信息。本章内容信息可能会有滞后,最新的具体详细介绍请查看工具帮助信息。 + +```text +$ gf +USAGE + gf COMMAND [OPTION] + +COMMAND + env show current Golang environment variables + run running go codes with hot-compiled-like feature + gen automatically generate go files for dao/dto/entity/pb/pbentity... + init create and initialize an empty GoFrame project + pack packing any file/directory to a resource file, or a go file + build cross-building go project for lots of platforms + docker build docker image for current GoFrame project + install install gf binary to system (might need root/admin permission) + version show version information of current binary + +OPTION + -y, --yes all yes for all command without prompt ask + -v, --version show version information of current binary + -d, --debug show internal detailed debugging information + -h, --help more information about this command + +ADDITIONAL + Use "gf COMMAND -h" for details about a command. +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" new file mode 100644 index 00000000000..5cd47dd3b06 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\346\241\206\346\236\266\345\215\207\347\272\247-up.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/cli/up' +title: '框架升级-up' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,CLI工具,版本更新,自动修复,不兼容变更,主框架,社区组件,命令行选项,代码升级] +description: '使用GoFrame框架的gf up命令进行版本升级。gf up命令可以同时更新主框架和CLI工具版本,并自动修复升级过程中的不兼容代码变更。本文提供详细的使用方式、命令选项及使用示例,帮助用户安全高效地完成升级操作。' +--- +:::tip +该命令从框架 `v2.3` 版本开始提供。 +::: +## 使用方式 + +```text +$ gf up -h +USAGE + gf up [OPTION] + +OPTION + -a, --all upgrade both version and cli, auto fix codes + -c, --cli also upgrade CLI tool + -f, --fix auto fix codes(it only make sense if cli is to be upgraded) + -h, --help more information about this command + +EXAMPLE + gf up + gf up -a + gf up -c + gf up -cf +``` + +用以实现版本更新,同时更新主框架版本、社区组件版本到最新版本。 + +选项说明: + +| 名称 | 含义 | +| --- | --- | +| `all` | 同时更新 `cli` 工具版本,并且自动修复本地代码在升级中的不兼容变更 | +| `fix` | 升级时自动修复本地代码在升级中的不兼容变更 | +| `cli` | 升级时同时更新 `cli` 工具版本 | + +## 使用示例 + +```text +$ gf up -a +start upgrading version... +upgrading "github.com/gogf/gf/contrib/drivers/mysql/v2" from "v2.2.4" to "latest" +go: upgraded github.com/BurntSushi/toml v1.1.0 => v1.2.1 +go: upgraded github.com/cespare/xxhash/v2 v2.1.2 => v2.2.0 +go: upgraded github.com/clbanning/mxj/v2 v2.5.6 => v2.5.7 +go: upgraded github.com/fsnotify/fsnotify v1.5.4 => v1.6.0 +go: upgraded github.com/go-sql-driver/mysql v1.6.0 => v1.7.0 +go: upgraded github.com/gogf/gf/contrib/drivers/mysql/v2 v2.2.4 => v2.2.6 +go: upgraded github.com/gogf/gf/v2 v2.2.4 => v2.2.6 +go: upgraded github.com/magiconair/properties v1.8.6 => v1.8.7 +go: upgraded github.com/mattn/go-colorable v0.1.12 => v0.1.13 +go: upgraded github.com/mattn/go-isatty v0.0.14 => v0.0.17 +go: upgraded github.com/mattn/go-runewidth v0.0.13 => v0.0.14 +go: upgraded github.com/rivo/uniseg v0.2.0 => v0.4.3 +go: upgraded go.opentelemetry.io/otel v1.7.0 => v1.11.2 +go: upgraded go.opentelemetry.io/otel/sdk v1.7.0 => v1.11.2 +go: upgraded golang.org/x/net v0.0.0-20220621193019-9d032be2e588 => v0.5.0 +go: upgraded golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c => v0.4.0 +go: upgraded golang.org/x/text v0.3.8-0.20220509174342-b4bca84b0361 => v0.6.0 +go: upgraded golang.org/x/tools v0.1.11-0.20220504162446-54c7ba520b92 => v0.1.12 + +upgrading "github.com/gogf/gf/v2" from "v2.2.4" to "latest" + +auto fixing path "/Users/john/Workspace/Go/GOPATH/src/github.com/Khaos/eros"... +done! +``` + +## 注意事项 + +命令执行前请 `git` 提交本地修改内容或执行目录备份。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" new file mode 100644 index 00000000000..ca633521660 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\347\211\210\346\234\254\346\237\245\347\234\213-version.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/cli/version' +title: '版本查看-version' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf命令行,版本查看,gf version,CLI工具,Golang版本,技术文档,代码编译,环境配置] +description: '使用GoFrame命令行工具查看版本信息,包括gf -v和gf version的使用方式。内容涵盖不同版本的使用示例,展示GoFrame在项目中的具体版本信息,并说明CLI编译细节和注意事项,帮助用户准确理解GoFrame版本与Golang及相关技术的关系。' +--- + +## 使用方式 + +- `gf -v` +- `gf version` + +用以查看当前 `gf` 命令行工具编译时的版本信息。 + +## 使用示例 + +### `>= v2.5.7` + +```text +$ gf version +v2.7.2 +Welcome to GoFrame! +Env Detail: + Go Version: go1.22.2 linux/amd64 + GF Version(go.mod): + github.com/gogf/gf/contrib/drivers/mysql/v2@v2.7.2 + github.com/gogf/gf/v2@v2.7.2 +CLI Detail: + Installed At: /data/home/v_hlaghuang/go/bin/gf + Built Go Version: go1.20.8 + Built GF Version: v2.7.2 + Git Commit: 2024-06-26 10:08:04 b11caba5b03ed54fbb1415151f7d62b6d913179d + Built Time: 2024-06-26 10:09:50 +Others Detail: + Docs: https://goframe.org + Now : 2024-07-17T15:48:57+08:00 +``` + +### `< v2.5.6` + +```text +$ gf version +GoFrame CLI Tool v2.0.0, https://goframe.org +GoFrame Version: v2.0.0-beta.0.20211214160159-19c9f0a48845 in current go.mod +CLI Installed At: /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-cli/main +CLI Built Detail: + Go Version: go1.16.3 + GF Version: v2.0.0-beta + Git Commit: 2021-12-15 22:43:12 7884058b5df346d34ebab035224e415afb556c19 + Build Time: 2021-12-15 23:00:43 +``` + +## 注意事项 + +在打印的版本信息中会自动检测当前项目使用的 `GoFrame` 版本(自动解析 `go.mod`),并以 `GoFrame Version` 的信息打印出来。 + +在 `CLI Built Detail` 信息中展示的是当前二进制编译时使用的各种 `Golang` 版本以及 `GoFrame` 版本信息,编译时的 `Git` 提交版本、当前二进制文件的编译时间。 +:::warning +大家请勿将 `GoFrame Version` 和 `CLI Built Detail` 中的 `GF Version` 混淆。 +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" new file mode 100644 index 00000000000..38864720472 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\207\252\345\212\250\347\274\226\350\257\221-run.md" @@ -0,0 +1,95 @@ +--- +slug: '/docs/cli/run' +title: '自动编译-run' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,自动编译,热编译特性,go文件自动监控,命令行参数,编译运行,二进制文件,文件路径监听,GoFrame框架,gf run 命令] +description: '在使用GoFrame框架构建项目时,如何通过gf run命令实现自动编译功能。虽然Go语言本身不支持热编译特性,但gf run命令可实现当项目中的go文件发生变更时,自动编译并运行新版本程序的功能,旨在提高开发效率。' +--- + +## 注意事项 + +由于 `Go` 是不支持热编译特性的,每一次代码变更后都要重新手动停止、编译、运行代码文件。 `run` 命令也不是实现热编译功能,而是提供了自动编译功能,当开发者修改了项目中的 `go` 文件时,该命令将会自动编译当前程序,并停止原有程序,运行新版的程序。 +:::tip +`run` 命令会递归监控 **当前运行目录** 的所有 `go` 文件变化来实现自动编译。 +::: +## 使用帮助 + +```text +$ gf run -h +USAGE + gf run FILE [OPTION] + +ARGUMENT + FILE building file path. + +OPTION + -p, --path output directory path for built binary file. it's "./" in default + -e, --extra the same options as "go run"/"go build" except some options as follows defined + -a, --args custom arguments for your process + -w, --watchPaths watch additional paths for live reload, separated by ",". i.e. "manifest/config/*.yaml" + -h, --help more information about this command + +EXAMPLE + gf run main.go + gf run main.go --args "server -p 8080" + gf run main.go -mod=vendor + gf run main.go -w "manifest/config/*.yaml" + +DESCRIPTION + The "run" command is used for running go codes with hot-compiled-like feature, + which compiles and runs the go codes asynchronously when codes change. + +``` + +配置文件格式示例: + +```yaml +gfcli: + run: + path: "./bin" + extra: "" + args: "all" + watchPaths: + - api/*.go + - internal/controller/*.go +``` + +参数介绍: + +| 名称 | 默认值 | 含义 | 示例 | +| --- | --- | --- | --- | +| `path` | `./` | 指定编译后生成的二进制文件存放目录。 | | +| `extra` | | 指定用于底层 `go build` 的命令参数 | | +| `args` | | 指定启动运行二进制文件的命令行参数 | | +| `watchPath` | | 指定本地项目文件监听的文件路径格式,支持多个路径使用 `,` 覆盖分隔。该参数的格式同标准库的 `filepath.Match` 方法参数 | `internal/*.go` | + +## 使用示例 + +一般 `gf run main.go` 即可 + +```text +$ gf run main.go --swagger +2020-12-31 00:40:16.948 build: main.go +2020-12-31 00:40:16.994 producing swagger files... +2020-12-31 00:40:17.145 done! +2020-12-31 00:40:17.216 gf pack swagger packed/swagger.go -n packed -y +2020-12-31 00:40:17.279 done! +2020-12-31 00:40:17.282 go build -o bin/main main.go +2020-12-31 00:40:18.696 go file changes: "/Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-demos/packed/swagger.go": WRITE +2020-12-31 00:40:18.696 build: main.go +2020-12-31 00:40:18.775 producing swagger files... +2020-12-31 00:40:18.911 done! +2020-12-31 00:40:19.045 gf pack swagger packed/swagger.go -n packed -y +2020-12-31 00:40:19.136 done! +2020-12-31 00:40:19.144 go build -o bin/main main.go +2020-12-31 00:40:21.367 bin/main +2020-12-31 00:40:21.372 build running pid: 40954 +2020-12-31 00:40:21.437 [DEBU] [ghttp] SetServerRoot path: /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf-demos/public +2020-12-31 00:40:21.440 40954: http server started listening on [:8199] +... +``` + +## 常见问题 + +[too many open files on macOS](https://github.com/fsnotify/fsnotify/issues/129) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" new file mode 100644 index 00000000000..369a5c1d787 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\350\265\204\346\272\220\346\211\223\345\214\205-pack.md" @@ -0,0 +1,51 @@ +--- +slug: '/docs/cli/pack' +title: '资源打包-pack' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,资源打包,CLI工具,gf pack,资源文件,go代码文件,文件打包,命令行工具,源码管理] +description: '该文档介绍了如何使用GoFrame框架中的gf pack命令将任意文件打包为资源文件或Go代码文件。通过该工具,用户可以实现资源打包和随可执行文件一同发布。此外,gf pack命令还能与build命令结合,实现打包和编译的一步操作。文档中详细列出命令的使用方法和选项说明,帮助用户更好地理解和使用该功能。' +--- + +## 使用方式 + +```text +$ gf pack -h +USAGE + gf pack SRC DST + +ARGUMENT + SRC source path for packing, which can be multiple source paths. + DST destination file path for packed file. if extension of the filename is ".go" and "-n" option is given, + it enables packing SRC to go file, or else it packs SRC into a binary file. + +OPTION + -n, --name package name for output go file, it's set as its directory name if no name passed + -p, --prefix prefix for each file packed into the resource file + -k, --keepPath keep the source path from system to resource file, usually for relative path + -h, --help more information about this command + +EXAMPLE + gf pack public data.bin + gf pack public,template data.bin + gf pack public,template packed/data.go + gf pack public,template,config packed/data.go + gf pack public,template,config packed/data.go -n=packed -p=/var/www/my-app + gf pack /var/www/public packed/data.go -n=packed +``` + +该命令用以将任意的文件打包为资源文件或者 `Go` 代码文件,可将任意文件打包后随着可执行文件一同发布。此外,在 `build` 命令中支持打包+编译一步进行,具体请查看 `build` 命令帮助信息。关于资源管理的介绍请参考 [资源管理](../核心组件/资源管理/资源管理.md) 章节。 + +## 使用示例 + +```text +$ gf pack public,template packed/data.go +done! +$ ll packed +total 184 +-rw-r--r-- 1 john staff 89K Dec 31 00:44 data.go +``` + +## 延伸阅读 + +- [资源管理-最佳实践](../核心组件/资源管理/资源管理-最佳实践.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" new file mode 100644 index 00000000000..3b59b5bf8f9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\225\234\345\203\217\347\274\226\350\257\221-docker.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/cli/docker' +title: '镜像编译-docker' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame框架,Docker镜像编译,gf docker,Makefile构建,二进制构建,gf build,gf gen enums,命令组合,自动推送,配置文件管理] +description: '使用GoFrame框架的gf docker命令编译和生成Docker镜像。在v2.5版本之后,建议通过Makefile脚本组合使用gf build, gf gen enums, gf docker等命令。这种方式更为灵活易于维护,文中提供了详细的使用示例和配置文件管理建议。' +--- +:::tip +从 `v2.5` 版本开始,考虑到各个工具命令的解耦性, `gf docker` 工具命令默认不再执行二进制构建编译,而是推荐大家通过 `Makefile` 构建脚本自行组织使用 `gf build, gf gen enums, gf docker` 等命令结合的方式来 **组合使用** 命令(工程项目中提供了对应的 `make build, make enums, make docker` 命令),组合使用更加灵活且易维护。 +::: +## 使用方式 + +```text +$ gf docker -h +USAGE + gf docker [MAIN] [OPTION] + +ARGUMENT + MAIN main file path for "gf build", it's "main.go" in default. empty string for no binary build + +OPTION + -f, --file file path of the Dockerfile. it's "manifest/docker/Dockerfile" in default + -s, --shell path of the shell file which is executed before docker build + -b, --build binary build options before docker image build, it's "-a amd64 -s linux" in default + -tn, --tagName tag name for this docker, pattern like "image:tag". this option is required with TagPrefixes + -tp, --tagPrefixes tag prefixes for this docker, which are used for docker push. this option is required with + TagName + -p, --push auto push the docker image to docker registry if "-t" option passed + -e, --extra extra build options passed to "docker image" + -h, --help more information about this command + +EXAMPLE + gf docker + gf docker -t hub.docker.com/john/image:tag + gf docker -p -t hub.docker.com/john/image:tag + gf docker main.go + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -t hub.docker.com/john/image:tag + gf docker main.go -p -t hub.docker.com/john/image:tag + +DESCRIPTION + The "docker" command builds the GF project to a docker images. + It runs "gf build" firstly to compile the project to binary file. + It then runs "docker build" command automatically to generate the docker image. + You should have docker installed, and there must be a Dockerfile in the root of the project. +``` + +自动编译并生成 `docker` 镜像。非必需 `MAIN` 参数为编译文件路径,默认为 `main.go`。非必需参数 `OPTIONS` 为 `docker build` 命令相同参数及选项。 + +## 使用示例 + +```text +$ gf docker main.go -p -tn loads/gf-demos:test +2020-12-31 00:47:28.207 start building... +2020-12-31 00:47:28.207 go build -o ./bin/linux_amd64/main main.go +2020-12-31 00:47:35.894 done! +Sending build context to Docker daemon 37.63MB +Step 1/10 : FROM loads/alpine:3.8 + ---> f9fb622e6db2 +Step 2/10 : LABEL maintainer="john@goframe.org" + ---> Using cache + ---> da238418d031 +Step 3/10 : ENV WORKDIR /var/www/gf-demos + ---> Using cache + ---> 3e7129c087c9 +Step 4/10 : ADD ./bin/linux_amd64/main $WORKDIR/main + ---> 3661a9dea494 +Step 5/10 : RUN chmod +x $WORKDIR/main + ---> Running in 1d49d5d91080 +Removing intermediate container 1d49d5d91080 + ---> a03ee04e3380 +Step 6/10 : ADD public $WORKDIR/public + ---> 63dd06d0e1a3 +Step 7/10 : ADD config $WORKDIR/config + ---> fa7a57eba577 +Step 8/10 : ADD template $WORKDIR/template + ---> 7075609b0447 +Step 9/10 : WORKDIR $WORKDIR + ---> Running in a34ef38e1031 +Removing intermediate container a34ef38e1031 + ---> 580077998eaf +Step 10/10 : CMD ./main + ---> Running in ed286b518ad9 +Removing intermediate container ed286b518ad9 + ---> fbbc05842901 +Successfully built fbbc05842901 +Successfully tagged loads/gf-demos:test +The push refers to repository [docker.io/loads/gf-demos] +b4025b95a79f: Preparing +9e0369a57507: Preparing +46c68dcc8e12: Preparing +59adbc083ee5: Preparing +10e0b999ba57: Preparing +8e850d7b086e: Waiting +d5e057db20a2: Waiting +92e898fd7f84: Waiting +d9ff549177a9: Waiting +... +``` + +## 配置文件示例 + +大部分场景下,我们推荐使用配置文件来管理工具的配置,在 `hack/config.yaml` 文件中维护,例如 `docker` 命令的配置示例: + +```yaml +gfcli: + docker: + build: "-a amd64 -s linux -p temp -ew" + tagPrefixes: + - ccr.ccs.tencentyun.com/cdb.khaos.eros +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" new file mode 100644 index 00000000000..be27ce4b9d6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\274\200\345\217\221\345\267\245\345\205\267/\351\241\271\347\233\256\345\210\233\345\273\272-init.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/cli/init' +title: '项目创建-init' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,项目创建,gf init,MonoRepo,GoFrame空框架,代码分层设计,go module,资源管理,微服务大仓管理模式] +description: '使用GoFrame框架提供的gf init命令创建项目。自v2版本起,项目创建更快速,不再依赖远端,模板已内置于二进制文件中。您可以按照需要选择初始化单仓或大仓项目模式,并灵活调整生成的目录结构以适应实际业务需求。' +--- +:::tip +从 `v2` 版本开始,项目的创建不再依赖远端获取,仓库模板已经通过 [资源管理](../核心组件/资源管理/资源管理.md) 的方式内置到了工具二进制文件中,因此项目创建速度非常迅速。 +::: +## 使用方式 + +```text +$ gf init -h +USAGE + gf init ARGUMENT [OPTION] + +ARGUMENT + NAME 项目名称,在当前目录下创建名为 NAME 的文件夹,并且 module 名称也为 NAME + +OPTION + -m, --mono 初始化大仓模式 mono-repo + -a, --monoApp 初始化大仓下的一个小仓 mono-repo-app + -u, --update 初始化后使用最新的框架版本 + -g, --module 自定义 module + -h, --help 更多帮助 + +EXAMPLE + gf init my-project + gf init my-mono-repo -m +``` + +我们可以使用 `init` 命令在当前目录生成一个示例的 `GoFrame` 空框架项目,并可给定项目名称参数。生成的项目目录结构仅供参考,根据业务项目具体情况可自行调整。生成的目录结构请参考 [代码分层设计](../框架设计/工程开发设计/代码分层设计.md) 章节。 +:::note +`GoFrame` 框架开发推荐统一使用官方的 `go module` 特性进行依赖包管理,因此空项目根目录下也有一个 `go.mod` 文件。 +::: +:::tip +工程目录采用了通用化的设计,实际项目中可以根据项目需要适当增减模板给定的目录。例如,没有 `kubernetes` 部署需求的场景,直接删除对应 `deploy` 目录即可。 +::: + +## 使用示例 + +### 在当前目录下初始化项目 + +```bash +$ gf init . +initializing... +initialization done! +you can now run 'gf run main.go' to start your journey, enjoy! +``` + +### 创建一个指定名称的项目 + +```bash +$ gf init myapp +initializing... +initialization done! +you can now run 'cd myapp && gf run main.go' to start your journey, enjoy! +``` + +### 创建一个 `MonoRepo` 项目 + +默认情况下创建的是 `SingleRepo` 项目,若有需要也可以创建一个 `MonoRepo`(大仓)项目,通过使用 `-m` 选项即可。 + +```bash +$ gf init mymono -m +initializing... +initialization done! +``` + +关于大仓的介绍请参考章节: [微服务大仓管理模式](../框架设计/工程开发设计/微服务大仓管理模式.md) + +#### 创建一个 `MonoRepoApp` 项目 + +若需要在 `MonoRepo`(大仓)下的创建一个小仓,在仓库项目根目录下,给定需要生成的项目地址,并使用 `-a` 选项即可。 + +```bash +$ gf init app/user -a +initializing... +initialization done! +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..21219bd047a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\267\245\347\250\213\347\256\241\347\220\206.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/micro-service/structure' +title: '工程管理' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,微服务,工程目录,协议文件,接口文件,开发工具,服务启动,接口实现,数据校验] +description: '使用GoFrame框架进行微服务开发的标准工程目录结构,包括协议文件和接口文件的管理。详细描述了如何利用GoFrame框架的开发工具生成数据表结构对应的protobuf文件,以及如何编译协议文件生成接口和控制器。同时,说明了服务的启动和接口实现的具体步骤,并介绍了标签注入和数据校验插件的使用方法。' +--- + +## 基本介绍 + +基于微服务开发的工程目录采用统一的框架工程目录结构,具体请参考章节: [工程目录设计🔥](../框架设计/工程开发设计/工程目录设计.md) + +我们这里以项目 [https://github.com/gogf/gf-demo-grpc](https://github.com/gogf/gf-demo-grpc) 为例说明。 + +## 协议文件 + +![](/markdown/016fd519878bf775e744f9f2d1c46cb8.png)协议文件定义到 `manifest/protobuf` 目录下。目录下的协议文件路径规则: `模块名/版本号/xxx.proto ` 其中的版本号以 `v1/v2` 类似形式管理,便于维护接口兼容性。 + +其中,涉及到数据表数据结构通过框架开发工具生成的 `protobuf` 文件存放到 `manifest/pbentity` 目录下。 + +## 接口文件 + +通过 `proto` 协议文件编译生成的接口文档存放到统一的 `api` 目录下。 + +## 开发工具 + +### 生成数据表结构 + +我们可以通过 `gf gen pbentity / make pbentity` 命令自动根据数据库中的数据表结构生成对应的 `protobuf` 协议文件。命令介绍请参考章节: [数据表PB-gen pbentity](../开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) + +### 编译 `proto` 协议文件 + +框架提供了 `gf gen pb / make pb` 命令自动编译 `proto` 协议文件,并生成对应的接口文件以及控制器文件。 + +## 服务的启动 + +服务的启动控制仍然是通过 `cmd` 目录来维护的,例如: [https://github.com/gogf/gf-demo-grpc/blob/main/internal/cmd/cmd.go](https://github.com/gogf/gf-demo-grpc/blob/main/internal/cmd/cmd.go) + +## 接口的实现与注册 + +控制器用于 `proto` 定义的接口的具体实现,控制器可以通过命令自动生成,并自动生成 `Register` 方法,用于将具体实现注册到服务对象中。 + +注册方法: + +![](/markdown/50e4eb739f08fcc6479bb32c9e9a6ade.png) + +启动注册: + +![](/markdown/5cda3b08b1346f392c4b717b71fa2710.png) + +## 标签注入与数据校验 + +### 标签自动注入 + +使用 `gf gen pb/make pb` 命令进行 `proto` 文件编译时,支持自动的标签注入。只需要通过注释的形式写到 `proto` 文件中,这些注释将会自动作为 `dc` 标签嵌入到结构体属性中。如果注释规则形如 `xxx:yyy `,那么将会自动生成 `xxx` 的标签。例如: + +![](/markdown/96c4eaa4ff55045ac0d224539a903a2b.png) + +![](/markdown/620e319d848d5b91b93d86c33862f19a.png) + +需要注意,在 `GRPC` 协议中,由于输入输出均是采用结构体形式,因此无法实现像 `HTTP` 服务那样的默认值特性。 + +### 数据校验插件 + +数据校验插件将会根据标签中设置的规则对请求进行自动校验,需要在服务端通过拦截器的形式手动引入: + +![](/markdown/a38675f4912ab10e2680814f0dae2e0f.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..720e73d91f6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221.md" @@ -0,0 +1,288 @@ +--- +slug: '/docs/micro-service' +title: '微服务开发' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,微服务,HTTP协议,GRPC协议,组件化,服务发现,注册组件,链路跟踪,protobuf,开发教程] +description: 'GoFrame框架的微服务开发特性,包括支持HTTP和GRPC协议的微服务组件、低藕性设计、服务注册发现组件的使用以及链路跟踪能力的示例教程,帮助团队快速实现微服务的开发与转型。示例涵盖了如何使用GoFrame进行微服务架构的设置与通信。' +--- +:::tip +微服务完整特性及相关组件从 `v2.4` 版本开始提供。 +::: +## 基本介绍 + +`GoFrame` 框架支持微服务模式开发,提供了常用的微服务组件、开发工具、开发教程帮助团队快速微服务转型。 + +## 简单示例 + +`GoFrame` 微服务组件的低藕及通用化设计的,组件化使用支持大部分的微服务通信协议。在官方文档中,我们以 `HTTP` 及 `GRPC` 协议为示例,介绍微服务的开发以及组件工具的使用。由于 `HTTP Web` 开发已经有比较丰富完善的独立章节介绍,因此微服务章节大部分介绍以 `GRPC` 为主。 + +### `HTTP` 微服务示例 + +[https://github.com/gogf/gf/tree/master/example/registry/file](https://github.com/gogf/gf/tree/master/example/registry/file) + +#### `server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +可以看到,一个 `HTTP` 的微服务端和一个普通的 `Web Server` 端没什么差异,但是顶部多了一行代码: + +``` +gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) +``` + +这行代码用于启用并注册当前服务使用的注册发现组件,在该示例中使用的 `file.New(gfile.Temp("gsvc"))` 是基于本地系统文件的服务注册发现组件,其中的 `gfile.Temp("gsvc")` 指定的是存放服务文件的路径,例如在 `Linux/MacOS` 系统下,指向的是 `/tmp/gsvc` 目录。基于文件系统的注册发现仅用于本地微服务示例,不能用于跨节点通信。在生产环境时,我们往往会使用其他的服务注册发现组件,例如 `etcd, polaris, zookeeper` 等,框架的社区组件中已经提供了常用的服务注册发现组件的实现。 + +其次,在该示例中,我们给 `Server` 设置了一个名字 `hello.svc`,该名字表示该 `Server` 绑定的微服务的名称,服务名称作为微服务的唯一标识,用于服务间的识别通信。当服务注册组件注册启用时, `HTTP Server` 在运行时将会把自己的访问地址注册到服务注册组件中,方便其他服务通过相同组件按照服务名称进行访问。 + +#### `client.go` + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/contrib/registry/file/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + gsvc.SetRegistry(file.New(gfile.Temp("gsvc"))) + + client := g.Client() + for i := 0; i < 10; i++ { + ctx := gctx.New() + res, err := client.Get(ctx, `http://hello.svc/`) + if err != nil { + panic(err) + } + g.Log().Debug(ctx, res.ReadAllString()) + res.Close() + time.Sleep(time.Second) + } +} +``` + +客户端通过 `g.Client()` 创建一个 `HTTP Client`,并通过 `http://hello.svc/` 地址访问服务端,其中的 `hello.svc` 即先前的 `Server` 端绑定的微服务名称。当客户端通过微服务名称访问的时候,服务注册发现组件将会在底层进行检索,并找到对应的服务端地址进行通信。 + +#### 执行结果 + +先执行 `server.go` 服务端运行一个简单的服务,随后再执行 `client.go` 通过服务名称请求服务。 + +执行后,客户端输出: + +```bash +$ go run client.go +2023-03-14 20:22:10.006 [DEBU] {8054f3a48c484c1760fb416bb3df20a4} Hello world +2023-03-14 20:22:11.007 [DEBU] {6831cae08c484c1761fb416b9d4df851} Hello world +2023-03-14 20:22:12.008 [DEBU] {9035761c8d484c1762fb416b1e648b81} Hello world +2023-03-14 20:22:13.011 [DEBU] {a05a32588d484c1763fb416bc19ff667} Hello world +2023-03-14 20:22:14.012 [DEBU] {40fdea938d484c1764fb416b8459fc43} Hello world +2023-03-14 20:22:15.014 [DEBU] {686c9acf8d484c1765fb416b3697d369} Hello world +2023-03-14 20:22:16.015 [DEBU] {906a470b8e484c1766fb416b85b9867e} Hello world +2023-03-14 20:22:17.017 [DEBU] {28c7fd468e484c1767fb416b86e5557f} Hello world +2023-03-14 20:22:18.018 [DEBU] {90d2ad828e484c1768fb416bfcde738f} Hello world +2023-03-14 20:22:19.019 [DEBU] {d05559be8e484c1769fb416baad06f23} Hello world +``` + +服务端输出: + +```bash +$ go run server.go +2023-03-14 20:20:06.364 [INFO] pid[96421]: http server started listening on [:61589] +2023-03-14 20:20:06.364 [INFO] openapi specification is disabled +2023-03-14 20:20:06.364 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:61589 Metadata:map[insecure:true protocol:http]} + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :61589 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :61589 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-14 20:22:10.006 [INFO] {8054f3a48c484c1760fb416bb3df20a4} request received +2023-03-14 20:22:11.007 [INFO] {6831cae08c484c1761fb416b9d4df851} request received +2023-03-14 20:22:12.008 [INFO] {9035761c8d484c1762fb416b1e648b81} request received +2023-03-14 20:22:13.010 [INFO] {a05a32588d484c1763fb416bc19ff667} request received +2023-03-14 20:22:14.012 [INFO] {40fdea938d484c1764fb416b8459fc43} request received +2023-03-14 20:22:15.013 [INFO] {686c9acf8d484c1765fb416b3697d369} request received +2023-03-14 20:22:16.015 [INFO] {906a470b8e484c1766fb416b85b9867e} request received +2023-03-14 20:22:17.016 [INFO] {28c7fd468e484c1767fb416b86e5557f} request received +2023-03-14 20:22:18.017 [INFO] {90d2ad828e484c1768fb416bfcde738f} request received +2023-03-14 20:22:19.019 [INFO] {d05559be8e484c1769fb416baad06f23} request received +``` + +### `GRPC` 微服务示例 + +[https://github.com/gogf/gf/tree/master/example/rpc/grpcx/basic](https://github.com/gogf/gf/tree/master/example/rpc/grpcx/basic) + +#### `helloworld.proto` + +`grpc` 和 `http` 协议比较大的差异在于 `grpc` 需要通过 `protobuf` 来实现 `API` 接口以及数据结构的定义。 + +``` +syntax = "proto3"; + +package protobuf; + +option go_package = "github.com/gogf/gf/grpc/example/helloworld/protobuf"; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply) {} +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} +``` + +以上 `protobuf` 文件通过以下命令执行编译(请提前安装 `protoc` 工具): + +``` +gf gen pb +``` + +将会生成对应的 `proto go` 数据结构文件以及 `grpc` 接口文件: + +``` +helloworld.pb.go +helloworld_grpc.pb.go +``` + +#### `controller.go` + +控制器用于实现 `proto` 中定义的接口方法(如果使用框架标准化工程目录结构,该控制器代码文件也是由框架的 `gf gen pb` 工具自动生成,开发者进需要填充对应方法的具体实现即可): + +```go +type Controller struct { + protobuf.UnimplementedGreeterServer +} + +func Register(s *grpcx.GrpcServer) { + protobuf.RegisterGreeterServer(s.Server, &Controller{}) +} + +// SayHello implements helloworld.GreeterServer +func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) { + return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil +} +``` + +#### `config.yaml` + +服务端配置文件,在该配置文件中指定了该服务的名称为 `demo`。微服务的名称用于服务间通信的唯一识别标识。在不显式配置服务端的监听端口时,服务端将会随机监听可用的本地端口。在微服务模式下,由于使用服务名称进行通信,服务端口往往不需要显式指定,随机监听即可。 + +``` +grpc: + name: "demo" + logPath: "./log" + logStdout: true + errorLogEnabled: true + accessLogEnabled: true + errorStack: true +``` + +#### `server.go` + +`grpc` 服务端,在不显式指定服务端使用的服务注册发现组件时,服务端默认使用系统文件注册发现组件,该组件仅用于单机测试。其中的 `controller.Register` 即调用我们通过工具生成的控制器注册方法将具体的接口实现注册到服务端中。 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +#### `client.go` + +`grpc` 客户端,在创建连接时需要给定服务端服务的具体名称。这里的服务端服务名称为 `demo`,指定的是上面提到的微服务名称。在不显式指定客户端使用的服务注册发现组件时,客户端默认使用系统文件注册发现组件,该组件仅用于单机测试。 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +#### 执行结果 + +服务端输出:可以看到,服务端输出了一下 `DEBU` 调试信息,用于显示一些服务注册的细节。同时,由于没有显式指定服务端的监听端口,这里随机监听了本地端口 `64517`。 + +```bash +$ go run server.go +2023-03-14 20:50:58.465 [DEBU] set default registry using file registry as no custom registry set +2023-03-14 20:50:58.466 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:64517 Metadata:map[protocol:grpc]} +2023-03-14 20:50:58.466 [INFO] pid[98982]: grpc server started listening on [:64517] +2023-03-14 20:52:37.059 {9898c809364a4c17da79e47f3e6c3b8f} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +``` + +客户端输出:客户端通过微服务名称访问,并接收到了服务端的返回。注意客户端的日志和服务端的日志中,链路跟踪的 `TraceID` 是相同的( `9898c809364a4c17da79e47f3e6c3b8f`),表示同一个请求产生的日志。 `GoFrame` 的微服务特性默认开启了链路跟踪能力。 + +```bash +$ go run client.go +2023-03-14 20:52:37.060 [DEBU] {9898c809364a4c17da79e47f3e6c3b8f} Response: Hello World +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..ce41d7f2859 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\213\246\346\210\252\345\231\250\347\273\204\344\273\266.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/micro-service/interceptor' +title: '拦截器组件' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,GRPC拦截器,服务端拦截器,客户端拦截器,grpcx组件,链路跟踪,错误处理,自动错误校验,服务端Panic捕获] +description: '在GoFrame框架中使用GRPC拦截器的方法,详细说明了服务端和客户端的拦截器实现,包括链路跟踪、错误处理和自动错误校验等功能。通过使用grpcx组件,用户可以灵活地添加和自定义拦截器,提高GRPC服务和客户端的可扩展性与稳定性。' +--- + +`GRPC` 支持拦截器特性,提高了 `GRPC` 的灵活性和扩展性。 + +## 拦截器使用 + +### 服务端 + +使用 `grpcx.Server.ChainUnary` 增加额外的服务端拦截器: + +```go +c := grpcx.Server.NewConfig() +c.Options = append(c.Options, []grpc.ServerOption{ + grpcx.Server.ChainUnary( + grpcx.Server.UnaryValidate, + )}..., +) +s := grpcx.Server.New(c) +user.Register(s) +s.Run() +``` + +### 客户端 + +使用 `grpcx.Client.ChainUnary` 增加额外的服务端拦截器: + +``` +conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Client.ChainUnary( + grpcx.Client.UnaryTracing, +)) +``` + +## 拦截器列表 + +框架的 `grpcx` 组件提供了一系列的常用拦截器,部分内置,部分可选择插拔使用。 + +| 拦截器 | 使用部位 | 是否内置 | 描述 | +| --- | --- | --- | --- | +| `UnaryError` | 客户端 | 是 | 支持框架错误处理组件。 | +| `UnaryTracing` | 客户端 | 是 | 支持链路跟踪。 | +| `StreamTracing` | 客户端 | 是 | 支持链路跟踪(长连接)。 | +| `UnaryError` | 服务端 | 是 | 支持框架错误处理组件。 | +| `UnaryRecover` | 服务端 | 是 | 支持服务端 `panic` 自动捕获不崩溃。 | +| `UnaryAllowNilRes` | 服务端 | 是 | 支持 `nil` 的 `Res` 对象返回。 | +| `UnaryValidate` | 服务端 | - | 支持框架的自动错误校验,基于结构体标签。需要手动引入。 | +| `UnaryTracing` | 服务端 | 是 | 支持链路跟踪。 | +| `StreamTracing` | 服务端 | 是 | 支持链路跟踪(长连接)。 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" new file mode 100644 index 00000000000..749fb95943c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\346\263\250\345\206\214\345\217\221\347\216\260.md" @@ -0,0 +1,229 @@ +--- +slug: '/docs/micro-service/registry-discovery' +title: '服务注册发现' +sidebar_position: 5 +hide_title: true +description: 'GoFrame框架内服务注册发现组件的使用和实现,由gsvc组件管理,支持etcd、zookeeper、polaris等多种实现方式。通过合理配置实现全局服务注册发现,提高服务调用的灵活性和可扩展性,是开发微服务架构的重要指南。' +keywords: [GoFrame框架,服务注册,服务发现,微服务架构,gsvc组件,etcd,zookeeper,polaris,灵活性,可扩展性] +--- + +## 基本介绍 + +`GoFrame` 框架提供了服务注册发现组件,由 `gsvc` 组件管理,该组件主要定义了注册发现的接口,具体的实现由社区组件提供: [https://github.com/gogf/gf/tree/master/contrib/registry](https://github.com/gogf/gf/tree/master/contrib/registry) 。目前社区组件提供了多种注册发现的实现,如 `etcd, zookeeper, polaris` 等,开发者根据需要插拔使用,也可以根据 `gsvc` 组件的接口定义实现自己的注册发现组件。 + +## 组件启用 + +注册发现组件只有在引入具体的接口实现时才会启用。例如,使用 `etcd` 实现注册发现的使用方式: + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + // ... +} +``` + +## 常用组件 + +| 组件名称 | 文档说明 | 备注 | +| --- | --- | --- | +| `file` | [https://github.com/gogf/gf/tree/master/contrib/registry/file](https://github.com/gogf/gf/tree/master/contrib/registry/file) | 仅用于单机测试 | +| `etcd` | [https://github.com/gogf/gf/tree/master/contrib/registry/etcd](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) | | +| `polaris` | [https://github.com/gogf/gf/tree/master/contrib/registry/polaris](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) | | +| `zookeeper` | [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) | | + +更多组件,请参考: [https://github.com/gogf/gf/tree/master/contrib/registry](https://github.com/gogf/gf/tree/master/contrib/registry) + +## 使用示例 + +### `HTTP` + +可以使用 ``gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`))`` 来设置使用 `etcd` 的注册发现。其中的 `etcd.New` 表示通过社区组件创建一个 `gsvc.Registry` 的接口实现对象,并通过 `gsvc.SetRegistry` 方法设置全局默认的注册发现接口实现对象。 + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + ctx := gctx.New() + res := g.Client().GetContent(ctx, `http://hello.svc/`) + g.Log().Info(ctx, res) +} +``` + +执行后,服务端输出: + +```bash +$ go run server.go +2023-03-15 20:55:56.256 [INFO] pid[3358]: http server started listening on [:60700] +2023-03-15 20:55:56.256 [INFO] openapi specification is disabled +2023-03-15 20:55:56.256 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:60700 Metadata:map[insecure:true protocol:http]} +2023-03-15 20:55:56.297 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:60700", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813002" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :60700 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :60700 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 20:56:45.739 [INFO] {880eaa8104994c17ffb384495cd4c613} request received +``` + +客户端输出: + +```bash +$ go run client.go +2023-03-15 20:56:45.739 [INFO] {880eaa8104994c17ffb384495cd4c613} Hello world +``` + +### `GRPC` +:::warning +如果是 `GRPC` 协议,必须使用 `grpcx.Resolver` 的 `grpcx` 模块来设置服务注册发现组件。 `Server` 端在 `config.yaml` 中没有设置 `grpc.name` 值时,默认值是 `default`。 +::: +`server.go` + +代码中的 `etcd.New` 表示通过社区组件创建一个 `gsvc.Registry` 的接口实现对象,并通过 `grpcx.Resolver.Register` 设置全局的 `grpc` 注册发现接口实现对象。 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/registry/etcd/grpc/controller" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +config.yaml + +服务端默认的配置文件: + +``` +grpc: + name: "demo" # 服务名称 + address: ":8000" # 自定义服务监听地址 + logPath: "./log" # 日志存储目录路径 + logStdout: true # 日志是否输出到终端 + errorLogEnabled: true # 是否开启错误日志记录 + accessLogEnabled: true # 是否开启访问日志记录 + errorStack: true # 当产生错误时,是否记录错误堆栈 +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/registry/etcd/grpc/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +执行后,服务端输出: + +```bash +$ go run server.go +2023-03-15 21:06:57.204 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:61978 Metadata:map[protocol:grpc]} +2023-03-15 21:06:57.257 [DEBU] etcd put success with key "/service/default/default/demo/latest/10.35.12.81:61978", value "{"protocol":"grpc"}", lease "7587869265945813015" +2023-03-15 21:06:57.257 [INFO] pid[5786]: grpc server started listening on [:61978] +2023-03-15 21:07:04.955 {08f0aead94994c1731591d2b653ddc18} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +``` + +客户端输出: + +```bash +$ go run client.go +2023-03-15 21:07:04.950 [DEBU] Watch key "/service/default/default/demo/latest/" +2023-03-15 21:07:04.952 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:61978","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 21:07:04.953 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:61978","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 21:07:04.955 [DEBU] {08f0aead94994c1731591d2b653ddc18} Response: Hello World +``` + +## 常见问题 + +### 在全局启用服务注册发现时,如何关闭特定请求的发现特性 + +**问题**:在使用 `gclient` 时,如果全局开启了服务注册发现特性,那么 `gclient` 的所有请求都将走发现服务。但是针对不是在服务注册发现中维护的服务,例如一个 `IP:PORT` 的地址请求或者一个外网请求服务,也会走发现服务,并且会引起服务找不到而报错。如何避免这样的问题? + +**回答**:在全局启用服务发现特性时, `gclient` 请求默认情况下会使用全局设置的发现服务,如果特定的请求不使用发现服务,那么可以通过 `gclient.Client` 的 `Discovery(nil)` 链式操作方法来 **关闭当前请求的发现服务**,或者 `SetDiscovery(nil)` 配置方法来 **关闭当前客户端的发现服务**。这样请求就不会走发现服务啦。 + +**例子**: + +```go +// 通过链式操作方法来关闭当前请求的发现服务 +g.Client().Discovery(nil).Get(ctx, "http://192.168.1.1/api/v1/user") + +// 通过配置方法来关闭当前客户端的发现服务 +client := g.Client() +client.SetDiscovery(nil) +client.Get(ctx, "http://192.168.1.1/api/v1/user") +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..f16fb6109d5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\347\253\257\351\205\215\347\275\256.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/micro-service/config' +title: '服务端配置' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,配置管理,服务端配置,GrpcServerConfig,日志配置,日志组件,服务监听,自动映射,错误日志] +description: '服务端的配置方式,包括如何通过GoFrame框架读取和管理配置文件。提供了一份完整的配置模板示例,涵盖服务名称、服务监听地址、日志存储目录、错误日志记录及访问日志记录的设置方法。该配置与框架自动读取逻辑一致,确保了便捷的服务部署和高效的日志管理,以及如何设置和使用参数日志组件的配置进行独立的grpc server日志管理。' +--- + +## 基本介绍 + +服务端支持配置文件,所有的配置将会自动映射到配置对象中。配置对象如下: + +```go +// GrpcServerConfig is the configuration for server. +type GrpcServerConfig struct { + Address string // (optional) Address for server listening. + Name string // (optional) Name for current service. + Logger *glog.Logger // (optional) Logger for server. + LogPath string // (optional) LogPath specifies the directory for storing logging files. + LogStdout bool // (optional) LogStdout specifies whether printing logging content to stdout. + ErrorStack bool // (optional) ErrorStack specifies whether logging stack information when error. + ErrorLogEnabled bool // (optional) ErrorLogEnabled enables error logging content to files. + ErrorLogPattern string // (optional) ErrorLogPattern specifies the error log file pattern like: error-{Ymd}.log + AccessLogEnabled bool // (optional) AccessLogEnabled enables access logging content to file. + AccessLogPattern string // (optional) AccessLogPattern specifies the error log file pattern like: access-{Ymd}.log +} +``` + +配置文件的自动读取逻辑与框架一致,详细介绍请参考章节: [配置管理](../核心组件/配置管理/配置管理.md) + +## 配置模板 + +一个完整的配置模板示例: + +```yaml +grpc: + name: "demo" # 服务名称 + address: ":8000" # 自定义服务监听地址 + logPath: "./log" # 日志存储目录路径 + logStdout: true # 日志是否输出到终端 + errorLogEnabled: true # 是否开启错误日志记录 + accessLogEnabled: true # 是否开启访问日志记录 + errorStack: true # 当产生错误时,是否记录错误堆栈 + + # 日志扩展配置(参数日志组件配置) + logger: + path: "/var/log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + file: "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log" + prefix: "" # 日志内容输出前缀。默认为空 + level: "all" # 日志输出级别 + stdout: true # 日志是否同时输出到终端。默认true + rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性 + rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性 + rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除 + rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除 + rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩 + rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时 + +``` + +其中的日志配置与 `http server` 一致,可以独立使用日志组件的配置项来配置 `grpc server` 的日志。关于日志组件的配置,可参考该文档详细了解: [日志组件-配置管理](../核心组件/日志组件/日志组件-配置管理.md) + +在没有配置 `address` 的情况下, `grpc server` 将会使用本地网卡的所有 `ip` 地址加上 **随机空闲端口** 来启动(默认配置 `:0`)。如果想要指定 `ip` 但是使用随机空闲端口启动 `grpc server`,可以使用 `ip:0` 的格式来配置 `address`,例如: `192.168.1.1:0, 10.0.1.1:0`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" new file mode 100644 index 00000000000..133b8f6386a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\350\264\237\350\275\275\345\235\207\350\241\241.md" @@ -0,0 +1,240 @@ +--- +slug: '/docs/micro-service/load-balance' +title: '服务负载均衡' +sidebar_position: 6 +hide_title: true +keywords: [负载均衡,GoFrame,gsel组件,LeastConnection,Random,RoundRobin,Weight,HTTP服务,GRPC服务,自定义策略] +description: 'GoFrame框架中的负载均衡组件,gsel组件提供了多种内置负载均衡策略,包括LeastConnection、Random、RoundRobin和Weight。开发者可以根据需求选择合适的策略或自定义实现,并通过HTTP和GRPC示例展示了负载均衡策略的实际应用。' +--- + +## 基本介绍 + +负载均衡组件使用在客户端中。 `GoFrame` 框架提供了解耦化设计的、灵活性高、扩展性强的负载均衡组件,由 `gsel` 组件管理,该组件定义了负载均衡的接口,并提供了多种内置的负载均衡策略实现。开发者也可以根据接口实现自定义的负载均衡策略。 + +## 策略列表 + +`gsel` 组件提供了多种常用的负载均衡策略,供开发者选择使用: + +| 策略名称 | 策略描述 | +| --- | --- | +| `LeastConnection` | 最小连接数。 | +| `Random` | 随机访问。 | +| `RoundRobin` | 轮训访问。 | +| `Weight` | 权重访问。服务注册时需要设置 `Weight` 参数。 | + +## 使用示例 + +### `HTTP` + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gsvc" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + + s := g.Server(`hello.svc`) + s.BindHandler("/", func(r *ghttp.Request) { + g.Log().Info(r.Context(), `request received`) + r.Response.Write(`Hello world`) + }) + s.Run() +} +``` + +`client.go` + +这里使用 `gsel.SetBuilder(gsel.NewBuilderRoundRobin())` 设置全局的负载均衡策略为轮训访问策略。 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gsel" + "github.com/gogf/gf/v2/net/gsvc" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + gsvc.SetRegistry(etcd.New(`127.0.0.1:2379`)) + gsel.SetBuilder(gsel.NewBuilderRoundRobin()) + + for i := 0; i < 10; i++ { + ctx := gctx.New() + res := g.Client().GetContent(ctx, `http://hello.svc/`) + g.Log().Info(ctx, res) + } +} +``` + +分别启动两个服务端,并执行客户端。 + +`server1` 终端输出: + +```bash +$ go run server.go +2023-03-15 21:24:08.413 [INFO] pid[10219]: http server started listening on [:63956] +2023-03-15 21:24:08.413 [INFO] openapi specification is disabled +2023-03-15 21:24:08.413 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:63956 Metadata:map[insecure:true protocol:http]} +2023-03-15 21:24:08.455 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:63956", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813020" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63956 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63956 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 21:24:18.357 [INFO] {e05b6049859a4c17d1de5d62eafa5a5f} request received +2023-03-15 21:24:18.358 [INFO] {785e9349859a4c17d3de5d62049e5b51} request received +2023-03-15 21:24:18.360 [INFO] {7076ab49859a4c17d5de5d62aaa64c85} request received +2023-03-15 21:24:18.360 [INFO] {205fb849859a4c17d7de5d62cb2590f4} request received +2023-03-15 21:24:18.361 [INFO] {885fc349859a4c17d9de5d6235937e31} request received +``` + +`server2` 终端输出: + +```bash +$ go run server.go +2023-03-15 21:24:10.769 [INFO] pid[10242]: http server started listening on [:63964] +2023-03-15 21:24:10.770 [INFO] openapi specification is disabled +2023-03-15 21:24:10.770 [DEBU] service register: &{Head: Deployment: Namespace: Name:hello.svc Version: Endpoints:10.35.12.81:63964 Metadata:map[insecure:true protocol:http]} +2023-03-15 21:24:10.812 [DEBU] etcd put success with key "/service/default/default/hello.svc/latest/10.35.12.81:63964", value "{"insecure":true,"protocol":"http"}", lease "7587869265945813023" + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63964 | ALL | / | main.main.func1 | +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + hello.svc | default | :63964 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2023-03-15 21:24:18.357 [INFO] {602d8749859a4c17d2de5d62d515e464} request received +2023-03-15 21:24:18.359 [INFO] {e0ed9b49859a4c17d4de5d628284ae62} request received +2023-03-15 21:24:18.360 [INFO] {e0e0b249859a4c17d6de5d62beda3001} request received +2023-03-15 21:24:18.361 [INFO] {7087bd49859a4c17d8de5d62f892e8aa} request received +2023-03-15 21:24:18.361 [INFO] {e8aec849859a4c17dade5d6247101836} request received +``` + +客户端终端输出: + +```bash +$ go run client.go +2023-03-15 21:24:18.357 [INFO] {e05b6049859a4c17d1de5d62eafa5a5f} Hello world +2023-03-15 21:24:18.358 [INFO] {602d8749859a4c17d2de5d62d515e464} Hello world +2023-03-15 21:24:18.358 [INFO] {785e9349859a4c17d3de5d62049e5b51} Hello world +2023-03-15 21:24:18.359 [INFO] {e0ed9b49859a4c17d4de5d628284ae62} Hello world +2023-03-15 21:24:18.360 [INFO] {7076ab49859a4c17d5de5d62aaa64c85} Hello world +2023-03-15 21:24:18.360 [INFO] {e0e0b249859a4c17d6de5d62beda3001} Hello world +2023-03-15 21:24:18.360 [INFO] {205fb849859a4c17d7de5d62cb2590f4} Hello world +2023-03-15 21:24:18.361 [INFO] {7087bd49859a4c17d8de5d62f892e8aa} Hello world +2023-03-15 21:24:18.361 [INFO] {885fc349859a4c17d9de5d6235937e31} Hello world +2023-03-15 21:24:18.361 [INFO] {e8aec849859a4c17dade5d6247101836} Hello world +``` + +### `GRPC` + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx context.Context + conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom()) + client = protobuf.NewGreeterClient(conn) + ) + for i := 0; i < 10; i++ { + ctx = gctx.New() + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) + } +} +``` + +其中的 `grpcx.Balancer.WithRandom()` 表示使用随机的请求策略。启动两个 `server.go` 服务端,随后运行 `client.go` 客户端,查看服务端的请求日志: + +`server1` 终端输出: + +```bash +$ go run server.go +2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:44.802 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53962 Metadata:map[protocol:grpc]} +2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962] +2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +`server2` 终端输出: + +```bash +$ go run server.go +2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:51.721 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53973 Metadata:map[protocol:grpc]} +2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973] +2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +客户端终端输出: + +```bash +$ go run client.go +2023-03-15 19:50:57.278 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:53962","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null},{"Addr":"10.35.12.81:53973","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World +2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World +2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World +2023-03-15 19:50:57.287 [DEBU] {f0fab02f6d954c17cdf33505438b2c80} Response: Hello World +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..fd63ba9100f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\346\234\215\345\212\241\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,179 @@ +--- +slug: '/docs/micro-service/config-service' +title: '服务配置管理' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,配置管理,微服务,Polaris,Apollo,Nacos,Consul,Kubernetes,容器编排,配置中心] +description: '在GoFrame框架中使用配置管理组件,通过解耦化设计,支持多种第三方配置中心如Polaris、Apollo、Nacos、Consul等。通过详细代码展示了如何初始化和启用Polaris配置客户端,并提供了使用示例和错误处理方式。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了配置管理组件,采用了解耦化及接口化设计,可以很灵活地对接各种第三方配置管理中心。配置管理组件默认提供基于本地系统文件的实现,更多的实现请参考社区组件: [https://github.com/gogf/gf/tree/master/contrib/config](https://github.com/gogf/gf/tree/master/contrib/config) + +社区组件提供了常用的多种配置中心实现,例如 `polaris, apollo, nacos, consul` 以及容器编排的 `kubernetes configmap`。 + +## 组件启用 + +配置管理组件的启用采用了包初始化的方式,并且由于配置管理功能比较底层,因此需要保证社区包在 `main` 包的最顶部引入,防止踩坑。我们这里以 `polaris` 为例,社区组件的使用说明: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) + +需要提供一个独立的引入包,例如 `boot`: + +```go +package boot + +import ( + "github.com/gogf/gf/contrib/config/polaris/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func init() { + var ( + ctx = gctx.GetInitCtx() + namespace = "default" + fileGroup = "TestGroup" + fileName = "config.yaml" + path = "manifest/config/polaris.yaml" + logDir = "/tmp/polaris/log" + ) + // Create polaris Client that implements gcfg.Adapter. + adapter, err := polaris.New(ctx, polaris.Config{ + Namespace: namespace, + FileGroup: fileGroup, + FileName: fileName, + Path: path, + LogDir: logDir, + Watch: true, + }) + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + // Change the adapter of default configuration instance. + g.Cfg().SetAdapter(adapter) +} +``` + +其中: + +- `Namespace` 指定的是 `polaris` 配置中的命名空间。 +- `FileGroup` 指定的是 `polaris` 中的文件分组。 +- `FileName` 指定的是需要读取的 `polaris` 中的配置文件名称。 +- `Path` 指定的是 `polaris` 服务端配置,包括polaris的链接地址、监听地址、组件输出日志路径等。 + +`Polaris` 的配置文件如下: + +```yaml +global: + serverConnector: + addresses: + - 127.0.0.1:8091 +config: + configConnector: + addresses: + - 127.0.0.1:8093 +consumer: + localCache: + persistDir: "/tmp/polaris/backup" +``` + +随后在 `main.go` 顶部引入 `boot` 包,保证该包的引入在所有组件的最前面: + +```go +package main + +import ( + _ "github.com/gogf/gf/example/config/polaris/boot" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.GetInitCtx() + + // Available checks. + g.Dump(g.Cfg().Available(ctx)) + + // All key-value configurations. + g.Dump(g.Cfg().Data(ctx)) + + // Retrieve certain value by key. + g.Dump(g.Cfg().MustGet(ctx, "server.address")) +} +``` + +注意其中顶部 `import` 的引入语句: + +``` +_ "github.com/gogf/gf/example/config/polaris/boot" +``` + +## 常用组件 + +| 组件名称 | 文档说明 | 备注 | +| --- | --- | --- | +| `file` | 框架内置 | 默认实现 | +| `apollo` | [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) | | +| `kubecm` | [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) | 常用于容器部署环境 | +| `nacos` | [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) | | +| `polaris` | [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) | | +| `consul` | [https://github.com/gogf/gf/tree/master/contrib/config/consul](https://github.com/gogf/gf/tree/master/contrib/config/consul) | | + +更多组件,请参考: [https://github.com/gogf/gf/tree/master/contrib/config](https://github.com/gogf/gf/tree/master/contrib/config) + +## 使用示例 + +[https://github.com/gogf/gf/tree/master/example/config/polaris](https://github.com/gogf/gf/tree/master/example/config/polaris) + +### 运行 `polaris` + +```bash +docker run -d --name polaris -p 8080:8080 -p 8090:8090 -p 8091:8091 -p 8093:8093 loads/polaris-server-standalone:1.11.2 +``` + +### 运行示例 + +```bash +$ go run main.go +true +{} +"failed to update local value: config file is empty" +panic: failed to update local value: config file is empty + +goroutine 1 [running]: +github.com/gogf/gf/v2/os/gcfg.(*Config).MustGet(0x0?, {0x1c1c4f8?, 0xc0000c2000?}, {0x1ac11ad?, 0x0?}, {0x0?, 0xc000002340?, 0xc000064738?}) + /Users/john/Workspace/gogf/gf/os/gcfg/gcfg.go:167 +0x5e +main.main() + /Users/john/Workspace/gogf/gf/example/config/polaris/main.go:20 +0x1b8 +``` + +可以看到最后的 `MustGet` 方法执行有报错,这是因为 `polaris` 中并没有指定的命名空间、配置分组以及配置文件,即便查询不到任何数据但是涉及到配置问题因此返回错误了。由于这里使用的是 `Must*` 方法,那么在执行返回错误时将会直接 `panic` 而不是返回错误。 + +那么我们进入 `polaris` 的后台添加一些测试数据再试试。 + +### 添加测试数据 + +进入 [http://127.0.0.1:8080/#/login](http://127.0.0.1:8080/#/login) 默认账号 `polaris` 密码 `polaris` + +![](/markdown/b6aa368b0594187558a778aa54a428a2.png) + +![](/markdown/acaecb43af69ec5f149e1fbe8f74dc4b.png) + +### 再次运行示例 + +```bash +$ go run main.go +true +{ + "server": { + "openapiPath": "/api.json", + "swaggerPath": "/swagger", + "address": ":8199", + }, +} + +":8199" +``` + +可以看到已经正确查询到了 `polaris` 中的配置数据。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" new file mode 100644 index 00000000000..7a73920f5fc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\347\216\257\345\242\203\345\207\206\345\244\207.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/micro-service/prepare-environment' +title: '环境准备' +sidebar_position: 0 +hide_title: true +keywords: [环境准备,微服务,GRPC协议,GoFrame,GoFrame框架,依赖安装,CLI开发工具,protoc工具,框架工具,协议支持] +description: '为微服务开发准备环境,主要包括GRPC协议的依赖安装指南和GoFrame框架的CLI工具。学习者需确保GRPC相关工具的安装,并掌握GRPC协议在GoFrame框架中微服务开发中的基本应用。' +--- + +后续微服务章节主要以 `GRPC` 协议为主,介绍微服务的开发、组件及工具使用。 + +## 依赖安装 + +在进一步学习之前,请先了解 `GRPC` 相关概念,并且安装对应的工具到本地开发环境: + +- [https://grpc.io/](https://grpc.io/) +- [https://grpc.io/docs/languages/go/quickstart/](https://grpc.io/docs/languages/go/quickstart/) + +如果是 `MacOS` 环境,可以考虑使用 `brew` 工具安装依赖: + +``` +brew install grpc protoc-gen-go protoc-gen-go-grpc +``` + +依赖工具安装完成后,请参考 [https://grpc.io/docs/languages/go/quickstart/](https://grpc.io/docs/languages/go/quickstart/) 章节的介绍完成基础的 `protoc` 工具使用学习。 + +## 框架工具 + +`GoFrame` 框架的CLI开发工具请保证版本 `>= v2.4`。工具安装、升级请参考章节: [开发工具](../开发工具/开发工具.md) + +框架工具针对 `GRPC` 协议,提供了额外的命令支持,极大地简化了基于 `GRPC` 协议的微服务开发。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" new file mode 100644 index 00000000000..b7f9f4331fa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\345\276\256\346\234\215\345\212\241\345\274\200\345\217\221/\350\204\232\346\211\213\346\236\266\346\250\241\345\235\227.md" @@ -0,0 +1,376 @@ +--- +slug: '/docs/micro-service/scaffold' +title: '脚手架模块' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,微服务,GRPC,脚手架,服务端,客户端,上下文,负载均衡,注册发现,etcd] +description: 'GoFrame框架中的脚手架模块提供了便捷的GRPC服务端和客户端实现,包含上下文、负载均衡和服务注册发现等功能。通过grpcx组件,用户可以轻松地构建和管理微服务应用,支持多种负载均衡策略以及etcd注册中心,简化服务间通信与数据传递。' +--- + +`GoFrame` 框架包含多个微服务组件,并提供了易用的 `GRPC` 脚手架模块和工具。脚手架由 `grpcx` 社区包实现: [https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx](https://github.com/gogf/gf/tree/master/contrib/rpc/grpcx) 包含多个模块。 + +## 服务端- `Server` + +服务端由 `grpcx.Server` 模块维护,用于实现服务端对象的创建与维护。使用示例: [https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/server/main.go](https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/server/main.go) + +服务端的创建往往结合配置文件一起使用,关于服务端配置文件的介绍请参考章节: [服务端配置](服务端配置.md) + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +## 客户端- `Client` + +客户端由 `grpcx.Client` 模块维护,用于实现客户端对象的创建与维护。使用示例: [https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/client/main.go](https://github.com/gogf/gf/blob/master/example/rpc/grpcx/basic/client/main.go) + +大部分场景下,服务间的访问使用的是服务名称。 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/basic/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` +:::warning +常见问题 + +**是否需要保存复用创建的 `Client` 对象?** + +每一个 `grpc Client` 对象实际上对应的是对一个目标服务的访问,该对象应该保存下来复用,而不是每一次都新建 `Client` 对象,这样可以提高效率、降低资源使用、使用方式对 `GC` 友好。 +::: +## 上下文- `Ctx` + +上下文由 `grpcx.Ctx` 模块维护,用于实现客户端与服务端之间、服务与服务之间的自定义数据传递。包含以下常用方法: + +```go +func (c Ctx) NewIncoming(ctx context.Context, data ...g.Map) context.Context +func (c Ctx) NewOutgoing(ctx context.Context, data ...g.Map) context.Context +func (c Ctx) IncomingToOutgoing(ctx context.Context, keys ...string) context.Context +func (c Ctx) IncomingMap(ctx context.Context) *gmap.Map +func (c Ctx) OutgoingMap(ctx context.Context) *gmap.Map +func (c Ctx) SetIncoming(ctx context.Context, data g.Map) context.Context +func (c Ctx) SetOutgoing(ctx context.Context, data g.Map) context.Context +``` + +其中的 `Outgoing` 用在客户端,表示将要传递给服务端的自定义数据,一般是由 `Key-Value` 键值对组成的 `Map` 数据格式; `Incoming` 使用在服务端,表示服务端接收到的客户端提交数据。其中框架相关的一些内嵌信息也写入到 `Incoming` 键值对中,例如,链路跟踪信息、客户端版本信息等。使用示例: + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`controller.go` + +```go +package controller + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf" + "github.com/gogf/gf/v2/frame/g" +) + +type Controller struct { + protobuf.UnimplementedGreeterServer +} + +func Register(s *grpcx.GrpcServer) { + protobuf.RegisterGreeterServer(s.Server, &Controller{}) +} + +// SayHello implements helloworld.GreeterServer +func (s *Controller) SayHello(ctx context.Context, in *protobuf.HelloRequest) (*protobuf.HelloReply, error) { + m := grpcx.Ctx.IncomingMap(ctx) + g.Log().Infof(ctx, `incoming data: %v`, m.Map()) + return &protobuf.HelloReply{Message: "Hello " + in.GetName()}, nil +} +``` + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/ctx/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ctx = grpcx.Ctx.NewOutgoing(gctx.New(), g.Map{ + "UserId": "1000", + "UserName": "john", + }) + ) + g.Log().Infof(ctx, `outgoing data: %v`, grpcx.Ctx.OutgoingMap(ctx).Map()) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +执行后,服务端输出: + +```bash +$ go run server.go +2023-03-15 19:28:45.331 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:28:45.331 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:51707 Metadata:map[protocol:grpc]} +2023-03-15 19:28:45.332 [INFO] pid[83753]: grpc server started listening on [:51707] +2023-03-15 19:28:54.093 [INFO] {d049db1a39944c1711bd9f37d58a88f9} incoming data: map[:authority:service/default/default/demo/latest/ content-type:application/grpc traceparent:00-d049db1a39944c1711bd9f37d58a88f9-adbd2ba657ffea45-01 user-agent:grpc-go/1.49.0 userid:1000 username:john] +2023-03-15 19:28:54.093 {d049db1a39944c1711bd9f37d58a88f9} /protobuf.Greeter/SayHello, 0.049ms, name:"World", message:"Hello World" +``` + +客户端输出: + +```bash +$ go run client.go +2023-03-15 19:28:54.087 [INFO] {d049db1a39944c1711bd9f37d58a88f9} outgoing data: map[userid:1000 username:john] +2023-03-15 19:28:54.089 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:51707","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:28:54.093 [DEBU] {d049db1a39944c1711bd9f37d58a88f9} Response: Hello World +``` + +## 负载均衡- `Balancer` + +负载均衡由 `grpcx.Balancer` 模块维护,用于实现当服务端存在多个访问地址时,客户端根据何种策略进行请求。当客户端未设置负载均衡策略时,默认使用轮训策略。关于负载均衡的详细介绍请参考章节: [服务负载均衡](服务负载均衡.md) + +我们这里使用 **随机策略** 来做示例,随机的策略将会使得各个服务端接收到的请求数比较随机。 + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/controller" +) + +func main() { + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +`client.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/balancer/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx context.Context + conn = grpcx.Client.MustNewGrpcClientConn("demo", grpcx.Balancer.WithRandom()) + client = protobuf.NewGreeterClient(conn) + ) + for i := 0; i < 10; i++ { + ctx = gctx.New() + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) + } +} +``` + +其中的 `grpcx.Balancer.WithRandom()` 表示使用随机的请求策略。 + +启动两个 `server.go` 服务端,随后运行 `client.go` 客户端,查看服务端的请求日志: + +`server1` 终端输出: + +```bash +$ go run server.go +2023-03-15 19:50:44.801 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:44.802 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53962 Metadata:map[protocol:grpc]} +2023-03-15 19:50:44.802 [INFO] pid[89290]: grpc server started listening on [:53962] +2023-03-15 19:50:57.282 {7025612f6d954c17c5f335051bf10899} /protobuf.Greeter/SayHello, 0.003ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {60567c2f6d954c17c7f335052ce05185} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.285 {f8d09b2f6d954c17ccf33505dff1a4ea} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.287 {f0fab02f6d954c17cdf33505438b2c80} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +`server2` 终端输出: + +```bash +$ go run server.go +2023-03-15 19:50:51.720 [DEBU] set default registry using file registry as no custom registry set +2023-03-15 19:50:51.721 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:53973 Metadata:map[protocol:grpc]} +2023-03-15 19:50:51.722 [INFO] pid[89351]: grpc server started listening on [:53973] +2023-03-15 19:50:57.280 {b89a0d2f6d954c17c4f33505a046817c} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.282 {28bf732f6d954c17c6f33505adedff5f} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.283 {9876832f6d954c17c8f3350580ed535b} /protobuf.Greeter/SayHello, 0.002ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {684e8b2f6d954c17c9f33505d56e4b05} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {c045912f6d954c17caf3350599006197} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +2023-03-15 19:50:57.284 {500a972f6d954c17cbf33505252b0e01} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +客户端终端输出: + +```bash +$ go run client.go +2023-03-15 19:50:57.278 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:53962","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null},{"Addr":"10.35.12.81:53973","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 19:50:57.281 [DEBU] {b89a0d2f6d954c17c4f33505a046817c} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {7025612f6d954c17c5f335051bf10899} Response: Hello World +2023-03-15 19:50:57.282 [DEBU] {28bf732f6d954c17c6f33505adedff5f} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {60567c2f6d954c17c7f335052ce05185} Response: Hello World +2023-03-15 19:50:57.283 [DEBU] {9876832f6d954c17c8f3350580ed535b} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {684e8b2f6d954c17c9f33505d56e4b05} Response: Hello World +2023-03-15 19:50:57.284 [DEBU] {c045912f6d954c17caf3350599006197} Response: Hello World +2023-03-15 19:50:57.285 [DEBU] {500a972f6d954c17cbf33505252b0e01} Response: Hello World +2023-03-15 19:50:57.286 [DEBU] {f8d09b2f6d954c17ccf33505dff1a4ea} Response: Hello World +2023-03-15 19:50:57.287 [DEBU] {f0fab02f6d954c17cdf33505438b2c80} Response: Hello World +``` + +可以看到,客户端发送了 `10` 次请求,两个服务端分别接收到了 `4` 次和 `6` 次请求,请求的负载比较随机。 + +## 注册发现- `Resolver` + +注册发现由 `grpcx.Resolver` 模块维护,该模块用于 `GRPC` 解析服务名称。 `grpcx` 组件默认情况下使用本地文件系统实现注册发现,仅用于测试使用。如果是生产环境,则需要使用其他的注册发现组件。一个简单示例: + +`server.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/resolver/controller" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + s := grpcx.Server.New() + controller.Register(s) + s.Run() +} +``` + +其中的 `grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) ` 用于设置服务注册发现的组件为 `etcd`,仅支持 `GRPC` 协议。 + +`client.go` + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/example/rpc/grpcx/resolver/protobuf" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ( + ctx = gctx.New() + conn = grpcx.Client.MustNewGrpcClientConn("demo") + client = protobuf.NewGreeterClient(conn) + ) + res, err := client.SayHello(ctx, &protobuf.HelloRequest{Name: "World"}) + if err != nil { + g.Log().Error(ctx, err) + return + } + g.Log().Debug(ctx, "Response:", res.Message) +} +``` + +启动 `etcd`: + +```bash +$ etcd +{"level":"info","ts":"2023-03-15T20:02:50.966+0800","caller":"etcdmain/etcd.go:73","msg":"Running: ","args":["etcd"]} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:100","msg":"failed to detect default host","error":"default host not supported on darwin_amd64"} +{"level":"warn","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:105","msg":"'data-dir' was empty; using default","data-dir":"default.etcd"} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"etcdmain/etcd.go:116","msg":"server has been already initialized","data-dir":"default.etcd","dir-type":"member"} +{"level":"info","ts":"2023-03-15T20:02:50.967+0800","caller":"embed/etcd.go:124","msg":"configuring peer listeners","listen-peer-urls":["http://localhost:2380"]} +{"level":"info","ts":"2023-03-15T20:02:50.968+0800","caller":"embed/etcd.go:132","msg":"configuring client listeners","listen-client-urls":["http://localhost:2379"]} +... +``` + +运行 `server.go`: + +```bash +$ go run server.go +2023-03-15 20:02:19.472 [DEBU] service register: &{Head: Deployment: Namespace: Name:demo Version: Endpoints:10.35.12.81:55066 Metadata:map[protocol:grpc]} +2023-03-15 20:02:19.516 [DEBU] etcd put success with key "/service/default/default/demo/latest/10.35.12.81:55066", value "{"protocol":"grpc"}", lease "7587869265929863945" +2023-03-15 20:02:19.516 [INFO] pid[92040]: grpc server started listening on [:55066] +2023-03-15 20:02:29.310 {88c4c04e0e964c17dddec53b1adb54f7} /protobuf.Greeter/SayHello, 0.001ms, name:"World", message:"Hello World" +``` + +运行 `client.go`: + +```bash +$ go run client.go +2023-03-15 20:02:29.297 [DEBU] Watch key "/service/default/default/demo/latest/" +2023-03-15 20:02:29.307 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 20:02:29.308 [DEBU] client conn updated with addresses [{"Addr":"10.35.12.81:55066","ServerName":"demo","Attributes":{},"BalancerAttributes":null,"Type":0,"Metadata":null}] +2023-03-15 20:02:29.310 [DEBU] {88c4c04e0e964c17dddec53b1adb54f7} Response: Hello World +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" new file mode 100644 index 00000000000..3543eaf59cf --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/obs' +title: '服务可观测性' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,服务可观测性,工程完备,开发框架,可观测特性,开发者,简便实现,技术文档,工程实践] +description: '服务可观测性是现代软件开发中的重要环节,使用GoFrame框架可以让开发者轻松实现这一特性。GoFrame提供了强大的工具和方法,帮助开发者提升服务的监控和管理能力,从而确保系统的稳定与可靠性。' +--- + + +作为一款工程完备的开发框架,开发者通过 `GoFrame` 可以非常简便地实现服务的可观测特性。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" new file mode 100644 index 00000000000..9a80efdc375 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246.md" @@ -0,0 +1,16 @@ +--- +slug: '/docs/obs/metrics' +title: '服务监控告警' +sidebar_position: 1 +hide_title: true +keywords: [服务监控,告警,监控告警,指标暴露,GoFrame,GoFrame框架,监控指标,开发者,v2.7版本,框架特性] +description: '使用GoFrame框架进行服务监控告警,从v2.7版本开始,开发者可快速实现监控指标及其暴露,便于对系统进行全面的监控和及时预警。' +--- + +监控告警特性从框架 `v2.7` 版本开始提供。 + +开发者通过 `GoFrame` 框架可以快速地实现监控指标及指标暴露。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..e54e335774e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\206\205\347\275\256\346\214\207\346\240\207.md" @@ -0,0 +1,95 @@ +--- +slug: '/docs/obs/metrics-builtin' +title: '监控告警-内置指标' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,内置指标,监控告警,指标类型,性能监控,Prometheus,OpenTelemetry,性能优化,Go基础指标] +description: 'GoFrame框架中监控告警内置指标的使用方法,涵盖了如何通过otelmetric开启Go基础指标,以及如何结合Prometheus和OpenTelemetry进行性能监控和优化。文中提供了示例代码和详细的指标说明,包括指标名称、指标类型以及描述,以帮助用户理解和实施性能监测。' +--- + +## 基本介绍 + +框架内置了 `Go` 基础指标,默认情况下是关闭的,需要手动开启,可以通过创建 `Provider` 时通过 `otelmetric.WithBuiltInMetrics()` 参数开启。 + +```go +package main + +import ( + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + otelmetric.WithBuiltInMetrics(), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +执行后,访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 查看结果。 + +![](/markdown/daf1d8449208ba307efd483c505b7b5a.png) + +## 内置指标说明 + +| **指标名称** | **指标类型** | **指标单位** | **指标描述** | +| --- | --- | --- | --- | +| `process.runtime.go.cgo.calls` | `Counter` | | 当前进程调用的 `cgo` 次数。 | +| `process.runtime.go.gc.count` | `Counter` | | 已完成的 `gc` 周期的次数。 | +| `process.runtime.go.gc.pause_ns` | `Histogram` | `ns` | 在 `GC stop-the-world ` 中暂停的纳秒数量。 | +| `process.runtime.go.gc.pause_total_ns` | `Counter` | `ns` | 自程序启动以来, `GC stop-the-world ` 的累计微秒计数。 | +| `process.runtime.go.goroutines` | `Gauge` | | 当前运行的协程数量。 | +| `process.runtime.go.lookups` | `Counter` | | 运行时执行的指针查询的数量。 | +| `process.runtime.go.mem.heap_alloc` | `Gauge` | `bytes` | 分配的堆对象的字节数。 | +| `process.runtime.go.mem.heap_idle` | `Gauge` | `bytes` | 空闲(未使用)的堆内存。 | +| `process.runtime.go.mem.heap_inuse` | `Gauge` | `bytes` | 已使用的堆内存。 | +| `process.runtime.go.mem.heap_objects` | `Gauge` | | 已分配的堆对象数量。 | +| `process.runtime.go.mem.live_objects` | `Gauge` | | 存活对象数量( `Mallocs - Frees`) | +| `process.runtime.go.mem.heap_released` | `Gauge` | `bytes` | 已交还给操作系统的堆内存。 | +| `process.runtime.go.mem.heap_sys` | `Gauge` | `bytes` | 从操作系统获得的堆内存。 | +| `process.runtime.uptime` | `Counter` | `ms` | 自应用程序被初始化以来的毫秒数。 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..b0cb710d418 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\220\214\346\255\245\346\214\207\346\240\207.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/obs/metrics-sync' +title: '监控告警-同步指标' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,监控,同步指标,Counter,UpDownCounter,Histogram,Prometheus,OpenTelemetry,指标导出,性能监控] +description: '在GoFrame框架中使用同步指标,通过gmetric提供的Counter、UpDownCounter和Histogram等类型快速暴露和记录HTTP请求的相关数据。利用Prometheus协议实现指标输出,以供外部监控工具抓取和分析,达到有效的性能监控和管理。' +--- + +## 基本介绍 + +**同步类型** 用于快速暴露监控指标,无论 `metrics reader` 是否使用该监控指标,指标的计算结果已完成,待读取使用。例如,HTTP的请求总数、请求大小,这些数值必须在请求执行流程中记录到对应的监控指标数值中,适合作为同步指标来管理。 + +`gmetric` 提供的同步类型指标包含: `Counter, UpDownCounter, Histogram`。 + +我们用一个简单的示例来演示同步指标的基本使用。 + +```go +package main + +import ( + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) + upDownCounter = meter.MustUpDownCounter( + "goframe.metric.demo.updown_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for UpDownCounter usage", + Unit: "%", + }, + ) + histogram = meter.MustHistogram( + "goframe.metric.demo.histogram", + gmetric.MetricOption{ + Help: "This is a simple demo for histogram usage", + Unit: "ms", + Buckets: []float64{0, 10, 20, 50, 100, 500, 1000, 2000, 5000, 10000}, + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider(otelmetric.WithReader(exporter)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // UpDownCounter. + upDownCounter.Inc(ctx) + upDownCounter.Add(ctx, 10) + upDownCounter.Dec(ctx) + + // Record values for histogram. + histogram.Record(1) + histogram.Record(20) + histogram.Record(30) + histogram.Record(101) + histogram.Record(2000) + histogram.Record(9000) + histogram.Record(20000) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +## Counter&UpDownCounter + +其中的 `Counter` 和 `UpDownCounter` 比较简单,这里就不详细介绍了,需要注意的是 `Counter` 和 `UpDownCounter` 虽然看起来差不多,实际上也是如此,只是为了更严谨和精细化区分使用场景。如果将这两种数据类型映射到经典的 `Prometheus` 指标类型中, `Counter` 对应的就是 `Prometheus` 的 `Counter` 指标类型,而 `UpDownCounter` 对应的是 `Prometheus` 的 `Gauge` 指标类型。 + +## Histogram + +`Histogram` 是一种统计类型,通过该指标可以快速统计出指标的 `p95, p99` 等百分位统计结果直方图,例如时间开销、成功/失败率等指标。但需要注意该指标比较占内存和空间,不能为其添加过多的 **动态属性**,因为不同的属性会衍生出同一种 `Histogram` 指标不同的存储数值。 + +## Primetheus Exporter + +在该示例中,我们使用了比较常用的 `Prometheus` 协议输出指标内容,通常用于对外暴露指标供外部组件抓取。通过以下路由将指标通过 `Prometheus` 协议暴露: + +``` +otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +``` + +执行后,访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 查看暴露的指标: + +![](/markdown/50c5c45e521aa19633873aa9f9186ac3.png) + +我们这里只关注本次示例中的指标,其他自动暴露的指标在后续章节介绍。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..64a94465dce --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\273\213\347\273\215.md" @@ -0,0 +1,124 @@ +--- +slug: '/docs/obs/metrics-intro' +title: '监控告警-基本介绍' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,监控告警,OpenTelemetry,指标类型,插件接口化,Metrics标准,抽象解耦设计,同步指标,异步指标] +description: 'GoFrame框架中监控告警的基础知识,重点介绍了OpenTelemetry关于监控和告警设计的相关规范和组件,涵盖了Meter Provider、Meter、Instrument等组件与数据流关系。文中还阐述了框架如何通过gmetric组件实现可观测性,采用抽象解耦设计,支持同步和异步多种指标类型,帮助开发者灵活处理和扩展监控功能。' +--- + +在介绍框架的监控告警之前,我们离不开业内可观测性的标准介绍, `OpenTelemetry` 关于监控告警这块的设计,以及相关规范。 `OpenTelemetry` 在这块的内容也比较多,我们选择一些重点来介绍一下。 + +## OpenTelemetry + +### 相关组件 + +我们来看一张 `OpenTelemetry` 组件的关系图,在 `OpenTelemetry Metrics` 标准的实现中,主要包含以下组件。 + +![](/markdown/f7c048b9050aa8d6c17f85e1dc1c0540.png) + +在标准化文档的落地时,各个组件通常都是采用接口化设计,以提高可扩展性: + +#### Meter Provider + +用于接口化管理全局的 `Meter` 创建,相当于全局的监控指标管理工厂。 + +#### Meter + +用于接口化创建并管理全局的 `Instrument`,不同的 `Meter` 可以看做是不同的程序组件。例如框架中的各个组件可以看做是不同的 `Meter`,比如 `gclient` 和 `ghttp` 就是两个不同的 `Meter`。 + +#### Instrument + +用于管理不同组件下的各个不同类型的指标,例如在 `ghttp` 下,就会有 `http.server.request.duration`、 `http.server.request.body_size` 等指标。 + +#### Measurements + +对应指标上报的具体的 `DataPoint` 指标数据,是一系列的数值项。 + +#### View + +实现对 `Measurements` 的计算、汇总、过滤、修改等操作,通常指标都是 **数值类型**,所以这个通常都使用默认的 `View` 即可。 + +#### Metric Reader + +用于实现对指标的数据流读取,内部定义了具体操作指标的数据结构。 `OpenTelemetry` 官方社区提供了多种灵活的 `Reader` 实现,例如 `PeridRader`、 `ManualReader` 等。 + +#### Metric Exporter + +`Exporter` 用于暴露本地指标到对应的第三方厂商,定义了具体是 `push` 还是 `pull` 的数据传输方式。 `Exporter` 需要用到 `Reader`,其中 `Reader` 只有几种方式,但是 `Exporter` 会根据厂商而定,会有很多,例如: `promtheus`、 `zipkin` 等。 + +一个 `Instrument` 多个 `DataPoint` 的数据流如下图所示: + +![](/markdown/5d476c4969a41f996ac8c39a6d841f81.png) + +### 相关类型 + +`OpenTelemetry` 的社区实现为了满足不同的使用场景,因此类型的设计粒度比较细,分为 `int64` 和 `float64` 数据类型,并且包含 **同步** 和 **异步** 指标类型。 + +#### 同步类型 + +**同步类型** 用于快速暴露监控指标,无论 `metrics reader` 是否使用该监控指标,指标的计算结果已完成,待读取使用。例如,HTTP的请求总数、请求大小,这些数值必须在请求执行流程中记录到对应的监控指标数值中,适合作为同步指标来管理。 + +| **类型** | **描述** | **示例** | +| --- | --- | --- | +| `Int64Counter` | `int64` 只增不减的指标。 | 请求总数、请求字节总大小 | +| `Int64UpDownCounter` | `int64` 可增可减的指标。 | 当前活跃请求、执行队列大小 | +| `Float64Counter` | `float64` 只增不减的指标。 | 请求总数、请求字节总大小 | +| `Float64UpDownCounter` | `float64` 可增可减的指标。 | 请求总数、请求字节总大小 | +| `Int64Histogram` | `int64` 可分组的指标 | 请求执行时间 `p99` | +| `Float64Histogram` | `float64` 可分组的指标 | 请求执行时间 `p99` | + +#### 异步类型 + +**异步类型** 的监控指标只有在 `metrics reader` 开始使用该监控指标时才会执行指标计算逻辑。异步类型的监控指标需要设置一个回调函数,该回调函数用于生成指标数值,并在 `metrics reader` 读取指标时才会触发回调函数。例如,机器CPU、内存、磁盘使用量的指标,如果没有目标端拉取或者使用该指标时,提前计算指标值毫无意义且浪费计算资源,适合作为异步指标来管理。 + +| **类型** | **描述** | **示例** | +| --- | --- | --- | +| `Int64ObservableCouter` | `int64` 只增不减的指标。 | CPU、内存、磁盘使用量 | +| `Int64ObservableUpDownCounter` | `int64` 可增可减的指标。 | CPU、内存、磁盘使用量 | +| `Float64ObservableCouter` | `float64` 只增不减的指标。 | CPU、内存、磁盘使用量 | +| `Float64ObservableUpDownCounter` | `float64` 可增可减的指标。 | 当前活跃请求、执行队列大小 | +| `Int64ObservableGauge` | `int64` 可增可减的指标。 | CPU、内存、磁盘使用量 | +| `Float64ObservableGauge` | `float64` 可增可减动态设置的指标。 | CPU、内存、磁盘使用量 | + +## 框架监控组件 + +### 组件抽象 + +框架通过 `gmetric` 组件来实现监控能力, `gmetric` 组件内部设计的组件层级关系同 `OpenTelemetry Metrics` 类似: + +![](/markdown/a1f33528941fcf91e87b87aa8c0219cd.png) + +`gmetric` 组件使用了 **抽象解耦设计**,一方面是因为框架设计需要减少外部依赖;另一方面是为了实现监控的自动开关能力。组件默认情况下使用了 `NoopPerform` 的实现对象,在默认情况下监控能力是关闭的,只有在真正引入监控接口实现时才会自动开启监控能力。 + +![](/markdown/99374a3d9b7e4805c5c7c0bd3fefb221.png) + +### 指标类型 + +框架提供的指标类型相比较 `OpenTelmetry` 社区实现去掉了 `int64` 数值类型,而是使用了统一的 `float64` 数值类型以简化使用。但同时需要注意,开发者在设计指标数值时 **尽可能避免小数设计,以避免数值计算的精度问题**。特别是在 `Histogram` 类型的 `Buckets` 设计时,不建议使用小数。 + +#### 同步类型 + +| **类型** | **描述** | **示例** | +| --- | --- | --- | +| `Counter` | `float64` 只增不减的指标。 | 请求总数、请求字节总大小 | +| `UpDownCounter` | `float64` 可增可减的指标。 | 请求总数、请求字节总大小 | +| `Histogram` | `float64` 可分组的指标 | 请求执行时间 `p99` | + +#### 异步类型 + +| **类型** | **描述** | **示例** | +| --- | --- | --- | +| `ObservableCounter` | `float64` 只增不减的指标。 | 请求总数、请求字节总大小 | +| `ObservableUpDownCounter` | `float64` 可增可减的指标。 | 请求总数、请求字节总大小 | +| `ObservableGauge` | `float64` 可增可减动态设置的指标。 | CPU、内存、磁盘使用量 | + +## 参考资料 + +- [https://github.com/prometheus/client\_golang](https://github.com/prometheus/client_golang%20"https://github.com/prometheus/client_golang") + +- [https://github.com/open-telemetry/opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib%20"https://github.com/open-telemetry/opentelemetry-go-contrib") + +- [https://opentelemetry.io/docs/specs/otel/metrics/api/](https://opentelemetry.io/docs/specs/otel/metrics/api/%20"https://opentelemetry.io/docs/specs/otel/metrics/api/") + +- [https://opentelemetry.io/docs/specs/otel/metrics/data-model](https://opentelemetry.io/docs/specs/otel/metrics/data-model%20"https://opentelemetry.io/docs/specs/otel/metrics/data-model") \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..b2d32d2df2c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/obs/metrics-example' +title: '监控告警-基本使用' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,监控指标,otelmetric,OpenTelemetry,指标管理,Counter,MetricOption,gmetric,数据读取,指标实现] +description: '在GoFrame框架中使用gmetric组件进行监控指标的开发。通过引入otelmetric组件,可以利用OpenTelemetry实现框架的监控指标接口。文章详细描述了指标管理对象的创建、各种监控指标对象的使用及初始化方法,并通过代码示例展示了如何读取和操作指标数据。' +--- + +## 基本介绍 + +监控指标的代码开发直接使用框架主库的 `gmetric` 组件即可,但由于 `gmetric` 组件实际上只是定义了监控指标的相关接口,并且默认提供的 `NoopPerformer`,默认监控指标特性是关闭的。因此需要引入具体的接口实现组件才能真正开启监控指标特性。框架社区提供了社区组件 `github.com/gogf/gf/contrib/metric/otelmetric/v2`,使用了 `OpenTelemetry` 实现框架的监控指标接口,引入该社区组件并且执行监控指标管理对象创建即可开启监控指标特性。 `otelmetric` 组件源码地址: [https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric](https://github.com/gogf/gf/tree/master/contrib/metric/otelmetric) + +我们通过一个简单的监控指标实现示例来介绍一下监控指标组件的基本使用。 + +```go +package main + +import ( + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + }, + ) +) + +func main() { + var ( + ctx = gctx.New() + reader = metric.NewManualReader() + ) + + provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + counter.Inc(ctx) + counter.Add(ctx, 10) + + var ( + rm = metricdata.ResourceMetrics{} + err = reader.Collect(ctx, &rm) + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + g.DumpJson(rm) +} +``` + +## 指标管理组件的创建 + +通过 `gmetric.GetGlobalProvider()` 方法可以获取全局的监控 **指标管理对象**, **该对象是一个单例设计,全局只能有一个**。并通过该对象的 `Meter` 方法可以创建/获取对应的 **组件对象**。组件对象用于管理该组件下所有的监控指标。在创建组件对象时通常需要定义组件( `Instrument`)的名称以及版本(虽然也可以为空,但为了方便后续维护建议都设置上)。 + +``` +meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", +}) +``` + +其中的 `gmeter.MeterOption` 数据结构如下: + +```go +// MeterOption holds the creation option for a Meter. +type MeterOption struct { + // Instrument is the instrumentation name to bind this Metric to a global MeterProvider. + // This is an optional configuration for a metric. + Instrument string + + // InstrumentVersion is the instrumentation version to bind this Metric to a global MeterProvider. + // This is an optional configuration for a metric. + InstrumentVersion string + + // Attributes holds the constant key-value pair description metadata for all metrics of Meter. + // This is an optional configuration for a meter. + Attributes Attributes +} +``` + +## 监控指标对象的创建 + +通过 `Meter` 接口对象,我们可以在该组件下创建对应的各种指标。指标有各种数据类型,因此 `Meter` 接口的定义如下: + +```go +// Meter hold the functions for kinds of Metric creating. +type Meter interface { + // Counter creates and returns a new Counter. + Counter(name string, option MetricOption) (Counter, error) + + // UpDownCounter creates and returns a new UpDownCounter. + UpDownCounter(name string, option MetricOption) (UpDownCounter, error) + + // Histogram creates and returns a new Histogram. + Histogram(name string, option MetricOption) (Histogram, error) + + // ObservableCounter creates and returns a new ObservableCounter. + ObservableCounter(name string, option MetricOption) (ObservableCounter, error) + + // ObservableUpDownCounter creates and returns a new ObservableUpDownCounter. + ObservableUpDownCounter(name string, option MetricOption) (ObservableUpDownCounter, error) + + // ObservableGauge creates and returns a new ObservableGauge. + ObservableGauge(name string, option MetricOption) (ObservableGauge, error) + + // MustCounter creates and returns a new Counter. + // It panics if any error occurs. + MustCounter(name string, option MetricOption) Counter + + // MustUpDownCounter creates and returns a new UpDownCounter. + // It panics if any error occurs. + MustUpDownCounter(name string, option MetricOption) UpDownCounter + + // MustHistogram creates and returns a new Histogram. + // It panics if any error occurs. + MustHistogram(name string, option MetricOption) Histogram + + // MustObservableCounter creates and returns a new ObservableCounter. + // It panics if any error occurs. + MustObservableCounter(name string, option MetricOption) ObservableCounter + + // MustObservableUpDownCounter creates and returns a new ObservableUpDownCounter. + // It panics if any error occurs. + MustObservableUpDownCounter(name string, option MetricOption) ObservableUpDownCounter + + // MustObservableGauge creates and returns a new ObservableGauge. + // It panics if any error occurs. + MustObservableGauge(name string, option MetricOption) ObservableGauge + + // RegisterCallback registers callback on certain metrics. + // A callback is bound to certain component and version, it is called when the associated metrics are read. + // Multiple callbacks on the same component and version will be called by their registered sequence. + RegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric) error + + // MustRegisterCallback performs as RegisterCallback, but it panics if any error occurs. + MustRegisterCallback(callback Callback, canBeCallbackMetrics ...ObservableMetric) +} +``` + +以本示例代码中使用的 `meter.MustCounter` 方法来介绍,该方法是创建一个 `Counter` 同步指标,同时由于我们偷懒这个使用了 `Must*` 方法,也就是说如果创建指标失败,那么这个方法会 `panic` 报错。在创建指标对象时,指标名称 `name` 是必须参数,另外的 `MetricOption` 是可选项参数,用于进一步描述指标信息。 `MetricOption` 的数据结构定义如下: + +```go +// MetricOption holds the basic options for creating a metric. +type MetricOption struct { + // Help provides information about this Histogram. + // This is an optional configuration for a metric. + Help string + + // Unit is the unit for metric value. + // This is an optional configuration for a metric. + Unit string + + // Attributes holds the constant key-value pair description metadata for this metric. + // This is an optional configuration for a metric. + Attributes Attributes + + // Buckets defines the buckets into which observations are counted. + // For Histogram metric only. + // A histogram metric uses default buckets if no explicit buckets configured. + Buckets []float64 + + // Callback function for metric, which is called when metric value changes. + // For observable metric only. + // If an observable metric has either Callback attribute nor global callback configured, it does nothing. + Callback MetricCallback +} +``` + +## 初始化监控指标实现 + +通过 `otelmetric.MustProvider` 创建方法即可创建并初始化监控指标管理对象。 + +```go +provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) +provider.SetAsGlobal() +defer provider.Shutdown(ctx) +``` + +前面我们有介绍到, `GlobalProvider` 其实是一个单例的指标管理对象,因此这里通过 `provider.SetAsGlobal` 方法调用可以将该对象设置为全局的指标管理对象,便于后续的指标创建均基于该对象创建。 + +我们在 `main` 函数中通过 `defer provider.ShutDown` 方法调用便于在程序结束时优雅结束指标管理对象,例如对象中的指标缓存及时输出到目标端。 + +## 监控指标对象的使用 + +不同的指标对象有不同的操作方法用于实现指标数值的变化。以示例中的 `Counter` 指标类型为例,其接口定义如下: + +```go +// Counter is a Metric that represents a single numerical value that can ever +// goes up. +type Counter interface { + Metric + CounterPerformer +} + +// CounterPerformer performs operations for Counter metric. +type CounterPerformer interface { + // Inc increments the counter by 1. Use Add to increment it by arbitrary + // non-negative values. + Inc(ctx context.Context, option ...Option) + + // Add adds the given value to the counter. It panics if the value is < 0. + Add(ctx context.Context, increment float64, option ...Option) +} +``` + +可以看到, `Counter` 指标主要可以执行 `Inc` 及 `Add` 两个操作方法。其中还有一个 `Metric` 的接口,所有的指标均实现了该接口用于获取当前指标的基础信息,其接口定义如下: + +```go +// Metric models a single sample value with its metadata being exported. +type Metric interface { + // Info returns the basic information of a Metric. + Info() MetricInfo +} + +// MetricInfo exports information of the Metric. +type MetricInfo interface { + Key() string // Key returns the unique string key of the metric. + Name() string // Name returns the name of the metric. + Help() string // Help returns the help description of the metric. + Unit() string // Unit returns the unit name of the metric. + Type() MetricType // Type returns the type of the metric. + Attributes() Attributes // Attributes returns the constant attribute slice of the metric. + Instrument() InstrumentInfo // InstrumentInfo returns the instrument info of the metric. +} + +// InstrumentInfo exports the instrument information of a metric. +type InstrumentInfo interface { + Name() string // Name returns the instrument name of the metric. + Version() string // Version returns the instrument version of the metric. +} +``` + +## 监控指标的数据读取 + +通过上一章节介绍的 `OpenTelemetry` 组件关系我们知道,如果想要使用指标必须要通过 `MetricReader`,因此在该示例代码中我们通过 `OpenTelemetry` 官方社区开源组件中最常用的 `ManualReader` 来简单实现指标数据读取, `ManualReader` 是 `OpenTelemetry` 官方社区提供的实现。 + +``` +reader = metric.NewManualReader() +``` + +并通过 `WithReader` 方法配置到 `Provider` 中: + +```go +provider := otelmetric.MustProvider(otelmetric.WithReader(reader)) +``` + +随后通过 `Collect` 方法可以获取当前所有的指标数据: + +```go +var ( + rm = metricdata.ResourceMetrics{} + err = reader.Collect(ctx, &rm) +) +if err != nil { + g.Log().Fatal(ctx, err) +} +g.DumpJson(rm) +``` + +执行后,终端输出: + +``` +... + "ScopeMetrics": [ + { + "Scope": { + "Name": "github.com/gogf/gf/example/metric/basic", + "Version": "v1.0", + "SchemaURL": "" + }, + "Metrics": [ + { + "Name": "goframe.metric.demo.counter", + "Description": "This is a simple demo for Counter usage", + "Unit": "bytes", + "Data": { + "DataPoints": [ + { + "Attributes": [], + "StartTime": "2024-03-25T10:13:19.326977+08:00", + "Time": "2024-03-25T10:13:19.327144+08:00", + "Value": 11 + } + ], + "Temporality": "CumulativeTemporality", + "IsMonotonic": true + } + } + ] + }, +... +``` + +为了简化示例介绍,我们在这里省略了一些输出内容,更详细的指标及输出介绍请参考后续章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..2c84e4ed496 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\345\274\202\346\255\245\346\214\207\346\240\207.md" @@ -0,0 +1,128 @@ +--- +slug: '/docs/obs/metrics-async' +title: '监控告警-异步指标' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,异步指标,监控告警,ObservableCounter,ObservableUpDownCounter,ObservableGauge,Callback函数,Prometheus导出,OpenTelemetry] +description: '在GoFrame框架下使用异步监控指标,详细说明了ObservableCounter、ObservableUpDownCounter和ObservableGauge三种异步指标的使用方法。通过定义Callback函数管理指标数值变化,并利用Prometheus导出指标数据。' +--- + +## 基本介绍 + +**异步类型** 的监控指标只有在 `metrics reader` 开始使用该监控指标时才会执行指标计算逻辑。异步类型的监控指标需要设置一个回调函数,该回调函数用于生成指标数值,并在 `metrics reader` 读取指标时才会触发回调函数。例如,机器CPU、内存、磁盘使用量的指标,如果没有目标端拉取或者使用该指标时,提前计算指标值毫无意义且浪费计算资源,适合作为异步指标来管理。 + +`gmetric` 提供的异步类型指标包含: `ObservableCounter, ObservableUpDownCounter, OvservableGauge`。异步指标类型均是使用 `Observable` 开头命名,三种异步指标的操作都差不多,均是在不同的业务场景下有不同的使用差异。 + +我们用一个简单的示例来演示异步指标的基本使用。 + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: "github.com/gogf/gf/example/metric/basic", + InstrumentVersion: "v1.0", + }) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + }, + ) + observableUpDownCounter = meter.MustObservableUpDownCounter( + "goframe.metric.demo.observable_updown_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableUpDownCounter usage", + Unit: "%", + }, + ) + observableGauge = meter.MustObservableGauge( + "goframe.metric.demo.observable_gauge", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableGauge usage", + Unit: "%", + }, + ) +) + +func main() { + var ctx = gctx.New() + + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) + obs.Observe(observableUpDownCounter, 20) + obs.Observe(observableGauge, 30) + return nil + }, observableCounter, observableUpDownCounter, observableGauge) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider(otelmetric.WithReader(exporter)) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} + +``` + +## Meter Callback + +异步指标需要定义 `Callback` 函数来管理指标数值变化,只有在请求或使用该指标时才会执行该 `Callback` 函数。 `Callback` 函数中使用 `Observe` 函数来更新指标的数值,针对不同异步指标类型的 `Observe` 会产生不同的结果。 + +- 针对 `ObservableCounter/ObservableUpDownCounter` 指标类型,使用 `Observe` 函数后将会在原有指标数值上进行增减。 +- 针对 `ObservableGauge` 指标类型,使用 `Observe` 函数后,该指标的值更会更新为 `Observe` 给定的数值。 + +## Metric Callback + +除了通过 `Meter CallBack` 来实现异步指标的数值更新,也可以在创建指标时通过 `MetricOption` 指定 `Callback` 函数。例如: + +``` +observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Callback: func(ctx context.Context, obs gmetric.MetricObserver) error { + obs.Observe(10) + return nil + }, + }, +) +``` + +## Primetheus Exporter + +通过以下路由将指标通过 `Prometheus` 协议暴露: + +``` +otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +``` + +执行后,访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 查看暴露的指标: + +![](/markdown/5e79d0fe7ae3773ee055a5d600abe7dd.png) + +我们这里只关注本次示例中的指标,其他自动暴露的指标在后续章节介绍。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" new file mode 100644 index 00000000000..580f355fccd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\346\214\207\346\240\207\345\261\236\346\200\247.md" @@ -0,0 +1,325 @@ +--- +slug: '/docs/obs/metrics-attributes' +title: '监控告警-指标属性' +sidebar_position: 4 +hide_title: true +keywords: [监控告警,指标属性,GoFrame,常量属性,变量属性,全局属性,OpenTelemetry,Prometheus,Meter,指标注入] +description: '在GoFrame框架中的监控告警组件中使用指标属性进行过滤、汇总和统计。提供了常量属性、变量属性和全局属性三种属性注入方式,并通过具体示例演示了如何在不同场景下应用这些属性。结合OpenTelemetry和Prometheus,展示了如何定义和应用指标属性,以实现灵活高效的数据监控和分析。' +--- + +指标属性用于在更上层的指标使用中进行过滤、汇总、统计等高纬度操作。在 `GoFrame` 框架的监控告警组件中,提供了3种属性注入方式: **常量属性**、 **变量属性** 和 **全局属性**。 +:::tip +在 `OpenTelemetry` 中叫做指标属性( `attributes`),但在 `Prometheus` 中叫做指标标签( `labels`),两者是一个含义。 +::: +## 常量属性 + +**常量属性** 是固定的一系列属性键值对,可以绑定到 `Meter` 中,也可以直接绑定到指标对象上。如果绑定到 `Meter` 上,那么 `Meter` 下所有创建的指标对象均带有该属性键值对,如果绑定到指标对象上,那么仅会在当前指标上生效。我们来看一个示例: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx) + counter.Add(ctx, 10) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +可以看到,我们通过创建 `Meter` 对象或者 `Metric` 对象时的 `MeterOption` 或 `MetricOption` 参数中的 `Attributes` 属性字段来指定常量属性。 + +执行后,我们访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 看看结果。可以看到绑定到 `Meter` 上的常量属性在两个指标上生效了,但是在各个指标上绑定的常量属性仅在对应的指标上生效了。 + +![](/markdown/7604946c482b5592bf13db15e99486f5.png) + +## 变量属性 + +**变量属性** 是在指标运行时指定的属性键值对,通常只能在运行时才能确定属性的键值对信息,并且键值对可能根据不同的执行场景而发生变化,因此叫做变量属性。 + +我们来看一个示例: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_1", 1), + }, + }) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_2", 2), + }, + }) + counter.Add(ctx, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_3", 3), + }, + }) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +可以看到,我们在运行通过 `Option` 参数中的 `Attributes` 属性字段来指定指标变量属性,变量属性比较灵活,相同的指标也可以使用不同的变量属性。 + +同样的,执行后,我们访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 看看结果。 + +![](/markdown/17cd106aa40f6ca397486301bdaf16cd.png) + +## 全局属性 + +**全局属性** 是一种更加灵活的指标属性注入方式,可以根据 `Instrument` 信息进行属性自动注入,并且可以根据 `Instrument` 名称的正则匹配来判断是否往该 `Instrument` 下所有的指标注入指标属性信息。 + +我们来看一个示例: + +```go +package main + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/prometheus" + + "github.com/gogf/gf/contrib/metric/otelmetric/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gmetric" +) + +const ( + instrument = "github.com/gogf/gf/example/metric/basic" + instrumentVersion = "v1.0" +) + +var ( + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("meter_const_attr_1", 1), + }, + }) + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "This is a simple demo for Counter usage", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_1", 1), + }, + }, + ) + observableCounter = meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "This is a simple demo for ObservableCounter usage", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("metric_const_attr_2", 2), + }, + }, + ) +) + +func main() { + var ctx = gctx.New() + + gmetric.SetGlobalAttributes(gmetric.Attributes{ + gmetric.NewAttribute("global_attr_1", 1), + }, gmetric.SetGlobalAttributesOption{ + Instrument: instrument, + InstrumentVersion: instrumentVersion, + InstrumentPattern: "", + }) + + // Callback for observable metrics. + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_1", 1), + }, + }) + return nil + }, observableCounter) + + // Prometheus exporter to export metrics as Prometheus format. + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), + prometheus.WithoutUnits(), + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // OpenTelemetry provider. + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), + ) + provider.SetAsGlobal() + defer provider.Shutdown(ctx) + + // Counter. + counter.Inc(ctx, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_2", 2), + }, + }) + counter.Add(ctx, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_3", 3), + }, + }) + + // HTTP Server for metrics exporting. + otelmetric.StartPrometheusMetricsServer(8000, "/metrics") +} +``` + +通过 `gmetric.SetGlobalAttributes` 方法设置全局属性,并且根据参数 `gmetric.SetGlobalAttributesOption` 限制影响的指标范围。 + +同样的,执行后,我们访问 [http://127.0.0.1:8000/metrics](http://127.0.0.1:8000/metrics) 看看结果。可以看到,全局属性已经自动添加到了指标中。 + +![](/markdown/dfc79773cb999c35208fe27c98e1ab48.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" new file mode 100644 index 00000000000..b3888a0e371 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\347\233\221\346\216\247\345\221\212\350\255\246/\347\233\221\346\216\247\345\221\212\350\255\246-\347\273\204\344\273\266\346\214\207\346\240\207.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/obs/metrics-components' +title: '监控告警-组件指标' +sidebar_position: 6 +hide_title: true +keywords: [监控告警,组件指标,HTTP Client,HTTP Server,监控指标,GoFrame,GoFrame框架,WEB服务开发,高级特性,性能监测] +description: '已支持监控指标的GoFrame框架组件,包括HTTP Client和HTTP Server。读者可以通过文档链接获取更详细的监控指标信息。其他组件指标将在后续版本中陆续提供,保证对系统性能的全面监测。' +--- + +已支持监控指标的的框架组件,其他组件指标在后续版本中陆续提供。 + +| 组件 | 文档地址 | +| --- | --- | +| `HTTP Client` | [HTTPClient-监控指标](../../WEB服务开发/HTTPClient/HTTPClient-监控指标.md) | +| `HTTP Server` | [HTTPServer-监控指标](../../WEB服务开发/高级特性/HTTPServer-监控指标.md) | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" new file mode 100644 index 00000000000..f4b93c2ceaf Binary files /dev/null and "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/8fbc65f937aaac8c9b6947faa89a6964.png" differ diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..bb2c36afd23 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/obs/tracing' +title: '服务链路跟踪' +sidebar_position: 0 +hide_title: true +keywords: [服务链路跟踪,分布式链路跟踪,GoFrame,GoFrame框架,链路跟踪实现,标准化链路跟踪,分布式系统,跟踪特性,服务跟踪,性能监测] +description: 'GoFrame框架支持服务链路跟踪,通过实现标准化的分布式链路跟踪,帮助开发者监控和分析服务之间的交互过程。利用此功能可以有效提高系统的性能和可靠性,帮助定位问题源头,优化服务架构,保证系统在复杂环境中的高效运行。' +--- + + +`GoFrame` 实现了标准化的分布式链路跟踪( `Distributed Tracing`)特性。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..32347492f0c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-GRPC\347\244\272\344\276\213.md" @@ -0,0 +1,324 @@ +--- +slug: '/docs/obs/tracing-grpc-example' +title: '链路跟踪-GRPC示例' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GRPC,链路跟踪,微服务,客户端,服务端,缓存适配器,数据库,Jaeger,protobuf] +description: '使用GoFrame框架开发一个简单的GRPC服务端和客户端,并为GRPC微服务增加链路跟踪特性。示例代码说明了如何初始化Jaeger,用Redis适配器实现缓存,以及在客户端与服务端间进行链路信息传递。' +--- + +在本章节中,我们将之前介绍 `HTTP Client&Server` 的示例修改为 `GRPC` 微服务,并演示如何使用 `GoFrame` 框架开发一个简单的 `GRPC` 服务端和客户端,并且为 `GRPC` 微服务增加链路跟踪特性。 + +本章节的示例代码位于: [https://github.com/gogf/gf/tree/master/example/trace/grpc\_with\_db](https://github.com/gogf/gf/tree/master/example/trace/grpc_with_db) + +## 目录结构 + +![](/markdown/e9fe7410038348854e83de6cb3e35e32.png) + +## Protobuf + +``` +syntax = "proto3"; + +package user; + +option go_package = "protobuf/user"; + +// User service for tracing demo. +service User { + rpc Insert(InsertReq) returns (InsertRes) {} + rpc Query(QueryReq) returns (QueryRes) {} + rpc Delete(DeleteReq) returns (DeleteRes) {} +} + +message InsertReq { + string Name = 1; // v: required#Please input user name. +} +message InsertRes { + int32 Id = 1; +} + +message QueryReq { + int32 Id = 1; // v: min:1#User id is required for querying. +} +message QueryRes { + int32 Id = 1; + string Name = 2; +} + +message DeleteReq { + int32 Id = 1; // v:min:1#User id is required for deleting. +} +message DeleteRes {} +``` + +使用 `gf gen pb` 命令编译该 `proto` 文件,将会生成对应的 `grpc` 接口文件和数据结构文件。 + +## GRPC Server + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" + + "context" + "fmt" + "time" + + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/contrib/trace/otlpgrpc/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +type Controller struct { + user.UnimplementedUserServer +} + +const ( + serviceName = "otlp-grpc-server" + endpoint = "tracing-analysis-dc-bj.aliyuncs.com:8090" + traceToken = "******_******" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ctx = gctx.New() + shutdown, err := otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + // Set ORM cache adapter with redis. + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + + s := grpcx.Server.New() + user.RegisterUserServer(s.Server, &Controller{}) + s.Run() +} + +// Insert is a route handler for inserting user info into database. +func (s *Controller) Insert(ctx context.Context, req *user.InsertReq) (res *user.InsertRes, err error) { + result, err := g.Model("user").Ctx(ctx).Insert(g.Map{ + "name": req.Name, + }) + if err != nil { + return nil, err + } + id, _ := result.LastInsertId() + res = &user.InsertRes{ + Id: int32(id), + } + return +} + +// Query is a route handler for querying user info. It firstly retrieves the info from redis, +// if there's nothing in the redis, it then does db select. +func (s *Controller) Query(ctx context.Context, req *user.QueryReq) (res *user.QueryRes, err error) { + err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: 5 * time.Second, + Name: s.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Scan(&res) + if err != nil { + return nil, err + } + return +} + +// Delete is a route handler for deleting specified user info. +func (s *Controller) Delete(ctx context.Context, req *user.DeleteReq) (res *user.DeleteRes, err error) { + err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: s.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Scan(&res) + return +} + +func (s *Controller) userCacheKey(id int32) string { + return fmt.Sprintf(`userInfo:%d`, id) +} +``` + +服务端代码简要说明: + +1、首先,服务端需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 + +2、可以看到,业务逻辑和之前HTTP示例项目完全一致,只是接入层修改为了GRPC协议。 + +3、我们仍然通过缓存适配器的方式注入Redis缓存: + +```go +g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) +``` + +5、这里也是通过 `Cache` 方法启用 `ORM` 的缓存特性,之前已经做过介绍,这里不再赘述。 + +## GRPC Client + +```go +package main + +import ( + "github.com/gogf/gf/contrib/registry/etcd/v2" + "github.com/gogf/gf/contrib/rpc/grpcx/v2" + "github.com/gogf/gf/contrib/trace/otlpgrpc/v2" + "github.com/gogf/gf/example/trace/grpc_with_db/protobuf/user" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-grpc-client" + endpoint = "tracing-analysis-dc-bj.aliyuncs.com:8090" + traceToken = "******_******" +) + +func main() { + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + var ctx = gctx.New() + shutdown, err := otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + client := user.NewUserClient(grpcx.Client.MustNewGrpcClientConn("demo")) + + // Baggage. + ctx = gtrace.SetBaggageValue(ctx, "uid", 100) + + // Insert. + insertRes, err := client.Insert(ctx, &user.InsertReq{ + Name: "john", + }) + if err != nil { + g.Log().Fatalf(ctx, `%+v`, err) + } + g.Log().Info(ctx, "insert id:", insertRes.Id) + + // Query. + queryRes, err := client.Query(ctx, &user.QueryReq{ + Id: insertRes.Id, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "query result:", queryRes) + + // Delete. + _, err = client.Delete(ctx, &user.DeleteReq{ + Id: insertRes.Id, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "delete id:", insertRes.Id) + + // Delete with error. + _, err = client.Delete(ctx, &user.DeleteReq{ + Id: -1, + }) + if err != nil { + g.Log().Errorf(ctx, `%+v`, err) + return + } + g.Log().Info(ctx, "delete id:", -1) +} +``` + +客户端代码简要说明: + +1、首先,客户端也是需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 + +2、客户端非常简单,内部初始化以及默认拦截器的设置已经由 `Katyusha` 框架封装好了,开发者只需要关心业务逻辑实现即可, + +## 效果查看 + +**启动服务端:** + +![](/markdown/3470e30ff88d5fdafcaaa175ed5d40a6.png) + +**启动客户端:** + +![](/markdown/3730fb2264d7e19a4990cdf8470c3998.png) + +这里客户端的执行最后报了一个错误,那是我们 **故意为之**,目的是演示 `GRPC` 报错时的链路信息展示。我们打开 `jaeger` 查看一下链路跟踪信息: + +![](/markdown/933c5a2bda5208eaf0b92231923a8334.png) + +可以看到本次请求涉及到两个服务: `tracing-grpc-client` 和 `tracing-grpc-server`,即客户端和服务端。整个请求链路涉及到 `17` 个 `span`,客户端 `5` 个 `span`,服务端 `12` 个 `span`,并且产生了 `2` 个错误。我们点击查看详情: + +![](/markdown/4ebecd20a7894a222dfacffe33ccf262.png) + +我们点击查看一下最后接口调用错误的 `span` 情况: + +![](/markdown/f0e9b3892dbbb628757686a311e8bbf0.png) + +看起来像个参数校验错误,点击查看 `Events/Logs` 中的请求参数: + +![](/markdown/a9c5cdcd7e86c22f926fd20a141f3d68.png) + +查看 `Process` 中的 `Log` 信息可以看到,是由于传递的参数为 `-1`,不满足校验规则,因此在数据校验的时候报错返回了。 + +### GRPC Client + +由于 `orm`、 `redis`、 `logging` 组件在之前的章节中已经介绍过链路信息,因此我们这里主要介绍 `GRPC Client&Server` 的链路信息。 + +#### Attributes + +![](/markdown/273442cb521050b63863e94ac9334d68.png) + +| Attribute/Tag | 说明 | +| --- | --- | +| `net.peer.ip` | 请求的目标IP。 | +| `net.peer.port` | 请求的目标端口。 | +| `rpc.grpc.status_code` | `GRPC` 的内部状态码, `0` 表示成功, `非0` 表示失败。 | +| `rpc.service` | `RPC` 的服务名称,注意这里是 `RPC` 而不是 `GRPC`,因为这里是通用定义,客户端支持多种 `RPC` 通信协议, `GRPC` 只是其中一种。 | +| `rpc.method` | `RPC` 的方法名称。 | +| `rpc.system` | `RPC` 协议类型,如: `grpc`, `thrift` 等。 | + +#### Events/Logs + +![](/markdown/3ee7778da75473938eb5acd4459304a5.png) + +| Event/Log | 说明 | +| --- | --- | +| `grpc.metadata.outgoing` | `GRPC` 客户端请求提交的 `Metadata` 信息,可能会比较大。 | +| `grpc.request.baggage` | `GRPC` 客户端请求提交的 `Baggage` 信息,用于服务间链路信息传递。 | +| `grpc.request.message` | `GRPC` 客户端请求提交的 `Message` 数据,可能会比较大,最大只记录 `512KB`,如果超过该大小则忽略。仅对 `Unary` 请求类型有效。 | +| `grpc.response.message` | `GRPC` 客户端请求接收返回的的 `Message` 信息,可能会比较大。仅对 `Unary` 请求类型有效。 | + +### GRPC Server + +#### Attributes + +![](/markdown/b6a1d35aebb050058c9305cfb49a4bff.png) + +`GRPC Server` 端的 `Attributes` 含义同 `GRPC Client`,在同一请求中,打印的数据基本一致。 + +#### Events + +![](/markdown/88e292828c1785d7e6bd1ba5af191414.png) + +`GRPC Server` 端的 `Events` 与 `GRPC Client` 不同的是,在同一请求中,服务端接收到的 `metadata` 为 `grpc.metadata.incoming`,其他同 `GRPC Client`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" new file mode 100644 index 00000000000..69119172d90 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-Baggage.md" @@ -0,0 +1,184 @@ +--- +slug: '/docs/obs/tracing-http-example-baggage' +title: '链路跟踪-HTTP示例-Baggage' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,链路跟踪,HTTP示例,Baggage,客户端,服务端,Jaeger,OTLP,TraceId] +description: '使用GoFrame框架进行链路跟踪,重点展示了通过HTTP示例实现Baggage数据在服务间传递的方法。详细说明了客户端和服务端的代码实现,包括如何设置和获取Baggage,并提供了Jaeger查看链路信息的方式,为开发者在分布式系统中实现高效的链路跟踪提供了实用指南。' +--- + +## `baggage` 链路数据传递 + +`baggage` 用户链路间(服务间)传递自定义的信息。 + +示例代码地址: [https://github.com/gogf/gf/tree/master/example/trace/http](https://github.com/gogf/gf/tree/master/example/trace/http) + +## 客户端 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" +) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + ctx = gtrace.SetBaggageValue(ctx, "name", "john") + + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/hello") + g.Log().Print(ctx, content) +} +``` + +客户端代码简要说明: + +1. 首先,客户端也是需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 +2. 随后,这里通过 `gtrace.SetBaggageValue(ctx, "name", "john")` 方法设置了一个 `baggage`,该 `baggage` 将会在该请求的所有链路中传递。不过我们该示例也有两个节点,因此该 `baggage` 数据只会传递到服务端。该方法会返回一个新的 `context.Context` 上下文变量,在随后的调用链中我们将需要传递这个新的上下文变量。 +3. 其中,这里通过 `g.Client()` 创建一个HTTP客户端请求对象,该客户端会自动启用链路跟踪特性,无需开发者显示调用任何方法或者任何设置。 +4. 最后,这里使用了 `g.Log().Print(ctx, content)` 方法打印服务端的返回内容,其中的 `ctx` 便是将链路信息传递给日志组件,如果 `ctx` 上下文对象中存在链路信息时,日志组件会同时自动将 `TraceId` 输出到日志内容中。 + +## 服务端 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-server" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/hello", HelloHandler) + }) + s.SetPort(8199) + s.Run() +} + +func HelloHandler(r *ghttp.Request) { + ctx, span := gtrace.NewSpan(r.Context(), "HelloHandler") + defer span.End() + + value := gtrace.GetBaggageVar(ctx, "name").String() + + r.Response.Write("hello:", value) +} +``` + +服务端代码简要说明: + +1. 当然,服务端也是需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 +2. 服务端启动启用链路跟踪特性,开发者无需显示调用任何方法或任何设置。 +3. 服务端通过 `gtrace.GetBaggageVar(ctx, "name").String()` 方法获取客户端提交的 `baggage` 信息,并转换为字符串返回。 + +## 效果查看 + +**启动服务端:** + +![](/markdown/54c448f0e2863c1159a5470adc52aac8.png) + +**启动客户端:** + +![](/markdown/8237ff1b02be36ce1b4e9c160be80d26.png) + +可以看到,客户端提交的 `baggage` 已经被服务端成功接收到并打印返回。并且客户端在输出日志内容的时候也同时输出的 `TraceId` 信息。 `TraceId` 是一条链路的唯一ID,可以通过该ID检索该链路的所有日志信息,并且也可以通过该 `TraceId` 在 `Jaeger` 系统上查询该调用链路详情。 + +在 `Jaeger` 上查看链路信息: + +![](/markdown/83cd3fa37aab22f429df13682afdbe30.png) + +可以看到在这里出现了两个服务名称: `tracing-http-client` 和 `tracing-http-server`,表示我们这次请求涉及到两个服务,分别是HTTP请求的客户端和服务端,并且每个服务中分别涉及到 `2` 个 `span` 链路节点。 + +我们点击这个 `trace` 的详情,可以看得到调用链的层级关系。并且可以看得到客户端请求的地址、服务端接收的路由以及服务端路由函数名称。我们这里来介绍一下客户端的 `Atttributes` 信息和 `Events` 信息,也就是 `Jaeger` 中展示的 `Tags` 信息和 `Process` 信息。 + +### HTTP Client Attributes + +![](/markdown/dcbb5e8e0444a4ce3b433aaa4308222c.png) + +| Attribute/Tag | 说明 | +| --- | --- | +| `otel.instrumentation_library.name` | 当前仪表器名称,往往是当前 `span` 操作的组件名称 | +| `otel.instrumentation_library.version` | 当前仪表器组件版本 | +| `span.kind` | 当前 `span` 的类型,一般由组件自动写入。常见 `span` 类型为: + +| 类型 | 说明 | +| --- | --- | +| `client ` | 客户端 | +| `server` | 服务端 | +| `producer` | 生产者,常用于MQ | +| `consumer` | 消费者,常用于MQ | +| `internal` | 内部方法,一般业务使用 | +| `undefined` | 未定义,较少使用 | | +| `status.code` | 当前 `span` 状态, `0` 为正常, `非0` 表示失败 | +| `status.message` | 当前 `span` 状态信息,往往在失败时会带有错误信息 | +| `hostname` | 当前节点的主机名称 | +| `ip.intranet` | 当前节点的主机内网地址列表 | +| `http.address.local` | HTTP通信的本地地址和端口 | +| `http.address.remote` | HTTP通信的目标地址和端口 | +| `http.dns.start` | 当请求的目标地址带有域名时,开始解析的域名地址 | +| `http.dns.done` | 当请求的目标地址带有域名时,解析结束之后的IP地址 | +| `http.connect.start` | 开始创建连接的类型和地址 | +| `http.connect.done` | 创建连接成功后的类型和地址 | + +### HTTP Client Events + +![](/markdown/9d35a850c1713efc19d56ec3ac990013.png) + +| Event/Log | 说明 | +| --- | --- | +| `http.request.headers` | HTTP客户端请求提交的 `Header` 信息,可能会比较大。 | +| `http.request.baggage` | HTTP客户端请求提交的 `Baggage` 信息,用于服务间链路信息传递。 | +| `http.request.body` | HTTP客户端请求提交的 `Body` 数据,可能会比较大,最大只记录 `512KB`,如果超过该大小则忽略。 | +| `http.response.headers` | HTTP客户端请求接收返回的的 `Header` 信息,可能会比较大。 | +| `http.response.body` | HTTP客户端请求接收返回的 `Body` 数据,可能会比较大,最大只记录 `512KB`,如果超过该大小则忽略。 | + +### HTTP Server Attributes + +![](/markdown/4df0d3d8e5de018788b9b134ea13d535.png) + +`HTTP Server` 端的 `Attributes` 含义同 `HTTP Client`,在同一请求中,打印的数据基本一致。 + +### HTTP Server Events + +![](/markdown/f951072d1f5f116b90350788ad71bc89.png) + +`HTTP Server` 端的 `Events` 含义同 `HTTP Client`,在同一请求中,打印的数据基本一致。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..fd8a3eb3a1d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213-\346\225\260\346\215\256\346\223\215\344\275\234.md" @@ -0,0 +1,312 @@ +--- +slug: '/docs/obs/tracing-http-example-with-database' +title: '链路跟踪-HTTP示例-数据操作' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,链路跟踪,HTTP示例,数据库操作,缓存管理,ORM,Redis,OTLP,Trace,Jaeger] +description: '该文档详细介绍了如何在GoFrame框架中实现链路跟踪,通过HTTP和数据库操作示例,展示了如何使用OTLP进行端到端的追踪。在示例中集成了缓存管理、数据库操作和Redis使用,通过Jaeger查看详细的链路信息,并分析了跨越客户端和服务端的追踪数据,帮助开发者优化和调试全流程。' +--- + +## `HTTP+DB+Redis+Logging` + +我们再来看一个相对完整一点的例子,包含几个常用核心组件的链路跟踪示例,示例代码地址: [https://github.com/gogf/gf/tree/master/example/trace/http\_with\_db](https://github.com/gogf/gf/tree/master/example/trace/http_with_db) + +## 客户端 + +```go +package main + +import ( + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + StartRequests() +} + +func StartRequests() { + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + var ( + err error + client = g.Client() + ) + // Add user info. + var insertRes = struct { + ghttp.DefaultHandlerResponse + Data struct{ Id int64 } `json:"data"` + }{} + err = client.PostVar(ctx, "http://127.0.0.1:8199/user/insert", g.Map{ + "name": "john", + }).Scan(&insertRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "insert result:", insertRes) + if insertRes.Data.Id == 0 { + g.Log().Error(ctx, "retrieve empty id string") + return + } + + // Query user info. + var queryRes = struct { + ghttp.DefaultHandlerResponse + Data struct{ User gdb.Record } `json:"data"` + }{} + err = client.GetVar(ctx, "http://127.0.0.1:8199/user/query", g.Map{ + "id": insertRes.Data.Id, + }).Scan(&queryRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "query result:", queryRes) + + // Delete user info. + var deleteRes = struct { + ghttp.DefaultHandlerResponse + }{} + err = client.PostVar(ctx, "http://127.0.0.1:8199/user/delete", g.Map{ + "id": insertRes.Data.Id, + }).Scan(&deleteRes) + if err != nil { + panic(err) + } + g.Log().Info(ctx, "delete result:", deleteRes) +} +``` + +客户端代码简要说明: + +1. 首先,客户端也是需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 +2. 在本示例中,我们通过HTTP客户端向服务端发起了 `3` 次请求: +1. `/user/insert` 用于新增一个用户信息,成功后返回用户的ID。 +2. `/user/query` 用于查询用户,使用前一个接口返回的用户ID。 +3. `/user/delete` 用于删除用户,使用之前接口返回的用户ID。 + +## 服务端 + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/gogf/gf/contrib/trace/otlphttp/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +type cTrace struct{} + +const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" ) + +func main() { + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + // Set ORM cache adapter with redis. + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + + // Start HTTP server. + s := g.Server() + s.Use(ghttp.MiddlewareHandlerResponse) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/user", new(cTrace)) + }) + s.SetPort(8199) + s.Run() +} + +type InsertReq struct { + Name string `v:"required#Please input user name."` +} +type InsertRes struct { + Id int64 +} + +// Insert is a route handler for inserting user info into database. +func (c *cTrace) Insert(ctx context.Context, req *InsertReq) (res *InsertRes, err error) { + result, err := g.Model("user").Ctx(ctx).Insert(req) + if err != nil { + return nil, err + } + id, _ := result.LastInsertId() + res = &InsertRes{ + Id: id, + } + return +} + +type QueryReq struct { + Id int `v:"min:1#User id is required for querying"` +} +type QueryRes struct { + User gdb.Record +} + +// Query is a route handler for querying user info. It firstly retrieves the info from redis, +// if there's nothing in the redis, it then does db select. +func (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err error) { + one, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: 5 * time.Second, + Name: c.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).One() + if err != nil { + return nil, err + } + res = &QueryRes{ + User: one, + } + return +} + +type DeleteReq struct { + Id int `v:"min:1#User id is required for deleting."` +} +type DeleteRes struct{} + +// Delete is a route handler for deleting specified user info. +func (c *cTrace) Delete(ctx context.Context, req *DeleteReq) (res *DeleteRes, err error) { + _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: c.userCacheKey(req.Id), + Force: false, + }).WherePri(req.Id).Delete() + if err != nil { + return nil, err + } + return +} + +func (c *cTrace) userCacheKey(id int) string { + return fmt.Sprintf(`userInfo:%d`, id) +} +``` + +服务端代码简要说明: + +1. 首先,客户端也是需要通过 `jaeger.Init` 方法初始化 `Jaeger`。 +2. 在本示例中,我们使用到了数据库和数据库缓存功能,以便于同时演示 `ORM` 和 `Redis` 的链路跟踪记录。 +3. 我们在程序启动时通过以下方法设置当前数据库缓存管理的适配器为 `redis`。关于缓存适配器的介绍感兴趣可以参考 [缓存管理-接口设计](../../../核心组件/缓存管理/缓存管理-接口设计.md) 章节。 + +```go +g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) +``` + +4. 在 `ORM` 的操作中,需要通过 `Ctx` 方法将上下文变量传递到组件中, `orm` 组件会自动识别当前上下文中是否包含Tracing链路信息,如果包含则自动启用链路跟踪特性。 +5. 在 `ORM` 的操作中,这里使用 `Cache` 方法缓存查询结果到 `redis` 中,并在删除操作中也使用 `Cache` 方法清除 `redis` 中的缓存结果。关于 `ORM` 的缓存管理介绍请参考 [ORM链式操作-查询缓存](../../../核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) 章节。 + +## 效果查看 + +**启动服务端:** + +![](/markdown/da456bb3ba924d8310f8f2a730f6c883.png) + +**启动客户端:** + +![](/markdown/1cdc85405e6cdb92d653df295edf4672.png) + +在 `Jaeger` 上查看链路信息: + +![](/markdown/85c062f73b9254973232ec56fa76e1db.png) + +可以看到,这次请求总共产生了 `14` 个 `span`,其中客户端有 `4` 个 `span`,服务端有 `10` 个 `span`,每一个 `span` 代表一个链路节点。不过,我们注意到,这里产生了 `3` 个 `errors`。我们点击详情查看什么原因呢。 + +![](/markdown/14a27e50c9d458f751a4aca17cb6ecb4.png) + +我们看到好像所有的 `redis` 操作都报错了,随便点击一个 `redis` 的相关 `span`,查看一下详情呢: + +![](/markdown/2c59e1b7feaa7094ae74bdcc987bd6a6.png) + +原来是 `redis` 连接不上报错了,这样的话所有的 `orm` 缓存功能都失效了,但是可以看到并没有影响接口逻辑,只是所有的查询都走了数据库。这个报错是因为我本地忘了打开 `redis server`,我赶紧启动一下本地的 `redis server`,再看看效果: + +![](/markdown/f43bf06efa5da79a21146d9f6d93ceab.png) + +再把上面的客户端运行一下,查看 `jaeger`: + +![](/markdown/4a9fdf41e5e608907176a5d3e4bfd1ff.png) + +![](/markdown/405329a1897f8cc0c9cc28aba01505d9.png) + +现在就没有报错了。 + +`HTTP Client&Server`、 `Logging` 组件在之前已经介绍过,因此这里我们主要关注 `orm` 和 `redis` 组件的链路跟踪信息。 + +### ORM链路信息 + +#### Attributes/Tags + +我们随便点开一个 `ORM` 链路 `Span`,看看 `Attributes/Tags` 信息: + +![](/markdown/dd036ffacb33f3750edce3484387fe30.png) + +可以看到这里的 `span.kind` 是 `internal`,也就是之前介绍过的方法内部 `span` 类型。这里很多 `Tags` 在之前已经介绍过,因此这里主要介绍关于数据库相关的 `Tags`: + +| Attribute/Tag | 说明 | +| --- | --- | +| `
    db.type
    ` | 数据库连接类型。如 `mysql`, `mssql`, `pgsql` 等等。 | +| `db.link` | 数据库连接信息。其中密码字段被自动隐藏。 | +| `db.group` | 在配置文件中的数据库分组名称。 | + +#### Events/Process + +![](/markdown/598498f523ea992f53d3cb58fd51eb64.png) + +| Event/Log | 说明 | +| --- | --- | +| `db.execution.sql` | 执行的具体 `SQL` 语句。由于ORM底层是预处理,该语句为方便查看自动拼接而成,仅供参考。 | +| `db.execution.type` | 执行的 `SQL` 语句类型。常见为 `DB.ExecContext` 和 `DB.QueryContext`,分别代表写操作和读操作。 | +| `db.execution.cost` | 当前 `SQL` 语句执行耗时,单位为 `ms` 毫秒。 | + +### Redis链路信息 + +#### Attributes/Tags + +![](/markdown/b6acd314e55ef431966734940ea867fe.png) + +| Attribute/Tag | 说明 | +| --- | --- | +| `
    redis.host
    ` | `Redis` 连接地址。 | +| `redis.port` | `Redis` 连接端口。 | +| `redis.db` | `Redis` 操作 `db`。 | + +#### Events/Process + +![](/markdown/34139be20f62d27fb0a16fd2edf59031.png) + +| Event/Log | 说明 | +| --- | --- | +| `redis.execution.command` | `Redis` 执行指令。 | +| `redis.execution.arguments` | `Redis` 执行指令参数。 | +| `redis.execution.cost` | `Redis` 执行指令执行耗时,单位为 `ms` 毫秒。 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..447bae95219 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213/\351\223\276\350\267\257\350\267\237\350\270\252-HTTP\347\244\272\344\276\213.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/obs/tracing-http-example' +title: '链路跟踪-HTTP示例' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,链路跟踪,HTTP示例,baggage,上下文数据传递,HTTP+DB+Redis+Logging,服务链路,示例演示,组件跟踪] +description: '在使用GoFrame框架时进行链路跟踪。通过两个示例,演示服务间上下文数据传递以及HTTP、数据库、Redis和日志组件的完整链路跟踪,帮助开发者更好地掌握应用服务之间的请求处理过程,提高系统的监控和分析能力。' +--- + + +在本章节中,我们演示两个示例,一个用于演示 `baggage` 服务间上下文数据传递;一个用于演示较完整的 `HTTP+DB+Redis+Logging` 组件的链路跟踪。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" new file mode 100644 index 00000000000..f97c3031b16 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\207\206\345\244\207\345\267\245\344\275\234.md" @@ -0,0 +1,59 @@ +--- +slug: '/docs/obs/tracing-prepare' +title: '链路跟踪-准备工作' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,OpenTelemetry,Jaeger,分布式追踪,链路跟踪,docker,OTLP HTTP,OTLP GRPC,示例代码] +description: '在GoFrame框架中使用Jaeger实现链路追踪。我们将通过Jaeger和OpenTelemetry结合的方式,展示如何在系统中引入分布式追踪,包括Jaeger的快速部署方法,以及GoFrame框架中示例代码的位置和封装好的注册模块。' +--- + +对 `OpenTelemetry` 的概念有初步了解后,我们接着以 `Jaeger` 为例来演示如何在程序中使用实现链路追踪。 + +## Jaeger + +[Jaeger](https://www.jaegertracing.io/)\\ˈyā-gər\ 是Uber开源的分布式追踪系统,是支持 `OpenTelemetry` 的系统之一,也是 `CNCF` 项目。本篇将使用 `Jaeger` 来演示如何在系统中引入分布式追踪。以下是 `Opentracing+Jaeger` 的架构图,针对于使用 `OpenTelemetry` 也是如此。 + +## ![](/markdown/cd8d6734e501e9ac4917920666cb0867.png) + +## 准备工作 + +`Jaeger` 提供了 `all-in-one` 镜像,方便我们快速开始测试: + +```bash +docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.64.0 +``` + + `all-in-one` V1 相关文档地址:[https://www.jaegertracing.io/docs/1.64/getting-started/](https://www.jaegertracing.io/docs/1.64/getting-started/) + + `all-in-one` V2 相关文档地址:[https://www.jaegertracing.io/docs/2.1/getting-started/](https://www.jaegertracing.io/docs/2.1/getting-started/) + +如果 `docker` 镜像拉取太慢,您可以尝试修改 `docker` 拉取站点的镜像地址,例如: [http://mirrors.ustc.edu.cn/help/dockerhub.html?highlight=docker](http://mirrors.ustc.edu.cn/help/dockerhub.html?highlight=docker) + +镜像启动后,通过 [http://localhost:16686](http://localhost:16686/) 可以打开 `Jaeger UI`。 + +![](/markdown/870c4c69cfd848787f88b074f0879519.png) + +## 示例代码地址 + +我们的示例代码在 `gf` 主库中,地址: [https://github.com/gogf/gf/tree/master/example/trace](https://github.com/gogf/gf/tree/master/example/trace) + +## Jaeger注册封装(即将在2.6.0版本移除) + +为方便开发者使用,我们通过社区模块的形式,已经封装好了对 `jaeger` 的初始化逻辑,代码地址: [https://github.com/gogf/gf/tree/master/contrib/trace/jaeger](https://github.com/gogf/gf/tree/master/contrib/trace/jaeger) + +## OTLP HTTP注册封装 + +为方便开发者使用,我们通过社区模块的形式,已经封装好了对 `otelhttp` 的初始化逻辑,代码地址: [https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp](https://github.com/gogf/gf/tree/master/contrib/trace/otlphttp) + +## OTLP GRPC注册封装 + +为方便开发者使用,我们通过社区模块的形式,已经封装好了对 `otelgrpc` 的初始化逻辑,代码地址: [https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc](https://github.com/gogf/gf/tree/master/contrib/trace/otlpgrpc) diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..145b95713fc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\345\237\272\346\234\254\347\244\272\344\276\213.md" @@ -0,0 +1,136 @@ +--- +slug: '/docs/obs/tracing-example' +title: '链路跟踪-基本示例' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,链路跟踪,单进程,Root Span,Jaeger UI,Span创建,方法调用,GoFrame框架,OpenTelemetry,性能瓶颈] +description: '在单进程中使用GoFrame进行链路跟踪,通过创建Root Span来追踪方法调用链,并在Jaeger UI中查看结果。介绍了如何在方法间创建Span以记录方法调用过程,并通过详细的示例代码展示了如何在GoFrame框架中实现链路的信息传递与性能监控,帮助用户快速定位系统异常和发现性能瓶颈。' +--- + +## 单进程示例 + +单进程的链路跟踪即进程内方法之间的调用链关系。这种场景的跟踪没有涉及到分布式跟踪,比较简单,以该示例作为我们入门的一个例子吧。示例代码地址: [https://github.com/gogf/gf/tree/master/example/trace/inprocess](https://github.com/gogf/gf/tree/master/example/trace/inprocess) + +## Root Span + +`root span` 即链路中第一个 `span` 对象。在这里的单进程场景中,往往需要手动创建一个。随后在方法内部创建的 `span` 都会作为它的子级 `span`。 +:::tip +在分布式架构的服务间通信场景中,往往不需要开发者手动创建 `root span`,而是由客户端/服务端请求的拦截器来自动创建。 +::: +创建 `tracer`,生成 `root span`: + +```go +func main() { + const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" + ) + + var ctx = gctx.New() + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown() + + ctx, span := gtrace.NewSpan(ctx, "main") + defer span.End() + + // Trace 1. + user1 := GetUser(ctx, 1) + g.Dump(user1) + + // Trace 2. + user100 := GetUser(ctx, 100) + g.Dump(user100) +} +``` + +上述代码创建了一个 `root span`,并将该 `span` 通过 `context` 传递给 `GetUser` 方法,以便在 `GetUser` 方法中将追踪链继续延续下去。 + +## 方法间Span创建 + +```go +// GetUser retrieves and returns hard coded user data for demonstration. +func GetUser(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetUser") + defer span.End() + m := g.Map{} + gutil.MapMerge( + m, + GetInfo(ctx, id), + GetDetail(ctx, id), + GetScores(ctx, id), + ) + return m +} + +// GetInfo retrieves and returns hard coded user info for demonstration. +func GetInfo(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetInfo") + defer span.End() + if id == 100 { + return g.Map{ + "id": 100, + "name": "john", + "gender": 1, + } + } + return nil +} + +// GetDetail retrieves and returns hard coded user detail for demonstration. +func GetDetail(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetDetail") + defer span.End() + if id == 100 { + return g.Map{ + "site": "https://goframe.org", + "email": "john@goframe.org", + } + } + return nil +} + +// GetScores retrieves and returns hard coded user scores for demonstration. +func GetScores(ctx context.Context, id int) g.Map { + ctx, span := gtrace.NewSpan(ctx, "GetScores") + defer span.End() + if id == 100 { + return g.Map{ + "math": 100, + "english": 60, + "chinese": 50, + } + } + return nil +} +``` + +该示例代码展示了多层级方法间的链路信息传递,即是把 `ctx` 上下文变量作为第一个方法参数传递即可。在方法内部,我们通过的固定语法来创建/开始一个 `Span`: + +```go +ctx, span := gtrace.NewSpan(ctx, "xxx") +defer span.End() +``` + +并通过 `defer` 的方式调用 `span.End` 来结束一个 `Span`,这样可以很好地记录 `Span` 生命周期(开始和结束)信息,这些信息都将会展示到链路跟踪系统中。其中 `gtrace.NewSpan` 方法的第二个参数 `spanName` 我们直接给定方法的名称即可,这样在链路展示中比较有识别性。 + +## 效果查看 + +执行完上面的程序后,终端输出: + +![](/markdown/8124c7049fb50f1885c70626b28869da.png) + +打开 `Jaeger UI`: [http://localhost:16686/search](http://localhost:16686/search),可以看到链路追踪的结果:![](/markdown/bd0a6f9c87f239e6730243a09de02d6d.jpg) + +![](/markdown/fda619bc9de75bd8040ae71d18738528.png) + +点击详情可以查看具体信息,包括 `span` 的调用顺序、调用关系,执行时间轴,以及记录一些Attributes和 `Events` 信息,极大的方便我们定位系统中的异常和发现性能瓶颈。: + +![](/markdown/a4e87a91b4ba860a8045e68b6cc34cae.png) + +其中的 `tracing-inprocess` 是我们 `tracer` 的名称,该名称往往是服务名称,由于我们这里只有一个进程和一个 `tracer`,因此这里只看得到一个服务名称。其中的 `main` 为我们创建的 `root span` 名称,其他的 `span` 为基于该 `root span` 创建的子级 `span`。由于我们在程序中调用了两次 `GetUser` 方法,因此这里也展示了两次 `GetUser` 方法的调用。每一次 `GetUser` 调用的内部又分别去调用了 `GetIndo、GetDetail、GetScores` 三个方法,方法间的调用层级关系展示得非常清晰明了,并且每个方法的调用时长都可以看得到。 + +关于其中每个 `span` 记录的 `Tags` 和 `Process` 信息其实对应了 `OpenTelemetry` 中的 `Attributes` 和 `Events` 信息,这些信息我们放到后续章节去详细介绍。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" new file mode 100644 index 00000000000..6f240829a63 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\346\234\200\344\275\263\345\256\236\350\267\265/\351\223\276\350\267\257\350\267\237\350\270\252-TraceID\346\263\250\345\205\245\345\222\214\350\216\267\345\217\226.md" @@ -0,0 +1,334 @@ +--- +slug: '/docs/obs/tracing-practice-inject-traceid' +title: '链路跟踪-TraceID注入和获取' +sidebar_position: 0 +hide_title: true +keywords: [链路跟踪,TraceID,GoFrame,OpenTelemetry,Context,客户端,服务端,日志,请求,Response Header] +description: '在链路跟踪中如何使用GoFrame框架进行TraceID的注入和获取。TraceID是服务间请求关联的重要标识,通过Context参数传递,可在客户端和服务端中自动生成、承接或自定义TraceID。使用glog日志组件可以自动记录TraceID,GoFrame的Client和Server拥有便捷的TraceID管理方法。并提供了一些实践示例,包括自定义TraceID和处理第三方RequestID的集成方式。' +--- + +## 一、基本介绍 + +在链路跟踪中, `TraceID` 作为在各个服务间传递的唯一标识,用于串联服务间请求关联关系,是非常重要的一项数据。 `TraceID` 是通过 `Context` 参数传递的,如果使用框架的 `glog` 日志组件,那么在日志打印中将会自动读取 `TraceID` 记录到日志内容中。因此也建议大家使用框架的 `glog` 日志组件来打印日志,便于完美地支持链路跟踪特性。 + +## 二、TraceID的注入 + +### 1、客户端 + +如果使用 `GoFrame` 框架的 `Client`,那么所有的请求将会自带 `TraceID` 的注入。 `GoFrame` 的 `TraceID` 使用的是 `OpenTelemetry` 规范,是由十六进制字符组成的的 `32` 字节字符串。 +:::tip +强烈建议大家统一使用 `gclient` 组件,不仅功能全面而且自带链路跟踪能力。 +::: +### 2、服务端 + +如果使用 `GoFrame` 框架的 `Server`,如果请求带有 `TraceID`,那么将会自动承接到 `Context` 中;否则,将会自动注入标准的 `TraceID`,并传递给后续逻辑。 + +## 三、TraceID的获取 + +### 1、客户端 + +如果使用 `GoFrame` 框架的 `Client`,这里有三种方式。 + +#### 1)自动生成TraceID(推荐) + +通过 `gctx.New/WithCtx` 方法创建一个带有 `TraceID` 的 `Context`,该 `Context` 参数用于传递给请求方法。随后可以通过 `gctx.CtxId` 方法获取整个链路的 `TraceID`。相关方法: + +```go +// New creates and returns a context which contains context id. +func New() context.Context + +// WithCtx creates and returns a context containing context id upon given parent context `ctx`. +func WithCtx(ctx context.Context) context.Context +``` + +使用 `WithCtx` 方法时,如果给定的 `ctx` 参数本身已经带有 `TraceID`,那么它将会直接使用该 `TraceID`,并不会新建。 + +#### 2)客户端自定义TraceID + +这里还有个高级的用法,客户端可以自定义 `TraceID`,使用 `gtrace.WithTraceID` 方法。方法定义如下: + +```go +// WithTraceID injects custom trace id into context to propagate. +func WithTraceID(ctx context.Context, traceID string) (context.Context, error) +``` + +#### 3)读取Response Header + +如果是请求 `GoFrame` 框架的 `Server`,那么在返回请求的 `Header` 中将会增加 `Trace-Id` 字段,供客户端读取。 + +### 2、服务端 + +如果使用 `GoFrame` 框架的 `Server`,直接通过 `gctx.CtxId` 方法即可获取 `TraceID`。相关方法: + +```go +// CtxId retrieves and returns the context id from context. +func CtxId(ctx context.Context) string +``` + +## 四、使用示例 + +### 1、HTTP Response Header TraceID + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + req, err := g.Client().Get(context.Background(), "http://127.0.0.1:8199/") + if err != nil { + panic(err) + } + defer req.Close() + req.RawDump() +} +``` + +执行后,终端输出: + +``` + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:14:37.245 [INFO] pid[55899]: http server started listening on [:8199] +2022-06-06 21:14:38.247 [INFO] {908d2027560af616e218e912d2ac3972} handler ++---------------------------------------------+ +| REQUEST | ++---------------------------------------------+ +GET / HTTP/1.1 +Host: 127.0.0.1:8199 +User-Agent: GClient v2.1.0-rc4 at TXQIANGGUO-MB0 +Traceparent: 00-908d2027560af616e218e912d2ac3972-1e291041b9afa718-01 +Accept-Encoding: gzip + ++---------------------------------------------+ +| RESPONSE | ++---------------------------------------------+ +HTTP/1.1 200 OK +Connection: close +Content-Length: 32 +Content-Type: text/plain; charset=utf-8 +Date: Mon, 06 Jun 2022 13:14:38 GMT +Server: GoFrame HTTP Server +Trace-Id: 908d2027560af616e218e912d2ac3972 + +908d2027560af616e218e912d2ac3972 +``` + +### 2、客户端注入 `TraceID` + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + ctx := gctx.New() + g.Log().Info(ctx, "request starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "response: %s", content) +} +``` + +执行后,终端输出: + +```html +2022-06-06 21:17:17.447 [INFO] pid[56070]: http server started listening on [:8199] + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:17:18.450 [INFO] {e843f0737b0af616d8ed185d46ba65c5} request starts +2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} handler +2022-06-06 21:17:18.451 [INFO] {e843f0737b0af616d8ed185d46ba65c5} response: e843f0737b0af616d8ed185d46ba65c5 +``` + +### 3、客户端自定义 `TraceID` + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/text/gstr" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "handler") + r.Response.Write(traceID) + }) + s.SetPort(8199) + go s.Start() + + time.Sleep(time.Second) + + ctx, _ := gtrace.WithTraceID(context.Background(), gstr.Repeat("a", 32)) + g.Log().Info(ctx, "request starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "response: %s", content) +} +``` + +执行后,终端输出: + +``` + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------------------------------------------------------|-------------------- + :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-06 21:40:03.897 [INFO] pid[58435]: http server started listening on [:8199] +2022-06-06 21:40:04.900 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} request starts +2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} handler +2022-06-06 21:40:04.901 [INFO] {aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} response: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +``` + +## 五、常见问题 + +### 1、如果没有使用 `GoFrame` 框架的 `Client/Server`,如何获取链路的 `TraceID`? + +可以参考 `GoFrame` 框架的 `Client/Server` 的链路跟踪实现,并自行实现一遍,这块可能需要一定成本。 + +如果使用的第三方 `Client/Server` 组件,请参考第三方组件相关介绍。 + +### 2、企业内部服务没有使用标准的 `OpenTelemetry` 规范,但是每个请求都带 `RequestID` 参数,形如 `33612a70-990a-11ea-87fe-fd68517e7a2d`,如何和 `TraceID` 结合起来? + +根据我的分析,你这个 `RequestID` 的格式和 `TraceID` 规范非常切合,使用的是 `UUID` 字符串,而 `UUID` 可直接转换为 `TraceID`。建议在自己的 `Server` 内部第一层中间件中将 `RequestID` 转换为 `TraceID`,通过自定义 `TraceID` 的方式注入到 `Context` 中,并将该 `Context` 传递给后续业务逻辑。 + +我来给你写个例子吧: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/net/gtrace" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + // 内部服务 + internalServer := g.Server("internalServer") + internalServer.BindHandler("/", func(r *ghttp.Request) { + traceID := gctx.CtxId(r.Context()) + g.Log().Info(r.Context(), "internalServer handler") + r.Response.Write(traceID) + }) + internalServer.SetPort(8199) + go internalServer.Start() + + // 外部服务,访问以测试 + // http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d + userSideServer := g.Server("userSideServer") + userSideServer.Use(func(r *ghttp.Request) { + requestID := r.Get("RequestID").String() + if requestID != "" { + newCtx, err := gtrace.WithUUID(r.Context(), requestID) + if err != nil { + panic(err) + } + r.SetCtx(newCtx) + } + r.Middleware.Next() + }) + userSideServer.BindHandler("/", func(r *ghttp.Request) { + ctx := r.Context() + g.Log().Info(ctx, "request internalServer starts") + content := g.Client().GetContent(ctx, "http://127.0.0.1:8199/") + g.Log().Infof(ctx, "internalServer response: %s", content) + r.Response.Write(content) + }) + userSideServer.SetPort(8299) + userSideServer.Run() +} +``` + +为了演示服务间的链路跟踪能力,这个示例代码中运行了两个HTTP服务,一个内部服务,提供功能逻辑;一个外部服务,供外部的接口调用,它的功能是调用内部服务来实现的。执行后,我们访问: [http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d](http://127.0.0.1:8299/?RequestID=33612a70-990a-11ea-87fe-fd68517e7a2d) + +随后查看终端输出: + +```html +2022-06-07 14:51:21.957 [INFO] openapi specification is disabled +2022-06-07 14:51:21.958 [INTE] ghttp_server.go:78 78198: graceful reload feature is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + internalServer | default | :8199 | ALL | / | main.main.func1 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + internalServer | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-07 14:51:21.959 [INFO] pid[78198]: http server started listening on [:8199] +2022-06-07 14:51:21.965 [INFO] openapi specification is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | / | main.main.func3 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + userSideServer | default | :8299 | ALL | /* | main.main.func2 | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-06-07 14:51:21.965 [INFO] pid[78198]: http server started listening on [:8299] +2022-06-07 14:53:05.322 [INFO] {33612a70990a11ea87fefd68517e7a2d} request internalServer starts +2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer handler +2022-06-07 14:53:05.323 [INFO] {33612a70990a11ea87fefd68517e7a2d} internalServer response: 33612a70990a11ea87fefd68517e7a2d +``` + +我们发现, `RequestID` 作为 `TraceID` 贯通了整个服务间的链路了呢! \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" new file mode 100644 index 00000000000..d0a583379cd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\234\215\345\212\241\345\217\257\350\247\202\346\265\213\346\200\247/\346\234\215\345\212\241\351\223\276\350\267\257\350\267\237\350\270\252/\351\223\276\350\267\257\350\267\237\350\270\252-\350\203\214\346\231\257\347\237\245\350\257\206.md" @@ -0,0 +1,163 @@ +--- +slug: '/docs/obs/tracing-intro' +title: '链路跟踪-背景知识' +sidebar_position: 0 +hide_title: true +keywords: [OpenTelemetry,分布式链路跟踪,OpenTracing,OpenCensus,Tracer,Span,Attributes,Events,GoFrame,传播器] +description: 'OpenTelemetry项目的背景和重要概念,包括TracerProvider、Tracer、Span、Attributes、Events、SpanContext和Propagator等组件,并说明了GoFrame框架在这些技术上的支持,以及如何使用gtrace模块实现链路跟踪。此外,还列举了支持OpenTelemetry标准的GoFrame核心组件,如HTTP客户端、HTTP服务端、gRPC客户端和服务端、Logging、ORM和NoSQL Redis等。' +--- + +## OpenTelemetry + +分布式链路跟踪( ` + Distributed Tracing + `)的概念最早是由 `Google` 提出来的,发展至今技术已经比较成熟,也是有一些协议标准可以参考。目前在 `Tracing` 技术这块比较有影响力的是两大开源技术框架: `Netflix` 公司开源的 `OpenTracing` 和 `Google` 开源的 `OpenCensus`。两大框架都拥有比较高的开发者群体。为形成统一的技术标准,两大框架最终磨合成立了 `OpenTelemetry` 项目,简称 `otel`。具体可以参考: + +1. [OpenTracing介绍](https://johng.cn/observability/opentracing-introduction) +2. [OpenTelemetry介绍](https://johng.cn/observability/opentelemetry-introduction) + +因此,我们的 `Tracing` 技术方案以 `OpenTelemetry` 为实施标准,协议标准的一些 `Golang` 实现开源项目: + +1. [https://github.com/open-telemetry/opentelemetry-go](https://github.com/open-telemetry/opentelemetry-go) +2. [https://github.com/open-telemetry/opentelemetry-go-contrib](https://github.com/open-telemetry/opentelemetry-go-contrib) + +其他第三方的框架和系统(如 `Jaeger/Prometheus/Grafana` 等)也会按照标准化的规范来对接 `OpenTelemetry`,使得系统的开发和维护成本大大降低。 + +![](/markdown/63ac4d816e08ea5dd6d986f4119d03e1.jpg) + +## 重要概念 + +我们先看看 `OpenTelemetry` 的架构图,我们这里不会完整介绍,只会介绍其中大家常用的几个概念。 +关于 `OpenTelemetry` 的内部技术架构设计介绍,可以参考 [OpenTelemetry架构](https://johng.cn/observability/opentelemetry-architecture) , +关于语义约定请参考: [https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/api.md) + +![](8fbc65f937aaac8c9b6947faa89a6964.png) + +### TracerProvider + +主要负责创建 `Tracer`,一般是需要第三方的分布式链路跟踪管理平台提供具体的实现。默认情况是一个空的 `TracerProvider (NoopTracerProvider)`,虽然也能创建 `Tracer` 但是内部其实不会执行具体的数据流传输逻辑。 + +### Tracer + +`Tracer` 表示一次完整的追踪链路, `tracer` 由一个或多个 `span` 组成。下图示例表示了一个由 `8` 个 `span` 组成的 `tracer`: + +``` + [Span A] ←←←(the root span) + | + +------+------+ + | | + [Span B] [Span C] ←←←(Span C is a `ChildOf` Span A) + | | + [Span D] +---+-------+ + | | + [Span E] [Span F] >>> [Span G] >>> [Span H] + ↑ + ↑ + ↑ + (Span G `FollowsFrom` Span F) +``` + +时间轴的展现方式会更容易理解: + +``` +––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–––––––|–> time + + [Span A···················································] + [Span B··············································] + [Span D··········································] + [Span C········································] + [Span E·······] [Span F··] [Span G··] [Span H··] +``` + +我们通常通过以下方式创建一个 `Tracer`: + +``` +gtrace.NewTracer(tracerName) +``` + +### Span + +`Span` 是一条追踪链路中的基本组成要素,一个 `span` 表示一个独立的工作单元,比如可以表示一次函数调用,一次 `http` 请求等等。 `span` 会记录如下基本要素: + +- 服务名称( `operation name`) +- 服务的开始时间和结束时间 +- `K/V` 形式的 `Tags` +- `K/V` 形式的 `Logs` +- `SpanContext` + +`Span` 是这么多对象中使用频率最高的,因此创建 `Span` 也非常简便,例如: + +``` +gtrace.NewSpan(ctx, spanName, opts...) +``` + +### Attributes + +`Attributes` 以 `K/V` 键值对的形式保存用户自定义标签,主要用于链路追踪结果的查询过滤。例如: `http.method="GET",http.status_code=200`。其中 `key` 值必须为字符串, `value` 必须是字符串,布尔型或者数值型。 `span` 中的 `Attributes` 仅自己可见,不会随着 `SpanContext` 传递给后续 `span`。 设置 `Attributes` 方式例如: + +``` +span.SetAttributes( + label.String("http.remote", conn.RemoteAddr().String()), + label.String("http.local", conn.LocalAddr().String()), +) +``` + +### Events + +`Events` 与 `Attributes` 类似,也是 `K/V` 键值对形式。与 `Attributes` 不同的是, `Events` 还会记录写入 `Events` 的时间,因此 `Events` 主要用于记录某些事件发生的时间。 `Events` 的 `key` 值同样必须为字符串,但对 `value` 类型则没有限制。例如: + +``` +span.AddEvent("http.request", trace.WithAttributes( + label.Any("http.request.header", headers), + label.Any("http.request.baggage", gtrace.GetBaggageMap(ctx)), + label.String("http.request.body", bodyContent), +)) +``` + +### SpanContext + +`SpanContext` 携带着一些用于 **跨服务通信的(跨进程)** 数据,主要包含: + +- 足够在系统中标识该 `span` 的信息,比如: `span_id, trace_id`。 +- `Baggage` - 为整条追踪连保存跨服务(跨进程)的 `K/V` 格式的用户自定义数据。 `Baggage` 与 `Attributes` 类似,也是 `K/V` 键值对。与 `Attributes` 不同的是: + +- 其 `key` 跟 `value` 都只能是字符串格式 +- `Baggage` 不仅当前 `span` 可见,其会随着 `SpanContext` 传递给后续所有的子 `span`。要小心谨慎的使用 `Baggage` \- 因为在所有的 `span` 中传递这些 `K,V` 会带来不小的网络和 `CPU` 开销。 + +### Propagator + +`Propagator` 传播器用于端对端的数据编码/解码,例如: `Client` 到 `Server` 端的数据传输, `TraceId`、 `SpanId` 和 `Baggage` 也是需要通过传播器来管理数据传输。业务端开发者往往对 `Propagator` 无感知,只有中间件/拦截器的开发者需要知道它的作用。 `OpenTelemetry` 的标准协议实现库提供了常用的 `TextMapPropagator`,用于常见的文本数据端到端传输。此外,为保证 `TextMapPropagator` 中的传输数据兼容性,不应当带有特殊字符,具体请参考: [https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/context/api-propagators.md) + +`GoFrame` 框架通过 `gtrace` 模块使用了以下传播器对象,并全局设置到了 `OpenTelemetry` 中: + +```go +// defaultTextMapPropagator is the default propagator for context propagation between peers. +defaultTextMapPropagator = propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, +) +``` + +## 支持组件 +:::tip +`GoFrame` 的核心组件都已经全面支持 `OpenTelemetry` 标准,并且 **自动开启** 了链路跟踪特性,开发者无需显示调用、使用无感知。在没有注入外部 `TracerProvider` 的情况下,框架会使用默认的 `TracerProvider`,该 `TracerProvider` 只会自动创建 `TraceID` 及 `SpanID`,以便打通请求日志的链路,并不会执行复杂逻辑。 +::: +包括但不限于以下核心组件: + +| 自动支持链路跟踪特性的组件 | 组件名 | 描述 | +| --- | --- | --- | +| `HTTP Client` | `gclient` | `HTTP` 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 | +| `Http Server` | `ghttp` | `HTTP` 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 | +| `gRPC Client` | `contrib/rpc/grpcx` | `gRPC` 客户端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 | +| `gRPC Server` | `contrib/rpc/grpcx` | `gRPC` 服务端自动启用了链路跟踪特性,具体使用示例请参考后续示例章节。 | +| `Logging` | `glog` | 日志内容中需要注入当前请求的 `TraceId`,以方便通过日志快速查找定位问题点。该特性是由 `glog` 组件实现,这需要开发者在输出日志的时候调用 `Ctx` 链式操作方法将 `context.Context` 上下文变量传递到当前输出日志操作链路中,没有传递 `context.Context` 上下文变量,就会丢失日志内容中的 `TraceId`。 | +| `ORM` | `gdb` | 数据库的执行是很重要的链路环节, `Orm` 组件需要将自身的执行情况投递到链路中,作为执行链路的一部分。 | +| `NoSQL Redis` | `gredis` | `Redis` 的执行也是很重要的链路环节, `Redis` 需要将自身的执行情况投递到链路中,作为执行链路的一部分。 | +| `Utils` | `gtrace` | 对于 `Tracing` 特性的管理需要做一定的封装,主要考虑的是可扩展性和易用性两方面。该封装由 `gtrace` 模块实现,文档地址: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtrace) | + +## 参考资料 + +- [https://opentracing.io](https://opentracing.io/) +- [https://opencensus.io](https://opencensus.io/) +- [https://opentelemetry.io](https://opentelemetry.io/) +- [https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification](https://github.com/open-telemetry/opentelemetry-specification/tree/main/specification) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..72825279377 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\344\275\277\347\224\250\344\273\213\347\273\215.md" @@ -0,0 +1,347 @@ +--- +slug: '/docs/core/gi18n-example' +title: 'I18N国际化-使用介绍' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,i18n,国际化,多语言,上下文,模板转译,context,T方法,独立对象,SetLanguage] +description: '在GoFrame框架中使用i18n国际化功能,包括对象创建、语言设置、常用方法以及与视图引擎的集成。详细描述了通过单例和独立对象管理语言转译的方式,使用SetLanguage和WithLanguage方法进行语言设定,并通过T和Tf方法实现关键字和模板内容的转译。文章还展示了通过上下文设置和视图引擎进行国际化操作的示例。' +--- + +## 对象创建 + +### 单例对象 + +大多数场景下,我们推荐使用 `g.I18n` 单例对象,并可自定义配置不同的单例对象,但是需要注意的是,单例对象的配置修改是全局有效的。例如: + +```go +g.I18n().T(context.TODO(), "{#hello} {#world}") +``` +:::tip +在所有的转译方法中,第一个参数都要求输入 `Context` 上下文变量参数,用于上下文变量的传递、翻译语言的指定、后续的可扩展能力。该参数虽然直接传递 `nil` 也是可以的,但是为保证程序的严谨性,我们建议您当不知道传递什么或者没有特殊要求的时候传递 `context.TODO()` 或者 `context.Background()` 来替代。 +::: +### 独立对象 + +其次,我们也可以模块化独立使用 `gi18n` 模块,通过 `gi18n.New()` 方法创建独立的 `i18n` 对象,然后开发者自行进行管理。例如: + +```go +i18n := gi18n.New() +i18n.T(context.TODO(), "{#hello} {#world}") +``` + +## 语言设置 + +设置转译语言有两种方式,一种是通过 `SetLanguage` 方法设置当前 `I18N` 对象统一的转译语言,另一种是通过上下文设置当前执行转译的语言。 + +### `SetLanguage` + +例如,我们通过 `g.I18n().SetLanguage("zh-CN")` 即可设置当前转译对象的转译语言,随后使用该对象都将使用 `zh-CN` 进行转译。需要注意的是,组件的配置方法往往都不是并发安全的,该方法也同样如此,需要在程序初始化时进行设置,随后不能在运行时进行更改。 + +### `WithLanguage` + +`WithLanguage` 方法可以创建一个新的上下文变量,并临时设置您当前转译的语言,由于该方法作用于 `Context` 上下文,因此是并发安全的,常用于运行时转译语言设置。我们来看一个例子: + +```go +ctx := gi18n.WithLanguage(context.TODO(), "zh-CN") +i18n.Translate(ctx, `hello`) +``` + +其中 `WithLanguage` 方法定义如下: + +```go +// WithLanguage append language setting to the context and returns a new context. +func WithLanguage(ctx context.Context, language string) context.Context +``` + +用于将转译语言设置到上下文变量中,并返回一个新的上下文变量,该变量可用于后续的转译方法。 + +## 常用方法 + +### `T` 方法 + +`T` 方法为 `Translate` 方法的别名,也是大多数时候我们推荐使用的方法名称。 `T` 方法可以给定关键字名称,也可以直接给定模板内容,将会被自动转译并返回转译后的字符串内容。 + +此外, `T` 方法可以通过第二个语言参数指定需要转译的目标语言名称,该名称需要和配置文件/路径中的名称一致,往往是标准化的国际化语言缩写名称例如: `en/ja/ru/zh-CN/zh-TW` 等等。否则,将会自动使用 `Manager` 转译对象中设置的语言进行转译。 + +方法定义: + +```go +// T translates with configured language and returns the translated content. +func T(ctx context.Context, content string) +``` + +**关键字转译** + +关键字的转译直接给 `T` 方法传递关键字即可,例如: `T(context.TODO(), "hello")`、 `T(context.TODO(), "world")`。 `I18N` 组件将会优先将给定的关键字进行转译,转译成后返回转译后的内容,否则直接展示原内容。 + +**模板内容转译** + +`T` 方法支持模板内容转换,模板中的关键字默认使用 `{#` 和 `}` 标签进行包含,模板解析时将会自动替换该标签中的关键字内容。使用示例: + +#### 1)目录结构 + +```bash +├── main.go +└── i18n + ├── en.toml + ├── ja.toml + ├── ru.toml + └── zh-CN.toml +``` + +#### 2)转译文件 + +`ja.toml` + +```bash +hello = "こんにちは" +world = "世界" +``` + +`ru.toml` + +```bash +hello = "Привет" +world = "мир" +``` + +`zh-CN.toml` + +```bash +hello = "你好" +world = "世界" +``` + +#### 3)示例代码 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + ctx = gctx.New() + i18n = gi18n.New() + ) + + i18n.SetLanguage("en") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + i18n.SetLanguage("ja") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + i18n.SetLanguage("ru") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) + + ctx = gi18n.WithLanguage(ctx, "zh-CN") + fmt.Println(i18n.Translate(ctx, `hello`)) + fmt.Println(i18n.Translate(ctx, `GF says: {#hello}{#world}!`)) +} +``` + +执行后,终端输出为: + +```bash +Hello +GF says: HelloWorld! +こんにちは +GF says: こんにちは世界! +Привет +GF says: Приветмир! +你好 +GF says: 你好世界! +``` + +### `Tf` 方法 + +我们都知道,模板内容中也会存在一些变量,这些变量可以通过 `Tf` 方法进行转译。 + +`Tf` 为 `TranslateFormat` 的别名,该方法支持格式化转译内容,字符串格式化语法参考标准库 `fmt` 包的 `Sprintf` 方法。 + +**方法定义:** + +```go +// Tf translates, formats and returns the with configured language +// and given . +func Tf(ctx context.Context, format string, values ...interface{}) string +``` + +我们来看一个简单的示例。 + +#### 1)目录结构 + +```bash +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +#### 2) **转译文件** + +`en.toml` + +```bash +OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f." +``` + +`zh-CN.toml ` + +```bash +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +``` + +#### 3) **示例代码** + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + orderId = 865271654 + orderAmount = 99.8 + ) + + i18n := gi18n.New() + i18n.SetLanguage("en") + fmt.Println(i18n.Tf(ctx, `{#OrderPaid}`, orderId, orderAmount)) + + i18n.SetLanguage("zh-CN") + fmt.Println(i18n.Tf(ctx, `{#OrderPaid}`, orderId, orderAmount)) +} +``` + +执行后,终端输出为: + +```bash +You have successfully complete order #865271654 payment, paid amount: ¥99.80. +您已成功完成订单号 #865271654 支付,支付金额¥99.80。 +``` +:::note +为方便演示,该示例中对支付金额的处理比较简单,在实际项目中往往需要在业务代码中对支付金额的单位按照区域做自动转换,再渲染 `i18n` 显示内容。 +::: +### 上下文设置转译语言 + +我们将上面的示例做些改动来演示。 + +#### 1)目录结构 + +```bash +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +#### 2)转译文件 + +`en.toml` + +```bash +OrderPaid = "You have successfully complete order #%d payment, paid amount: ¥%0.2f." +``` + +`zh-CN.toml` + +```bash +OrderPaid = "您已成功完成订单号 #%d 支付,支付金额¥%.2f。" +``` + +#### 3)示例代码 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + orderId = 865271654 + orderAmount = 99.8 + ) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `en`), + `{#OrderPaid}`, orderId, orderAmount, + )) + fmt.Println(g.I18n().Tf( + gi18n.WithLanguage(context.TODO(), `zh-CN`), + `{#OrderPaid}`, orderId, orderAmount, + )) +} +``` + +执行后,终端输出为: + +```bash +You have successfully complete order #865271654 payment, paid amount: ¥99.80. +您已成功完成订单号 #865271654 支付,支付金额¥99.80。 +``` +:::note +为方便演示,该示例中对支付金额的处理比较简单,在实际项目中往往需要在业务代码中对支付金额的单位按照区域做自动转换,再渲染 `i18n` 显示内容。 +::: +## `I18N` 与视图引擎 + +`gi18n` 默认已经集成到了 `GoFrame` 框架的视图引擎中,直接在模板文件/内容中使用 `gi18n` 的关键字标签即可。我们同样可以通过上下文变量的形式来设置当前请求的转译语言。 +:::tip +此外,我们也可以通过设置模板变量 `I18nLanguage` 设置当前模板的解析语言,该变量可以控制不同的模板内容按照不同的国际化语言进行解析。 +::: +使用示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(func(r *ghttp.Request) { + r.SetCtx(gi18n.WithLanguage(r.Context(), r.Get("lang", "zh-CN").String())) + r.Middleware.Next() + }) + group.ALL("/", func(r *ghttp.Request) { + r.Response.WriteTplContent(`{#hello}{#world}!`) + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,访问以下页面,将会输出: + +1. [http://127.0.0.1:8199](http://127.0.0.1:8199/) + +```bash +你好世界! +``` + +2. [http://127.0.0.1:8199/?lang=ja](http://127.0.0.1:8199/?lang=ja) + +```bash +こんにちは世界! +``` diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..e4fc6f16377 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,78 @@ +--- +slug: '/docs/core/gi18n-config' +title: 'I18N国际化-配置管理' +sidebar_position: 0 +hide_title: true +keywords: [I18N国际化,配置管理,GoFrame框架,文件格式,国际化组件,配置文件,目录结构,资源管理器,智能识别,语言码] +description: 'I18N国际化组件是GoFrame框架的核心组件之一,支持多种配置文件格式如xml、ini、yaml、toml等,并兼容多种目录结构和文件格式,开发者可通过配置实现多语言支持。系统自动识别语言文件,支持自定义路径设置,推荐使用标准国际化语言码进行文件命名,确保程序多语言兼容性。' +--- + +作为框架的核心组件之一,传承 `goframe` 一贯的便捷化设计思想, `I18N` 国际化组件的配置管理非常简便。 + +## 文件格式 + +`gi18n` 国际化组件支持框架通用的五种配置文件格式: `xml/ini/yaml/toml/json/properties`(更详细的文件支持列表具体以配置管理章节为主: [配置管理](../配置管理/配置管理.md))。同样的,和配置管理模块一样,框架推荐使用 `toml` 文件格式。 + +## 读取路径 + +默认情况下 `gi18n` 会自动查找并读取当前项目源码根目录(或者当前 `PWD` 运行目录下)下的以下目录: + +- `manifest/i18n` +- `i18n` + +默认将查找到的目录作为国际化转译文件存储目录。开发者也可以通过 `SetPath` 方法自定义 `i18n` 文件的存储目录路径。 + +## 文件存储 + +在 `i18n` 目录下可以直接按照国际化名称命名的文件如: `en.toml`/ `ja.toml`/ `zh-CN.toml`;也可以给定国际化名称目录,目录下随意自定义配置文件,如: `en/editor.toml`/ `en/user.toml`、 `zh-CN/editor.toml`/ `zh-CN/user.toml`。您使用纯文件管理或者增加一级目录管理都是可以的, `gi18n` 可以智能识别加载。 +:::tip +国际化的文件/目录名称都是开发者可自行定义和维护的,该名称主要用于程序中设置和使用。建议按照标准化的国际化地区语言码进行命名,具体请参考WIKI: [https://zh.wikipedia.org/wiki/ISO\_639-1](https://zh.wikipedia.org/wiki/ISO_639-1) +::: +例如,以下的 `i18n` 目录结构以及文件格式都是支持的。 + +**通过单独的 `i18n` 文件区分不同的语言** + +``` +└── i18n + ├── en.toml + ├── ja.toml + ├── ru.toml + ├── zh-CN.toml + └── zh-TW.toml +``` + +**通过不同的目录名称区分不同的语言** + +``` +└── i18n + ├── en + │ ├── hello.toml + │ └── world.toml + ├── ja + │ ├── hello.yaml + │ └── world.yaml + ├── ru + │ ├── hello.ini + │ └── world.ini + ├── zh-CN + │ ├── hello.json + │ └── world.json + └── zh-TW + ├── hello.xml + └── world.xml +``` + +**不同语言可以存在不同文件格式** + +``` +└── i18n + ├── en.toml + ├── ja.yaml + ├── ru.ini + ├── zh-CN.json + └── zh-TW.xml +``` + +## 资源管理器 + +`gi18n` 默认支持资源管理器(详情请参考章节: [资源管理](../资源管理/资源管理.md)),默认情况下会从 `gres` 配置管理器中检索 `manifest/i18n` 及 `i18n` 目录,或者开发者设置的 `i18n` 目录路径。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" new file mode 100644 index 00000000000..3809cf92820 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/I18N\345\233\275\351\231\205\345\214\226/I18N\345\233\275\351\231\205\345\214\226.md" @@ -0,0 +1,29 @@ +--- +slug: '/docs/core/gi18n' +title: 'I18N国际化' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,I18N,国际化,gi18n模块,Go语言,编程框架,开源项目,软件开发,多语言支持] +description: 'GoFrame框架中的I18N国际化组件,它由gi18n模块实现。通过导入相关模块,开发者能够在软件项目中轻松实现多语言支持,从而提高应用的国际化能力。详细接口文档可通过提供的网址查看,以获得更多技术细节。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了常用的 `I18N` 国际化组件,由 `gi18n` 模块实现。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/i18n/gi18n" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/i18n/gi18n](https://pkg.go.dev/github.com/gogf/gf/v2/i18n/gi18n) + + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" new file mode 100644 index 00000000000..f608237ab5c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-Parser\350\247\243\346\236\220.md" @@ -0,0 +1,81 @@ +--- +slug: '/docs/core/gcmd-parser' +title: '命令管理-Parser解析' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令行解析,gcmd,Parser解析,Go开发,代码示例,选项管理,命令行选项,开发文档] +description: 'GoFrame框架的命令行解析功能,重点讲解了gcmd组件的Parser解析方法。通过自定义选项名称和数值解析,能够高效管理和解析命令行选项。包括代码示例和详细的接口文档,帮助开发者理解和应用于实际项目中。' +--- + +## 基本介绍 + +命令行解析最主要的是针对于选项的解析, `gcmd` 组件提供了 `Parse` 方法,用于自定义解析选项,包括有哪些选项名称,每个选项是否带有数值。根据这一配置便可将所有的参数和选项进行解析归类。 +:::tip +大部分场景下,我们并不需要显式创建 `Parser` 对象,因为我们有层级管理以及对象管理方式来管理多命令。但底层仍然是采用 `Parser` 方式实现,因此本章节大家了解原理即可。 +::: +相关方法: + +更多 `Parser` 方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd#Parser](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd#Parser) + +```go +func Parse(supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) +func ParseWithArgs(args []string, supportedOptions map[string]bool, option ...ParserOption) (*Parser, error) +func ParserFromCtx(ctx context.Context) *Parser +func (p *Parser) GetArg(index int, def ...string) *gvar.Var +func (p *Parser) GetArgAll() []string +func (p *Parser) GetOpt(name string, def ...interface{}) *gvar.Var +func (p *Parser) GetOptAll() map[string]string +``` + +其中, `ParserOption` 如下: + +```go +// ParserOption manages the parsing options. +type ParserOption struct { + CaseSensitive bool // Marks options parsing in case-sensitive way. + Strict bool // Whether stops parsing and returns error if invalid option passed. +} +``` + +解析示例: + +```go +parser, err := gcmd.Parse(g.MapStrBool{ + "n,name": true, + "v,version": true, + "a,arch": true, + "o,os": true, + "p,path": true, +}) +``` + +可以看到,选项输入参数其实是一个 `map` 类型。其中键值为选项名称,同一个选项的不同名称可以通过 `,` 符号进行分隔。比如,该示例中 `n` 和 `name` 选项是同一个选项,当用户输入 `-n john` 的时候, `n` 和 `name` 选项都会获得到数据 `john`。 + +而键值是一个布尔类型,标识该选项是否需要解析参数。这一选项配置是非常重要的,因为有的选项是不需要获得数据的,仅仅作为一个标识。例如, `-f force` 这个输入,在需要解析数据的情况下,选项 `f` 的值为 `force`;而在不需要解析选项数据的情况下,其中的 `force` 便是命令行的一个参数,而不是选项。 + +## 使用示例 + +```go +func ExampleParse() { + os.Args = []string{"gf", "build", "main.go", "-o=gf.exe", "-y"} + p, err := gcmd.Parse(g.MapStrBool{ + "o,output": true, + "y,yes": false, + }) + if err != nil { + panic(err) + } + fmt.Println(p.GetOpt("o")) + fmt.Println(p.GetOpt("output")) + fmt.Println(p.GetOpt("y") != nil) + fmt.Println(p.GetOpt("yes") != nil) + fmt.Println(p.GetOpt("none") != nil) + + // Output: + // gf.exe + // gf.exe + // true + // true + // false +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..46e0014de00 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\221\275\344\273\244\350\241\214\345\257\271\350\261\241.md" @@ -0,0 +1,202 @@ +--- +slug: '/docs/core/gcmd-command' +title: '命令管理-命令行对象' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,命令行管理,gcmd,Command对象,回调方法,Parser解析,启动命令,层级命令,子命令,帮助信息] +description: '使用GoFrame框架中的gcmd库管理命令行对象和命令,涵盖了Command对象的定义、回调方法的使用以及命令的层级管理。同时,提供了在GoFrame框架下启动HTTP和gRPC服务的命令行实现示例,展示了如何为命令增加子命令,并自动生成帮助信息。' +--- + +## 基本介绍 + +大部分场景下,我们通过 `Command` 命令行对象来管理单个或多个命令,并且使用默认的命令行解析规则(不用显式使用 `Parser` 解析器)即可。 `Command` 对象定义如下: + +详细请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd@master#Command](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd@master#Command) + +```go +// Command holds the info about an argument that can handle custom logic. +type Command struct { + Name string // Command name(case-sensitive). + Usage string // A brief line description about its usage, eg: gf build main.go [OPTION] + Brief string // A brief info that describes what this command will do. + Description string // A detailed description. + Arguments []Argument // Argument array, configuring how this command act. + Func Function // Custom function. + FuncWithValue FuncWithValue // Custom function with output parameters that can interact with command caller. + HelpFunc Function // Custom help function + Examples string // Usage examples. + Additional string // Additional info about this command, which will be appended to the end of help info. + Strict bool // Strict parsing options, which means it returns error if invalid option given. + Config string // Config node name, which also retrieves the values from config component along with command line. + parent *Command // Parent command for internal usage. + commands []*Command // Sub commands of this command. +} +``` + +由于对象均有详细的注释,这里不再赘述。 + +## 回调方法 + +`Command` 对象支持 `3` 个回调方法: + +- `Func` 我们一般需要自定义这个回调方法,用于实现当前命令执行的操作。 +- `FuncWithValue` 方法同 `Func`,只不过支持返回值,往往用于命令行相互调用的场景,一般项目用不到。 +- `HelpFunc` 自定义帮助信息,一般来说没有太大必要,因为 `Command` 对象能够自动生成帮助信息。 + +我们主要关注 `Func` 回调方法即可,其他方法大家感兴趣可以自行研究。 + +## `Func` 回调 + +方法定义: + +```go +// Function is a custom command callback function that is bound to a certain argument. +type Function func(ctx context.Context, parser *Parser) (err error) +``` + +可以看到,在回调方法内部,我们通过 `parser` 对象获取解析参数和选项,并通过返回 `error` 来告诉上层调用方法是否执行成功。 + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "start http server", + Description: "this is the command entry for starting your http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello world") + }) + s.SetPort(8199) + s.Run() + return + }, + } +) + +func main() { + Main.Run(gctx.New()) +} +``` + +这也是大部分项目的启动命令行对象的样子,大部分项目只有一个启动入口,并且只会有一个回调方法实现。 + +## 帮助信息生成 + +`Command` 对象虽然可以自定义 `HelpFunc` 帮助回调方法,但 `Command` 对象可以自动生成 `Help` 使用帮助信息,大部分场景下无需自定义。并且 `gcmd` 组件默认内置了支持了 `h/help` 选项,因此使用 `gcmd` 组件的程序可以通过这两个选项自动生成 `Help` 帮助信息。 + +我们来看一个例子,我们先通过 `go build main.go` 把上面的例子编译为二进制 `main` 文件,然后来简单看一下只有一个命令场景下自动生成的帮助信息: + +```bash +$ ./main -h +USAGE + main [OPTION] + +DESCRIPTION + this is the command entry for starting your http server +``` + +## 层级命令管理 + +### 父命令与子命令 + +一个 `Command` 命令可以添加子级命令,当 `Command` 存在子级命令时,自己便成为了父级命令。子级命令也可以添加自己的子级命令,以此类推,形成层级命令关系。父级命令和子级命令都可以有自己的回调方法,不过大部分场景下,一旦 `Command` 成为了父级命令,回调方法往往都没有太大存在的必要。我们通常通过 `AddCommand` 方法为 `Command` 添加子级命令: + +```go +// AddCommand adds one or more sub-commands to current command. +func (c *Command) AddCommand(commands ...*Command) error +``` + +### 层级命令使用示例 + +我们来演示一个多命令管理的示例。我们将上面的例子改进一下,增加两个子级命令。 + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "start http server", + Description: "this is the command entry for starting your process", + } + Http = &gcmd.Command{ + Name: "http", + Brief: "start http server", + Description: "this is the command entry for starting your http server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + fmt.Println("start http server") + return + }, + } + Grpc = &gcmd.Command{ + Name: "grpc", + Brief: "start grpc server", + Description: "this is the command entry for starting your grpc server", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + fmt.Println("start grpc server") + return + }, + } +) + +func main() { + err := Main.AddCommand(Http, Grpc) + if err != nil { + panic(err) + } + Main.Run(gctx.New()) +} +``` + +可以看到,我们通过 `AddCommand` 命令为主命令增加了两个子级命令 `http/grpc`,分别用于开启 `http/grpc` 服务。当存在子级命令式,父命令往往便没有 `Func` 回调定义的必要了,因此我们这里去掉了 `main` 命令的 `Func` 定义。 + +我们编译后来执行一下看看效果: + +```bash +$ main +USAGE + main COMMAND [OPTION] + +COMMAND + http start http server + grpc start grpc server + +DESCRIPTION + this is the command entry for starting your process +``` + +使用 `http` 命令: + +```bash +$ main http +start http server +``` + +使用 `grpc` 命令: + +```bash +$ main grpc +start grpc server +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" new file mode 100644 index 00000000000..397f2395b1a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\346\234\254\346\246\202\345\277\265.md" @@ -0,0 +1,68 @@ +--- +slug: '/docs/core/gcmd-intro' +title: '命令管理-基本概念' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令行参数,选项管理,参数和选项,命令行解析,gcmd组件,命令行优化,选项位置,gf命令] +description: 'GoFrame框架中的命令行参数和选项的基本概念,讨论了参数与选项的定义、区别及其在命令行中的表现方式。详细解释了选项与参数的解析规则,包括使用等号连接选项与数据的情况。并提供了gcmd组件在GoFrame中的实现细节和使用示例,以帮助用户更好地管理命令行输入。' +--- + +## 基本概念 + +### 参数( `Argument`) + +程序命令行按照顺序进行传递、没有名称标识的数据叫做参数( `Argument`),参数的输入具有顺序性。 + +### 选项( `Option`) + +控制程序逻辑的附加输入、并且具有名称标识的数据叫做选项( `Options`),选项名称以 `-` 或者 `--` 字符串作为前缀,选项是无序的,可以放置于命令行中任意位置。选项可以带有数据,也可以不带数据。在其他类似的第三方功能组件里面,选项的功能类似于 **标识**( `Flag`)。 + +此外,根据传统的命令行管理习惯,选项可以设置缩写别名( `Short`),用于简化命令行参数输入。缩写别名往往设定为一个单字母。 + +## 选项位置与 `=` 号 + +`gcmd` 组件支持选项的位置在命令行中是任意的,也就是说,以下命令行选项输入其实意义是一样的: + +```bash +gf build main.go -a amd64 -o linux -n app -yes +gf -a amd64 -o linux build main.go -yes -n app +gf -yes -n app build -o linux -a amd64 main.go +``` + +其中, + +- `gf`/ `build`/ `main.go` 是参数,索引分别为 `0`, `1`, `2`;因为参数是有序性的,因此无论命令行怎么修改,这三者的顺序却无法改变 +- `a`/ `o`/ `n` 是带有数据的选项,由于是顺序无关的,通过选项名称获取数据,因此可以随意放置位置 +- `yes` 是不带数据的选项,也可以随意放置位置 + +命令行的选项与数据之间可以通过空格,也可以通过 `=` 符号进行连接,如: + +```bash +gf build main.go -a=amd64 -o=linux -n=app -yes +``` + +## 默认解析规则 + +由于 `gcmd` 模块提供了一些包方法用以获取默认的命令行解析规则。在默认规则下,将会自动识别参数与选项。 + +### 命令行中带有 `=` 符号的场景下 + +```bash +gf build main.go -a=amd64 -o=linux -n=app -yes +``` + +在默认规则下, + +- `gf`/ `build`/ `main.go` 是参数,索引分别为 `0`, `1`, `2。` +- `a`/ `o`/ `n`/ `yes` 将会被解析为选项,并且 `yes` 为无数据选项。 + +### 不使用 `=` 符号来连接选项参数 + +```bash +gf build main.go -a amd64 -o linux -n app -yes +``` + +在默认规则下, + +- `gf`/ `build`/ `main.go` 是参数,索引分别为 `0`, `1`, `2。` +- `a`/ `o`/ `n`/ `yes` 将会被解析为选项,并且 `yes` 为无数据选项。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..08f7e92fecc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\345\237\272\347\241\200\346\226\271\346\263\225.md" @@ -0,0 +1,125 @@ +--- +slug: '/docs/core/gcmd-funcs' +title: '命令管理-基础方法' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令管理,gcmd组件,基础方法,命令行参数,命令行选项,参数获取,选项获取,自定义命令行] +description: 'GoFrame框架中的gcmd组件基本功能,包括如何获取命令行参数和选项及其常用方法。通过示例讲解如何使用Init方法自定义命令行数据,以及如何利用GetArg和GetOpt方法分别获取命令行参数和选项,详细展示了参数获取和选项获取的实现,帮助开发者快速掌握GoFrame中的命令管理功能。' +--- + +`gcmd` 组件提供了常用的基础包方法,可以按照默认的解析规则,直接获取命令行参数及选项。 + +## 常用方法 + +更多组件方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd) + +```go +func Init(args ...string) + +func GetArg(index int, def ...string) *gvar.Var +func GetArgAll() []string + +func GetOpt(name string, def ...string) *gvar.Var +func GetOptAll() map[string]string +``` + +## `Init` 自定义命令行 + +默认情况下, `gcmd` 组件会自动从 `os.Args` 解析获取参数及数据。我们可以通过 `Init` 方法自定义命令行数据。使用示例: + +```go +func ExampleInit() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetArgAll()) + + // Output: + // []string{"gf", "build", "main.go"} +} +``` + +## `GetArg*` 参数获取 + +参数获取可以通过以下两个方法: + +1. `GetArg` 方法用以获取默认解析的命令行参数,参数通过输入索引位置获取,索引位置从 `0` 开始,但往往我们需要获取的参数是从 `1` 开始,因为索引 `0` 的参数是程序名称。 +2. `GetArgAll` 方法用于获取所有的命令行参数。 + +使用示例: + +```go +func ExampleGetArg() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf( + `Arg[0]: "%v", Arg[1]: "%v", Arg[2]: "%v", Arg[3]: "%v"`, + gcmd.GetArg(0), gcmd.GetArg(1), gcmd.GetArg(2), gcmd.GetArg(3), + ) + + // Output: + // Arg[0]: "gf", Arg[1]: "build", Arg[2]: "main.go", Arg[3]: "" +} + +func ExampleGetArgAll() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetArgAll()) + + // Output: + // []string{"gf", "build", "main.go"} +} +``` + +## `GetOpt*` 选项获取 + +选项获取可以通过以下两个方法: + +1. `GetOpt` 方法用以获取默认解析的命令行选项,选项通过名称获取,并且选项的输入没有顺序性,可以输入到任意的命令行位置。当给定名称的选项数据不存在时,返回 `nil`。注意判断不带数据的选项是否存在时,可以通过 `GetOpt(name) != nil` 方式。 +2. `GetOptAll` 方法用于获取所有的选项。 + +使用示例: + +```go +func ExampleGetOpt() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf( + `Opt["o"]: "%v", Opt["y"]: "%v", Opt["d"]: "%v"`, + gcmd.GetOpt("o"), gcmd.GetOpt("y"), gcmd.GetOpt("d", "default value"), + ) + + // Output: + // Opt["o"]: "gf.exe", Opt["y"]: "", Opt["d"]: "default value" +} + +func ExampleGetOptAll() { + gcmd.Init("gf", "build", "main.go", "-o=gf.exe", "-y") + fmt.Printf(`%#v`, gcmd.GetOptAll()) + + // May Output: + // map[string]string{"o":"gf.exe", "y":""} +} +``` + +## `GetOptWithEnv` 特性 + +```go +func GetOptWithEnv(key string, def ...interface{}) *gvar.Var +``` + +该方法用于获取命令行中指定的选项数值,如果该选项不存在时,则从环境变量中读取。但是两者的名称规则会不一样。例如: `gcmd.GetOptWithEnv("gf.debug")` 将会优先去读取命令行中的 `gf.debug` 选项,当不存在时,则会去读取 `GF_DEBUG` 环境变量。 + +需要注意的是参数命名转换规则: + +- 环境变量会将名称转换为大写,名称中的 `.` 字符转换为 `_` 字符。 +- 命令行中会将名称转换为小写,名称中的 `_` 字符转换为 `.` 字符。 + +使用示例: + +```go +func ExampleGetOptWithEnv() { + fmt.Printf("Opt[gf.test]:%s\n", gcmd.GetOptWithEnv("gf.test")) + _ = genv.Set("GF_TEST", "YES") + fmt.Printf("Opt[gf.test]:%s\n", gcmd.GetOptWithEnv("gf.test")) + + // Output: + // Opt[gf.test]: + // Opt[gf.test]:YES +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" new file mode 100644 index 00000000000..39b96266314 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\210\347\253\257\344\272\244\344\272\222.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gcmd-scan' +title: '命令管理-终端交互' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令管理,终端交互,gcmd组件,用户输入,Scan方法,Scanf方法,终端读取,交互示例] +description: 'GoFrame框架中的gcmd组件,用于在终端环境中实现与用户的交互。主要提供了Scan和Scanf两个核心方法,通过这些方法可以便捷地从终端读取用户输入,并在命令行界面中进行交互展示。这些实用功能适用于需要与用户数据输入交互的命令行程序开发。' +--- + +## 基本介绍 + +`gcmd` 组件支持从终端读取用户输入数据,常用于终端交互场景。 + +相关方法: + +```go +func Scan(info ...interface{}) string +func Scanf(format string, info ...interface{}) string +``` + +这两个方法都是向终端展示给定的信息,自动读取终端用户输入的信息返回,通过回车符号返回。 + +## `Scan` 使用 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" +) + +func main() { + name := gcmd.Scan("What's your name?\n") + fmt.Println("Your name is:", name) +} +``` + +执行后,交互示例: + +``` +> What's your name? +john +> Your name is: john +``` + +## `Scanf` 使用 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcmd" +) + +func main() { + name := gcmd.Scan("> What's your name?\n") + age := gcmd.Scanf("> Hello %s, how old are you?\n", name) + fmt.Printf("> %s's age is: %s", name, age) +} +``` + +执行后,交互示例: + +``` +> What's your name? +john +> Hello john, how old are you? +18 +> john's age is: 18 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..a5d30e34818 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\347\273\223\346\236\204\345\214\226\345\217\202\346\225\260.md" @@ -0,0 +1,240 @@ +--- +slug: '/docs/core/gcmd-struct' +title: '命令管理-结构化参数' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令行管理,结构化参数,自动数据转换,参数校验,命令行工具,数据类型转换,配置读取,框架开发] +description: '通过GoFrame框架实现命令行管理的结构化参数处理。通过对象化管理父级及子级命令,定义规范化的输入参数对象,实现命令行的自动数据转换及校验功能。通过GoFrame的框架开发工具,用户可以轻松管理多个命令行项目,支持从配置中读取数据,提升项目的开发效率和稳定性。' +--- + +## 命令行管理痛点 + +前面我们介绍的命令行管理,都是通过回调函数的 `parser` 对象获取解析的参数及选项数据,在使用的时候存在以下痛点: + +- 需要手动传入硬编码的参数索引或者选项名称信息来获取数据 +- 难以定义参数/选项的说明介绍 +- 难以定义参数/选项的数据类型 +- 难以对参数/选项进行通用性的数据校验 +- 对于需要管理大量命令行的项目是个灾难 + +## 对象化管理命令 + +我们来一个最简单的结构化管理参数示例。我们将前面介绍过的 `Command` 示例改造为结构化管理: + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +type cMain struct { + g.Meta `name:"main"` +} + +type cMainHttpInput struct { + g.Meta `name:"http" brief:"start http server"` +} +type cMainHttpOutput struct{} + +type cMainGrpcInput struct { + g.Meta `name:"grpc" brief:"start grpc server"` +} +type cMainGrpcOutput struct{} + +func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) { + fmt.Println("start http server") + return +} + +func (c *cMain) Grpc(ctx context.Context, in cMainGrpcInput) (out *cMainGrpcOutput, err error) { + fmt.Println("start grpc server") + return +} + +func main() { + cmd, err := gcmd.NewFromObject(cMain{}) + if err != nil { + panic(err) + } + cmd.Run(gctx.New()) +} +``` + +可以看到,我们通过对象的形式来管理父级命令,通过方法的形式来管理其下一层级的子级命令,并通过规范化的 `Input` 输入参数对象来定义子级命令的描述/参数/选项。大部分场景下,大家可以忽略 `Output` 返回对象的使用,但为规范化及扩展性需要保留,如果未用到,该返回参数直接返回 `nil` 即可。关于其中的结构体标签,后续会有介绍。 + +我们将示例代码编译后,执行查看效果: + +```bash +$ main +USAGE + main COMMAND [OPTION] + +COMMAND + http start http server + grpc start grpc server + +DESCRIPTION + this is the command entry for starting your process +``` + +使用 `http` 命令: + +```bash +$ main http +start http server +``` + +使用 `grpc` 命令: + +```bash +$ main grpc +start grpc server +``` + +效果和前面介绍的示例一致。 + +## 结构化管理入参 + +既然命令行通过对象化管理,我们仔细看看参数/选项是如何通过结构化管理。 + +我们将上面的实例简化一下,来个简单的例子,实现通过 `http` 命令开启 `http` 服务: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +type cMain struct { + g.Meta `name:"main" brief:"start http server"` +} + +type cMainHttpInput struct { + g.Meta `name:"http" brief:"start http server"` + Name string `v:"required" name:"NAME" arg:"true" brief:"server name"` + Port int `v:"required" short:"p" name:"port" brief:"port of http server"` +} +type cMainHttpOutput struct{} + +func (c *cMain) Http(ctx context.Context, in cMainHttpInput) (out *cMainHttpOutput, err error) { + s := g.Server(in.Name) + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello world") + }) + s.SetPort(in.Port) + s.Run() + return +} + +func main() { + cmd, err := gcmd.NewFromObject(cMain{}) + if err != nil { + panic(err) + } + cmd.Run(gctx.New()) +} +``` + +我们为 `http` 命令定义了两个输入参数: + +- `NAME` 服务的名称,通过参数输入。这里使用了大写形式,方便展示在自动生成的帮助信息中 +- `port` 服务的端口,通过 `p/port` 选项输入 + +并且我们通过 `v:"required"` 校验标签为这两个参数都绑定的必需的校验规则。是的,在 `GoFrame` 框架中,只要涉及到校验的地方都使用了统一的校验组件,具体请参考章节: [数据校验](../数据校验/数据校验.md) + +我们编译后执行看看效果: + +```bash +$ main http +arguments validation failed for command "http": The Name field is required +1. arguments validation failed for command "http" + 1). github.com/gogf/gf/v2/os/gcmd.newCommandFromMethod.func1 + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_object.go:290 + 2). github.com/gogf/gf/v2/os/gcmd.(*Command).doRun + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:120 + 3). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValueError + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:77 + 4). github.com/gogf/gf/v2/os/gcmd.(*Command).RunWithValue + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:32 + 5). github.com/gogf/gf/v2/os/gcmd.(*Command).Run + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcmd/gcmd_command_run.go:26 + 6). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/test.go:38 +2. The Name field is required +``` + +执行后,报错了,这个错误来自于数据校验,表示必须参数( `Name/Port`)必须传递。 +:::tip +这里的报错打印了堆栈信息,因为 `GoFrame` 框架采用了全错误堆栈设计,所有组件错误都会带有自底向上的错误堆栈,以方便错误快速定位。当然我们可以通过 `RunWithError` 方法获取返回的错误对象关闭堆栈信息。 +::: +我增加参数输入再试试: + +```bash +$ main http my-http-server -p 8199 +2022-01-19 22:52:45.808 [DEBU] openapi specification is disabled + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + my-http-server | default | :8199 | ALL | / | main.(*cMain).Http.func1 | +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + my-http-server | default | :8199 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +-----------------|---------|---------|--------|-------|-----------------------------------------------------------------|-------------------- + +2022-01-19 22:52:45.810 66292: http server started listening on [:8199] +``` + +是的,这就对了。 + +## 完整使用案例 + +`GoFrame` 框架的开发工具普通使用了对象化、结构化的命令行管理,大家感兴趣可以更进一步查看源码了解: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) + +![](/markdown/fa0dce21882f5ac6fb8014b287122e73.png) + +## 预定义的标签 + +在结构化设计中,我们使用了一些结构体标签,大部分来源于 `Command` 命令的属性,这里我们来介绍一下: + +| 标签 | 缩写 | 说明 | 注意事项 | +| --- | --- | --- | --- | +| `name` | - | 命名名称 | 如果是输入参数结构体,在未指定 `name` 时将会自动读取 **方法名称** 作为 `name` | +| `short` | - | 命令缩写 | | +| `usage` | - | 命令使用 | | +| `brief` | - | 命令描述 | | +| `arg` | - | 表示该输入参数来源于参数而不是选项 | 仅用于属性标签 | +| `orphan` | - | 表示该选项不带参数 | 属性通常为 `bool` 类型 | +| `description` | `dc` | 命令的详细介绍 | | +| `additional` | `ad` | 命令的额外描述信息 | | +| `examples` | `eg` | 命令的使用示例 | | +| `root` | - | 指定子级命令名称是父级命令,其他方法是它的子级命令 | 仅用于 **主命令** 对象结构体 `Meta` 标签 | +| `strict` | - | 表示该命令严格解析参数/选项,当输入不支持的参数/选项时,返回错误 | 仅用于对象结构体 `Meta` 标签 | +| `config` | - | 表示该命令的选项数据支持从指定的配置读取,配置来源于默认的全局单例配置对象 | 仅用于方法输入结构体 `Meta` 标签 | +| `default` | `d` | 表示该输入参数不传递时使用默认值 | 仅用于属性标签 | + +## 高级特性 + +### 自动数据转换 + +结构化的参数输入支持自动的数据类型转换,您只需要定义好数据类型,其他的事情交给框架组件即可。自动数据类型转换出现在框架的很多组件中,特别是 `HTTP/GRPC` 服务的参数输入中。底层数据转换组件使用的是: [类型转换](../类型转换/类型转换.md) +:::tip +命令行参数的数据转换采用 **不区分大小写、且忽略特殊字符** 的规则来匹配属性字段。例如,如果入参结构体中存在 `Name` 字段属性,无论命令行输入 `name` 还是 `NAME` 的命名参数,都将能被 `Name` 字段属性接收。 +::: +### 自动数据校验 + +同样的,数据校验组件也是使用的统一的组件,具体请参考章节: [数据校验](../数据校验/数据校验.md) + +### 从配置读取数据 + +当命令行中没有传递对应的数据时,输入参数的结构体数据支持从配置组件中自动获取,只需要在 `Meta` 中设置 `config` 标签即可,配置来源于默认的全局单例配置对象。具体示例可以参考 `GoFrame` 框架开发工具源码: [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..dc84d7dc7f3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/core/gcmd-tracing' +title: '命令管理-链路跟踪' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令管理,链路跟踪,OpenTelemetry,主进程,子进程,gcmd,gproc,gctx] +description: '使用GoFrame框架的命令管理组件进行链路跟踪。通过OpenTelemetry规范,使用GoFrame可以实现跨进程的链路跟踪,特别适用于临时运行的进程。示例中展示了如何通过主进程调用子进程,并自动传递链路信息。' +--- + +## 基本介绍 + +`GoFrame` 命令管理组件也支持跨进程的链路跟踪特性,特别是对于一些临时运行的进程特别有用。 + +框架整体的链路跟踪都是采用的 `OpenTelemetry` 规范。 + +## 使用示例 + +### 主进程 + +`main.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +var ( + Main = &gcmd.Command{ + Name: "main", + Brief: "main process", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + g.Log().Debug(ctx, `this is main process`) + return gproc.ShellRun(ctx, `go run sub.go`) + }, + } +) + +func main() { + Main.Run(gctx.GetInitCtx()) +} +``` + +### 子进程 + +`sub.go` + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcmd" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + Sub = &gcmd.Command{ + Name: "sub", + Brief: "sub process", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + g.Log().Debug(ctx, `this is sub process`) + return nil + }, + } +) + +func main() { + Sub.Run(gctx.GetInitCtx()) +} +``` + +### 执行结果 + +执行后,终端输出如下: + +```bash +$ go run main.go +2022-06-21 20:35:06.196 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is main process +2022-06-21 20:35:07.482 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is sub process +``` + +可以看到,链路信息已经自动传递给了子进程。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..bc2388bb12c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\221\275\344\273\244\347\256\241\347\220\206/\345\221\275\344\273\244\347\256\241\347\220\206.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/core/gcmd' +title: '命令管理' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcmd,命令行管理,命令行参数,命令行解析,Cobra,参数校验,链路跟踪,命令行帮助] +description: 'GoFrame框架中的命令行管理组件gcmd。gcmd提供了强大的命令行管理功能,包括灵活的参数管理、自定义解析、多层级命令管理、自动类型转换、参数校验、读取配置组件参数、链路跟踪和自动生成命令行帮助信息等特性。' +--- + +## 基本介绍 + +程序需要通过命令行来管理程序启动入口,因此命令行管理组件也是框架的核心组件之一。 `GoFrame` 框架提供了强大的命令行管理模块,由 `gcmd` 组件实现。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gcmd" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcmd) + +## 组件特性 + +`gcmd` 组件具有以下显著特性: + +- 使用简便、功能强大 +- 命令行参数管理灵活 +- 支持灵活的 `Parser` 命令行自定义解析 +- 支持多层级的命令行管理、更丰富的命令行信息 +- 支持对象模式结构化输入/输出管理大批量命令行 +- 支持参数结构化自动类型转换、自动校验 +- 支持参数结构化从配置组件读取数据 +- 支持自动生成命令行帮助信息 +- 支持终端录入功能 + +## 与 `Cobra` 比较 + +`Cobra` 是 `Golang` 中使用比较广泛的命令行管理库,开源项目地址: [https://github.com/spf13/cobra](https://github.com/spf13/cobra) + +`GoFrame` 框架的 `gcmd` 命令行组件与 `Cobra` 比较,基础的功能比较相似,但差别比较大的在于参数管理方式以及可观测性支持方面: + +- `gcmd` 组件支持结构化的参数管理,支持层级对象的命令行管理、方法自动生成命令,无需开发者手动定义手动解析参数变量。 +- `gcmd` 组件支持自动化的参数类型转换,支持基础类型以及复杂类型。 +- `gcmd` 组件支持可配置的常用参数校验能力,提高参数维护效率。 +- `gcmd` 组件支持没有终端传参时通过配置组件读取参数方式。 +- `gcmd` 组件支持链路跟踪,便于父子进程的链路信息传递。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..3d5dca6de0b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\345\257\271\350\261\241\347\256\241\347\220\206.md" @@ -0,0 +1,199 @@ +--- +slug: '/docs/core/g' +title: '对象管理' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,对象管理,数据类型,常用对象,单例模式,配置管理,日志管理,模板引擎,WEB服务器,Redis客户端] +description: 'GoFrame框架提供了一系列常用数据类型和对象获取方法,通过g模块轻松获取常用对象,包括HTTP客户端、数据校验、配置管理、日志管理、模板引擎、WEB服务器、数据库ORM、Redis客户端等,实现简便高效的对象管理,满足不同应用场景需求。' +--- + +`GoFrame` 框架封装了一些常用的数据类型以及对象获取方法,通过 `g.*` 方法获取。 +:::tip +`g` 是一个强耦合的模块,目的是为开发者在对频繁使用的类型/对象调用时提供便利。 +::: +**使用方式**: + +```go +import "github.com/gogf/gf/v2/frame/g" +``` + +## 数据类型 + +常用数据类型别名。 + +```go +type ( + Var = gvar.Var // Var is a universal variable interface, like generics. + Ctx = context.Context // Ctx is alias of frequently-used context.Context. +) + +type ( + Map = map[string]interface{} // Map is alias of frequently-used map type map[string]interface{}. + MapAnyAny = map[interface{}]interface{} // MapAnyAny is alias of frequently-used map type map[interface{}]interface{}. + MapAnyStr = map[interface{}]string // MapAnyStr is alias of frequently-used map type map[interface{}]string. + MapAnyInt = map[interface{}]int // MapAnyInt is alias of frequently-used map type map[interface{}]int. + MapStrAny = map[string]interface{} // MapStrAny is alias of frequently-used map type map[string]interface{}. + MapStrStr = map[string]string // MapStrStr is alias of frequently-used map type map[string]string. + MapStrInt = map[string]int // MapStrInt is alias of frequently-used map type map[string]int. + MapIntAny = map[int]interface{} // MapIntAny is alias of frequently-used map type map[int]interface{}. + MapIntStr = map[int]string // MapIntStr is alias of frequently-used map type map[int]string. + MapIntInt = map[int]int // MapIntInt is alias of frequently-used map type map[int]int. + MapAnyBool = map[interface{}]bool // MapAnyBool is alias of frequently-used map type map[interface{}]bool. + MapStrBool = map[string]bool // MapStrBool is alias of frequently-used map type map[string]bool. + MapIntBool = map[int]bool // MapIntBool is alias of frequently-used map type map[int]bool. +) + +type ( + List = []Map // List is alias of frequently-used slice type []Map. + ListAnyAny = []MapAnyAny // ListAnyAny is alias of frequently-used slice type []MapAnyAny. + ListAnyStr = []MapAnyStr // ListAnyStr is alias of frequently-used slice type []MapAnyStr. + ListAnyInt = []MapAnyInt // ListAnyInt is alias of frequently-used slice type []MapAnyInt. + ListStrAny = []MapStrAny // ListStrAny is alias of frequently-used slice type []MapStrAny. + ListStrStr = []MapStrStr // ListStrStr is alias of frequently-used slice type []MapStrStr. + ListStrInt = []MapStrInt // ListStrInt is alias of frequently-used slice type []MapStrInt. + ListIntAny = []MapIntAny // ListIntAny is alias of frequently-used slice type []MapIntAny. + ListIntStr = []MapIntStr // ListIntStr is alias of frequently-used slice type []MapIntStr. + ListIntInt = []MapIntInt // ListIntInt is alias of frequently-used slice type []MapIntInt. + ListAnyBool = []MapAnyBool // ListAnyBool is alias of frequently-used slice type []MapAnyBool. + ListStrBool = []MapStrBool // ListStrBool is alias of frequently-used slice type []MapStrBool. + ListIntBool = []MapIntBool // ListIntBool is alias of frequently-used slice type []MapIntBool. +) + +type ( + Slice = []interface{} // Slice is alias of frequently-used slice type []interface{}. + SliceAny = []interface{} // SliceAny is alias of frequently-used slice type []interface{}. + SliceStr = []string // SliceStr is alias of frequently-used slice type []string. + SliceInt = []int // SliceInt is alias of frequently-used slice type []int. +) + +type ( + Array = []interface{} // Array is alias of frequently-used slice type []interface{}. + ArrayAny = []interface{} // ArrayAny is alias of frequently-used slice type []interface{}. + ArrayStr = []string // ArrayStr is alias of frequently-used slice type []string. + ArrayInt = []int // ArrayInt is alias of frequently-used slice type []int. +) +``` + +## 常用对象 + +常用对象往往通过 `单例模式` 进行管理,可以根据不同的单例名称获取对应的对象实例,并在对象初始化时会自动检索获取配置文件中的对应配置项,具体配置项请查看对应对象的章节介绍。 +:::info +注意事项:在运行时阶段,每一次通过 `g` 模块获取单例对象时都会有内部全局锁机制来保证操作和数据的并发安全性,原理性上来讲在并发量大的场景下会存在锁竞争的情况,但绝大部分的业务场景下开发者均不需要太在意锁竞争带来的性能损耗。此外,开发者也可以通过将获取到的单例对象保存到特定的模块下的内部变量重复使用,以此避免运行时锁竞争情况。 +::: +### `HTTP` 客户端对象 + +```go +func Client() *ghttp.Client +``` + +创建一个新的 `HTTP` 客户端对象。 + +### `Validator` 校验对象 + +```go +func Validator() *gvalid.Validator +``` + +创建一个新的数据校验对象。 + +### (单例) 配置管理对象 + +```go +func Cfg(name ...string) *gcfg.Config +``` + +该单例对象将会自动按照文件后缀 `toml/yaml/yml/json/ini/xml/properties` 文自动检索配置文件。默认情况下会自动检索以下配置文件: + +- `config` +- `config.toml` +- `config.yaml` +- `config.yml` +- `config.json` +- `config.ini` +- `config.xml` +- `config.properties` + +并缓存,配置文件在外部被修改时将会自动刷新缓存。 + +为方便多文件场景下的配置文件调用,简便使用并提高开发效率,单例对象在创建时将会自动使用单例名称进行文件检索。例如: `g.Cfg("redis")` 获取到的单例对象将默认会自动检索以下文件: + +- `redis` +- `redis.toml` +- `redis.yaml` +- `redis.yml` +- `redis.json` +- `redis.ini` +- `redis.xml` +- `redis.properties` + +如果检索成功那么将该文件加载到内存缓存中,下一次将会直接从内存中读取;当该文件不存在时,则使用默认的配置文件( `config.toml`)。 + +### (单例) 日志管理对象 + +```go +func Log(name ...string) *glog.Logger +``` + +该单例对象将会自动读取默认配置文件中的 `logger` 配置项,并只会初始化一次日志对象。 + +### (单例) 模板引擎对象 + +```go +func View(name ...string) *gview.View +``` + +该单例对象将会自动读取默认配置文件中的 `viewer` 配置项,并只会初始化一次模板引擎对象。内部采用了 `懒初始化` 设计,获取模板引擎对象时只是创建了一个轻量的模板管理对象,只有当解析模板文件时才会真正初始化。 + +### (单例) `WEB Server` + +```go +func Server(name ...interface{}) *ghttp.Server +``` + +该单例对象将会自动读取默认配置文件中的 `server` 配置项,并只会初始化一次 `Server` 对象。 + +### (单例) `TCP Server` + +```go +func TcpServer(name ...interface{}) *gtcp.Server +``` + +### (单例) `UDP Server` + +```go +func UdpServer(name ...interface{}) *gudp.Server +``` + +### (单例) 数据库 `ORM` 对象 + +```go +func DB(name ...string) *gdb.Db +``` + +该单例对象将会自动读取默认配置文件中的 `database` 配置项,并只会初始化一次 `DB` 对象。 + +此外,可以通过以下方法在默认数据库上创建一个 `Model` 对象: + +```go +func Model(tables string, db ...string) *gdb.Model +``` + +### (单例) `Redis` 客户端对象 + +```go +func Redis(name ...string) *gredis.Redis +``` + +该单例对象将会自动读取默认配置文件中的 `redis` 配置项,并只会初始化一次 `Redis` 对象。 + +### (单例) 资源管理对象 + +```go +func Res(name ...string) *gres.Resource +``` + +### (单例) 国际化管理对象 + +```go +func I18n(name ...string) *gi18n.Manager +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..e7955533dfd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\270\212\344\270\213\346\226\207\345\217\230\351\207\217.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/core/gdb-context' +title: 'ORM上下文变量' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM上下文变量,异步IO控制,链路跟踪,上下文变量,请求超时,嵌套事务,模型上下文操作,ORM事务处理] +description: '在GoFrame框架中如何通过ORM支持自定义的上下文变量,以实现异步IO控制、链路跟踪、嵌套事务等功能。通过使用Ctx方法,开发者可以轻松传递自定义上下文变量,实现更复杂的请求控制和跟踪。文章提供了请求超时控制和模型上下文操作的具体示例和建议。' +--- + +`ORM` 支持传递自定义的 `context` 上下文变量,用于异步 `IO` 控制、上下文信息传递(特别是链路跟踪信息的传递)、以及嵌套事务支持。 + +我们可以通过 `Ctx` 方法传递自定义的上下文变量给 `ORM` 对象, `Ctx` 方法其实是一个链式操作方法,该上下文传递进去后仅对当前 `DB` 接口对象有效,方法定义如下: + +```go +func Ctx(ctx context.Context) DB +``` + +## 请求超时控制 + +我们来看一个通过上下文变量控制请求超时时间的示例。 + +```go +ctx, cancel := context.WithTimeout(context.Background(), time.Second) +defer cancel() +_, err := db.Ctx(ctx).Query("SELECT SLEEP(10)") +fmt.Println(err) +``` + +该示例中执行会 `sleep 10` 秒中,因此必定会引发请求的超时。执行后,输出结果为: + +```html +context deadline exceeded, SELECT SLEEP(10) +``` + +## 链路跟踪信息 + +上下文变量也可以传递链路跟踪信息,并且可以和日志组件结合,将链路信息打印到日志中(仅当 `ORM` 日志开启时),具体请参考链路跟踪专题介绍章节: [服务链路跟踪](../../服务可观测性/服务链路跟踪/服务链路跟踪.md) + +## 模型上下文操作 + +模型对象也支持上下文变量的传递,同样也是通过 `Ctx` 方法。我们来看一个简单的示例,我们将示例2的例子通过模型操作调整一下。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + _, err := g.DB().Model("user").Ctx(gctx.New()).All() + if err != nil { + panic(err) + } +} +``` + +执行后,终端输出为: + +```html +2020-12-28 23:46:56.349 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 5 ms] [default] [rows:0 ] SHOW FULL COLUMNS FROM `user` +2020-12-28 23:46:56.354 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 5 ms] [default] [rows:100] SELECT * FROM `user` +``` +:::tip +其中 ``SHOW FULL COLUMNS FROM `user` `` 为 `ORM` 组件的数据表字段获取查询,每个表在执行操作之前仅会查询一次并缓存结果到内存中。 +::: +## 嵌套事务支持 + +嵌套事务的支持依赖 `Context` 上下文变量的层级传递,具体请参考章节: [ORM事务处理](ORM事务处理/ORM事务处理.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" new file mode 100644 index 00000000000..04d79227f74 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\265\214\345\245\227\344\272\213\345\212\241.md" @@ -0,0 +1,315 @@ +--- +slug: '/docs/core/gdb-transaction-nested' +title: 'ORM事务处理-嵌套事务' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,嵌套事务,事务处理,数据库,SQL,事务保存点,回滚,闭包] +description: '使用GoFrame框架中的ORM功能来处理嵌套事务的问题。文章详细讲解了嵌套事务的基本原理、方法以及日志信息,同时给出了常规操作和闭包操作的示例,并指出可能出现的问题。最后提供了嵌套事务在工程中的参考示例,帮助开发者理解在实际项目中如何应用。' +--- + +从 `GoFrame ORM` 支持数据库嵌套事务,嵌套事务在业务项目中用得比较多,特别是业务模块之间的相互调用,保证各个业务模块的数据库操作都处于一个事务中,其原理是通过传递的 `context` 上下文来隐式传递和关联同一个事务对象。需要注意的是,数据库服务往往并不支持嵌套事务,而是依靠 `ORM` 组件层通过 `Transaction Save Point` 特性实现的。同样的,我们推荐使用 `Transaction` 闭包方法来实现嵌套事务操作。为了保证文档的完整性,因此我们这里仍然从最基本的事务操作方法开始来介绍嵌套事务操作。 + +## 一、示例SQL + +一个简单的示例 `SQL`,包含两个字段 `id` 和 `name`: + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL COMMENT '用户ID', + `name` varchar(45) NOT NULL COMMENT '用户名称', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` + +## 二、常规操作(不推荐) + +```go +db := g.DB() + +tx, err := db.Begin() +if err != nil { + panic(err) +} +if err = tx.Begin(); err != nil { + panic(err) +} +_, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert() +if err = tx.Rollback(); err != nil { + panic(err) +} +_, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() +if err = tx.Commit(); err != nil { + panic(err) +} +``` + +### 1、 `db.Begin` 与 `tx.Begin` + +可以看到,在我们的嵌套事务中出现了 `db.Begin` 和 `tx.Begin` 两种事务开启方式,两者有什么区别呢? `db.Begin` 是在数据库服务上真正开启一个事务操作,并返回一个事务操作对象 `tx`,随后所有的事务操作都是通过该 `tx` 事务对象来操作管理。 `tx.Begin` 表示在当前事务操作中开启嵌套事务,默认情况下会对嵌套事务的 `SavePoint` 采用自动命名,命名格式为 `transactionN`,其中的 `N` 表示嵌套的层级数量,如果您看到日志中出现 ``SAVEPOINT `transaction1` `` 表示当前嵌套层级为 `2`(从 `0` 开始计算)。 + +### 2、更详细的日志 + +`goframe` 的 `ORM` 拥有相当完善的日志记录机制,如果您打开 `SQL` 日志,那么将会看到以下日志信息,展示了整个数据库请求的详细执行流程: + +```html +2021-05-22 21:12:10.776 [DEBU] [ 4 ms] [default] [txid:1] BEGIN +2021-05-22 21:12:10.776 [DEBU] [ 0 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:12:10.789 [DEBU] [ 13 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:12:10.790 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:12:10.791 [DEBU] [ 1 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:12:10.791 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:12:10.792 [DEBU] [ 1 ms] [default] [txid:1] COMMIT +``` + +其中的 `[txid:1]` 表示 `ORM` 组件记录的事务ID,多个真实的事务同时操作时,每个事务的ID将会不同。在同一个真实事务下的嵌套事务的事务ID是一样的。 + +执行后查询数据库结果: + +``` +mysql> select * from `user`; ++----+-------+ +| id | name | ++----+-------+ +| 2 | smith | ++----+-------+ +1 row in set (0.00 sec) +``` + +可以看到第一个操作被成功回滚,只有第二个操作执行并提交成功。 + +## 三、闭包操作(推荐) + +我们也可以通过闭包操作来实现嵌套事务,同样也是通过 `Transaction` 方法实现。 + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := tx.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := tx.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := tx.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := tx.Model(table).Ctx(ctx).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +嵌套事务的闭包嵌套中也可以不使用其中的 `tx` 对象,而是直接使用 `db` 对象或者 `dao` 包,这种方式更常见一些。特别是在方法层级调用时,使得对于开发者来说并不用关心 `tx` 对象的传递,也并不用关心当前事务是否需要嵌套执行,一切都由组件自动维护,极大减少开发者的心智负担。但是务必记得将 `ctx` 上下文变量层层传递下去哦。例如: + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +如果您打开 `SQL` 日志,那么执行后将会看到以下日志信息,展示了整个数据库请求的详细执行流程: + +```html +2021-05-22 21:18:46.672 [DEBU] [ 2 ms] [default] [txid:1] BEGIN +2021-05-22 21:18:46.672 [DEBU] [ 0 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:18:46.673 [DEBU] [ 0 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:18:46.674 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:18:46.674 [DEBU] [ 0 ms] [default] [txid:1] RELEASE SAVEPOINT `transaction0` +2021-05-22 21:18:46.675 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:18:46.675 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`name`,`id`) VALUES('smith',2) +2021-05-22 21:18:46.675 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:18:46.676 [DEBU] [ 1 ms] [default] [txid:1] ROLLBACK +``` +:::warning +假如 `ctx` 上下文变量没有层层传递下去,那么嵌套事务将会失败,我们来看一个错误的例子: + +```go +db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + // Nested transaction 1. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Ctx(ctx).Data(g.Map{"id": 1, "name": "john"}).Insert() + return err + }); err != nil { + return err + } + // Nested transaction 2, panic. + if err := db.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + _, err := db.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert() + // Create a panic that can make this transaction rollback automatically. + panic("error") + return err + }); err != nil { + return err + } + return nil +}) +``` + +打开 `SQL` 执行日志,执行后,您将会看到以下日志内容: + +```html +2021-05-22 21:29:38.841 [DEBU] [ 3 ms] [default] [txid:1] BEGIN +2021-05-22 21:29:38.842 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:29:38.843 [DEBU] [ 1 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:29:38.845 [DEBU] [ 2 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:29:38.845 [DEBU] [ 0 ms] [default] [txid:1] RELEASE SAVEPOINT `transaction0` +2021-05-22 21:29:38.846 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:29:38.847 [DEBU] [ 1 ms] [default] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:29:38.848 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:29:38.848 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK +``` + +可以看到,第二条 `INSERT` 操作 ``INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') `` 没有事务ID打印,表示没有使用到事务,那么该操作将会被真正提交到数据库执行,并不能被回滚。 +::: +## 四、 `SavePoint/RollbackTo` + +开发者也可以灵活使用 `Transaction Save Point` 特性,并实现自定义的 `SavePoint` 命名以及指定 `Point` 回滚操作。 + +```go +tx, err := db.Begin() +if err != nil { + panic(err) +} +defer func() { + if err := recover(); err != nil { + _ = tx.Rollback() + } +}() +if _, err = tx.Model(table).Data(g.Map{"id": 1, "name": "john"}).Insert(); err != nil { + panic(err) +} +if err = tx.SavePoint("MyPoint"); err != nil { + panic(err) +} +if _, err = tx.Model(table).Data(g.Map{"id": 2, "name": "smith"}).Insert(); err != nil { + panic(err) +} +if _, err = tx.Model(table).Data(g.Map{"id": 3, "name": "green"}).Insert(); err != nil { + panic(err) +} +if err = tx.RollbackTo("MyPoint"); err != nil { + panic(err) +} +if err = tx.Commit(); err != nil { + panic(err) +} +``` + +如果您打开 `SQL` 日志,那么将会看到以下日志信息,展示了整个数据库请求的详细执行流程: + +```html +2021-05-22 21:38:51.992 [DEBU] [ 3 ms] [default] [txid:1] BEGIN +2021-05-22 21:38:52.002 [DEBU] [ 9 ms] [default] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:38:52.002 [DEBU] [ 0 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:38:52.003 [DEBU] [ 1 ms] [default] [txid:1] SAVEPOINT `MyPoint` +2021-05-22 21:38:52.004 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:38:52.005 [DEBU] [ 1 ms] [default] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(3,'green') +2021-05-22 21:38:52.006 [DEBU] [ 0 ms] [default] [txid:1] ROLLBACK TO SAVEPOINT `MyPoint` +2021-05-22 21:38:52.006 [DEBU] [ 0 ms] [default] [txid:1] COMMIT +``` + +执行后查询数据库结果: + +``` +mysql> select * from `user`; ++----+------+ +| id | name | ++----+------+ +| 1 | john | ++----+------+ +1 row in set (0.00 sec) +``` + +可以看到,通过在第一个 `Insert` 操作后保存了一个 `SavePoint` 名称 `MyPoint`,随后的几次操作都通过 `RollbackTo` 方法被回滚掉了,因此只有第一次 `Insert` 操作被成功提交执行。 + +## 五、嵌套事务在工程中的参考示例 + +为了简化示例,我们还是使用用户模块相关的示例,例如用户注册,通过事务操作保存用户基本信息( `user`)、详细信息( `user_detail`)两个表,任一个表操作失败整个注册操作都将失败。为展示嵌套事务效果,我们将用户基本信息管理和用户详细信息管理划分为了两个 `dao` 对象。 + +假如我们的项目按照 `goframe` 标准项目工程化分为三层 `api-service-dao`,那么我们的嵌套事务操作可能是这样的。 + +### `controller` + +```go +// 用户注册HTTP接口 +func (*cUser) Signup(r *ghttp.Request) { + // .... + service.User().Signup(r.Context(), userServiceSignupReq) + // ... +} +``` + +承接HTTP请求,并且将 `Context` 上下文边变量传递给后续的流程。 + +### `service` + +```go +// 用户注册业务逻辑处理 +func (*userService) Signup(ctx context.Context, r *model.UserServiceSignupReq) { + // .... + dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) error { + err := dao.User.Ctx(ctx).Save(r.UserInfo) + if err != nil { + return err + } + err := dao.UserDetail.Ctx(ctx).Save(r.UserDetail) + if err != nil { + return err + } + return nil + }) + // ... +} +``` + +可以看到,内部的 `user` 表和 `user_detail` 表使用了嵌套事务来统一执行事务操作。注意在闭包内部需要通过 `Ctx` 方法将上下文变量传递给下一层级。假如在闭包中存在对其他 `service` 对象的调用,那么也需要将 `ctx` 变量传递过去,例如: + +```go +func (*userService) Signup(ctx context.Context, r *model.UserServiceSignupReq) { + // .... + dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.Tx) (err error) { + if err = dao.User.Ctx(ctx).Save(r.UserInfo); err != nil { + return err + } + if err = dao.UserDetail.Ctx(ctx).Save(r.UserDetail); err != nil { + return err + } + if err = service.XXXA().Call(ctx, ...); err != nil { + return err + } + if err = service.XXXB().Call(ctx, ...); err != nil { + return err + } + if err = service.XXXC().Call(ctx, ...); err != nil { + return err + } + // ... + return nil + }) + // ... +} +``` + +### `dao` + +`dao` 层的代码由 `goframe cli` 工具全自动化生成及维护即可。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..e07ebf79be6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\345\270\270\350\247\204\346\223\215\344\275\234.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/core/gdb-transaction-basic' +title: 'ORM事务处理-常规操作' +sidebar_position: 0 +hide_title: true +keywords: [事务操作,Begin,Commit,Rollback,GoFrame,数据库操作,Transaction,闭包方法,gdb.Tx,链式操作] +description: '在GoFrame框架中进行ORM事务处理的基础操作,包括如何使用Begin、Commit和Rollback方法来开启、提交和回滚事务。同时特别提示事务操作后需要及时关闭事务以避免资源泄露,建议使用Transaction闭包方法实现安全事务操作。' +--- + +常规的事务操作方法为 `Begin/Commit/Rollback`,每一个方法指定特定的事务操作。开启事务操作可以通过执行 `db.Begin` 方法,该方法返回事务的操作接口,类型为 `gdb.Tx`,通过该对象执行后续的数据库操作,并可通过 `tx.Commit` 提交修改,或者通过 `tx.Rollback` 回滚修改。 +:::warning +常见问题注意:开启事务操作后,请务必在不需要使用该事务对象时,通过 `Commit`/ `Rollback` 操作关闭掉该事务,建议充分利用好 `defer` 方法。如果事务使用后不关闭,在应用侧会引起 `goroutine` 不断激增泄露,在数据库侧会引起事务线程数量被打满,以至于后续的事务请求执行超时。此外,建议尽可能使用后续介绍的 `Transaction` 闭包方法来安全实现事务操作: [ORM事务处理-闭包操作](ORM事务处理-闭包操作.md) +::: +## 一、开启事务操作 + +```go +db := g.DB() + +if tx, err := db.Begin(ctx); err == nil { + fmt.Println("开启事务操作") +} +``` + +事务操作对象可以执行所有 `db` 对象的方法,具体请参考 [API文档](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb)。 + +## 二、事务回滚操作 + +```go +if tx, err := db.Begin(ctx); err == nil { + r, err := tx.Save("user", g.Map{ + "id" : 1, + "name" : "john", + }) + if err != nil { + tx.Rollback() + } + fmt.Println(r) +} +``` + +## 三、事务提交操作 + +```go +if tx, err := db.Begin(ctx); err == nil { + r, err := tx.Save("user", g.Map{ + "id" : 1, + "name" : "john", + }) + if err == nil { + tx.Commit() + } + fmt.Println(r) +} +``` + +## 四、事务链式操作 + +事务操作对象仍然可以通过 `tx.Model` 方法返回一个链式操作的对象,该对象与 `db.Model` 方法返回值相同,只不过数据库操作在事务上执行,可提交或回滚。 + +```go +if tx, err := db.Begin(); err == nil { + r, err := tx.Model("user").Data(g.Map{"id":1, "name": "john_1"}).Save() + if err == nil { + tx.Commit() + } + fmt.Println(r) +} +``` + +其他链式操作请参考 [ORM链式操作(🔥重点🔥)](../ORM链式操作/ORM链式操作.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..2318601b64e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206-\351\227\255\345\214\205\346\223\215\344\275\234.md" @@ -0,0 +1,60 @@ +--- +slug: '/docs/core/gdb-transaction-closure' +title: 'ORM事务处理-闭包操作' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM事务处理,闭包操作,事务管理,代码简化,嵌套事务,自动提交,自动回滚,上下文参数] +description: '在GoFrame框架中使用闭包操作进行ORM事务处理的优点,包括减少冗余代码、降低操作风险和简化嵌套事务,实现自动提交和回滚功能,并介绍了上下文参数在嵌套事务管理中的作用,确保事务处理的安全性和简便性。' +--- + +## 一、痛点描述 + +可以看到,通过常规的事务方法来管理事务有一些问题: + +- **冗余代码较多**。代码中存在很多重复性的 `tx.Commit/Rollback` 操作。 +- **操作风险较大**。非常容易遗忘执行 `tx.Commit/Rollback` 操作,或者由于代码逻辑判断 `BUG`,引发事务操作未正常关闭。在自行管理事务操作的情况下,大部分程序员都会踩到这个坑。作者已经见过了很多起由于事务未正常关闭引发的现网事故。我现在特意过来更新这段描述(2023-08-09),也是因为一个朋友由于自行通过 `tx.Commit/Rollback` 管理事务操作,细节未处理好,引发了现网事故。 +- **嵌套事务实现复杂**。假如业务逻辑中存在多层级的事务处理(嵌套事务),需要考虑如何加个 `tx` 对象往下传递,处理起来更加繁琐。 + +## 二、闭包操作 + +因此为方便安全执行事务操作, `ORM` 组件同样提供了事务的闭包操作,通过 `Transaction` 方法实现,该方法定义如下: + +```go +func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) +``` + +当给定的闭包方法返回的 `error` 为 `nil` 时,那么闭包执行结束后当前事务自动执行 `Commit` 提交操作;否则自动执行 `Rollback` 回滚操作。闭包中的 `context.Context` 参数为 `goframe v1.16` 版本后新增的上下文变量,主要用于链路跟踪传递以及嵌套事务管理。由于上下文变量是嵌套事务管理的重要参数,因此上下文变量通过显示的参数传递定义。 +:::tip +如果闭包内部操作产生 `panic` 中断,该事务也将自动进行回滚,以保证操作安全。 +::: +使用示例: + +```go +g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error { + // user + result, err := tx.Ctx(ctx).Insert("user", g.Map{ + "passport": "john", + "password": "12345678", + "nickname": "JohnGuo", + }) + if err != nil { + return err + } + // user_detail + id, err := result.LastInsertId() + if err != nil { + return err + } + _, err = tx.Ctx(ctx).Insert("user_detail", g.Map{ + "uid": id, + "site": "https://johng.cn", + "true_name": "GuoQiang", + }) + if err != nil { + return err + } + return nil +}) +``` + +通过闭包操作的方式可以很简便地实现嵌套事务,且对上层业务开发同学来说无感知,具体可以继续阅读章节: [ORM事务处理-嵌套事务](ORM事务处理-嵌套事务.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..b3af14c5a65 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\272\213\345\212\241\345\244\204\347\220\206/ORM\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -0,0 +1,22 @@ +--- +slug: '/docs/core/gdb-transaction' +title: 'ORM事务处理' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM事务,事务处理,闭包操作,事务安全,嵌套事务,数据库,事务接口,Go开发] +description: '使用GoFrame框架的ORM组件进行事务操作非常简便且安全,有两种方式:常规操作和闭包操作。常规操作通过Begin开启事务,返回一个事务接口gdb.TX,而闭包操作通过Transaction方法封装事务逻辑,并自动管理事务的关闭,支持嵌套事务,推荐使用闭包操作以确保事务安全。' +--- + + +使用 `GoFrame ORM` 组件进行事务操作非常简便、安全,可以通过两种操作方式来实现。 + +1. 常规操作:通过 `Begin` 开启事务之后会返回一个事务操作接口 `gdb.TX`,随后可以使用该接口进行如之前章节介绍的方法操作和链式操作。常规操作容易漏掉关闭事务,有一定的事务操作安全风险。 +2. 闭包操作:通过 `Transaction` 闭包方法的形式来操作事务,所有的事务逻辑在闭包中实现,闭包结束后自动关闭事务保障事务操作安全。并且闭包操作支持非常便捷的 **嵌套事务**,嵌套事务在业务操作中透明无感知。 +:::tip +我们推荐事务操作均统一采用 `Transaction` 闭包方式实现。 +::: +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#TX](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#TX) + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..110965e8812 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,41 @@ +--- +slug: '/docs/core/gdb-config-faq' +title: 'ORM使用配置-常见问题' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据库加密,自定义Driver,mysql,密码解密,配置文件加密,ORM接口开发,数据库账号保护,数据库连接] +description: '在GoFrame框架中实现数据库账号密码在配置文件中的加密,避免敏感信息泄露。用户可以通过自定义Driver的方式,在连接数据库时对加密的字段进行解密处理。以mysql为例,代码示例展示了如何包裹mysql driver并覆盖其Open方法,确保数据库连接的安全性与灵活性。' +--- + +## 如何实现数据库账号密码在配置文件中加密 + +在某些场景下,数据库的账号密码无法明文配置到配置文件中,需要进行一定的加密。在连接数据库的时候, +再对配置文件中加密的字段进行解密处理。这种需求可以通过自定义 `Driver` 来实现(关于 `Driver` 的详细介绍请参考章节: +[ORM接口开发](../ORM接口开发/ORM接口开发.md))。以 `mysql` 为例,我们可以自己编写一个 `Driver`,包裹框架社区组件中的 `mysql driver`,并且覆盖它的 `Open` 方法即可。代码示例: + +```go +import ( + "database/sql" + + "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/database/gdb" +) + +type MyBizDriver struct { + mysql.Driver +} + +// Open creates and returns an underlying sql.DB object for mysql. +// Note that it converts time.Time argument to local timezone in default. +func (d *MyBizDriver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { + config.User = d.decode(config.User) + config.Pass = d.decode(config.Pass) + return d.Driver.Open(config) +} + +func (d *MyBizDriver) decode(s string) string { + // 执行字段解密处理逻辑 + // ... + return s +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" new file mode 100644 index 00000000000..6d2512b876d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\207\344\273\266.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/core/gdb-config-file' +title: 'ORM使用配置-配置文件' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据库配置,配置文件,ORM,数据库管理,yaml配置,数据格式,集群模式,日志配置] +description: '使用GoFrame框架的配置组件来管理数据库配置,包括推荐使用的yaml格式配置文件。通过g对象轻松获取数据库操作的单例对象,支持多种数据格式下的配置和简化的连接配置方式,实现集群模式与日志输出功能。' +--- + +我们推荐使用配置组件来管理数据库配置,并使用 `g` 对象管理模块中的 `g.DB("数据库分组名称")` 方法获取数据库操作对象,数据库对象将会自动读取配置组件中的相应配置项,并自动初始化该数据库操作的单例对象。数据库配置管理功能使用的是配置管理组件实现(配置组件采用接口化设计默认使用文件系统实现),同样支持多种数据格式如: `toml/yaml/json/xml/ini/properties`。默认并且推荐的配置文件数据格式为 `yaml`。 + +## 简单配置 +:::tip +从 `v2.2.0` 版本开始,使用 `link` 进行数据库配置时,数据库组件统一了不同数据库类型的配置格式,以简化配置管理。 +::: +简化配置通过配置项 `link` 指定,格式如下: + +```text +type:username:password@protocol(address)[/dbname][?param1=value1&...¶mN=valueN] +``` + +即: + +```text +类型:账号:密码@协议(地址)/数据库名称?特性配置 +``` + +其中: + +- **数据库名称** 及 **特性配置** 为非必须参数,其他参数为必须参数。 +- **协议** 可选配置为: `tcp/udp/unix/file`,常见配置为 `tcp` +- **特性配置** 根据不同的数据库类型,由其底层实现的第三方驱动定义,具体需要参考第三方驱动官网。例如,针对 `mysql` 驱动而言,使用的第三方驱动为: [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) 支持的特性配置如 `multiStatements` 和 `loc` 等。 + +示例: + +```yaml +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + user: + link: "sqlite::@file(/var/data/db.sqlite3)" + local: + link: "mysql:username:password@unix(/tmp/mysql.sock)/dbname" +``` + +不同数据类型对应的 `link` 示例如下: + +| 类型 | link示例 | extra参数 | +| --- | --- | --- | +| `mysql` | ```mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `mariadb` | ```mariadb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `tidb` | ```tidb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true``` | [mysql](https://github.com/go-sql-driver/mysql) | +| `pgsql` | ```pgsql:root:12345678@tcp(127.0.0.1:5432)/test``` | [pq](https://github.com/lib/pq) | +| `mssql` | ```mssql:root:12345678@tcp(127.0.0.1:1433)/test?encrypt=disable``` | [go-mssqldb](https://github.com/microsoft/go-mssqldb) | +| `sqlite` | ```sqlite::@file(/var/data/db.sqlite3)``` | pure go:[go-sqlite](https://github.com/glebarez/go-sqlite)
    32bit-cgo:[go-sqlite3](https://github.com/mattn/go-sqlite3) | +| `oracle` | ```oracle:root:12345678@tcp(127.0.0.1:5432)/test``` | [go-ora](https://github.com/sijms/go-ora) | +| `clickhouse` | ```clickhouse:root:12345678@tcp(127.0.0.1:9000)/test``` | [clickhouse-go](https://github.com/ClickHouse/clickhouse-go) | +| `dm` | ```dm:root:12345678@tcp(127.0.0.1:5236)/test``` | [dm](https://gitee.com/chunanyong/dm) | + +:::tip +更多框架支持的数据库类型请参考: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) +::: +## 完整配置 + +完整的 `config.yaml` 数据库配置项的数据格式形如下: + +```yaml +database: + default: # 分组名称,可自定义,默认为default + host: "127.0.0.1" # 地址 + port: "3306" # 端口 + user: "root" # 账号 + pass: "your_password" # 密码 + name: "your_database" # 数据库名称 + type: "mysql" # 数据库类型(如:mariadb/tidb/mysql/pgsql/mssql/sqlite/oracle/clickhouse/dm) + link: "" # (可选)自定义数据库链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name,Type)将失效 + extra: "" # (可选)不同数据库的额外特性配置,由底层数据库driver定义,具体有哪些配置请查看具体的数据库driver介绍 + role: "master" # (可选)数据库主从角色(master/slave),默认为master。如果不使用应用主从机制请不配置或留空即可。 + debug: false # (可选)开启调试模式 + prefix: "gf_" # (可选)表名前缀 + dryRun: false # (可选)ORM空跑(只读不写) + charset: "utf8" # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。 + protocol: "tcp" # (可选)数据库连接协议,默认为TCP + weight: 100 # (可选)负载均衡权重,用于负载均衡控制,不使用应用层的负载均衡机制请置空 + timezone: "Local" # (可选)时区配置,例如:Local + namespace: "" # (可选)用以支持个别数据库服务Catalog&Schema区分的问题,原有的Schema代表数据库名称,而NameSpace代表个别数据库服务的Schema + maxIdle: 10 # (可选)连接池最大闲置的连接数(默认10) + maxOpen: 100 # (可选)连接池最大打开的连接数(默认无限制) + maxLifetime: "30s" # (可选)连接对象可重复使用的时间长度(默认30秒) + queryTimeout: "0" # (可选)查询语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。 + execTimeout: "0" # (可选)写入语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。 + tranTimeout: "0" # (可选)事务处理超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。 + prepareTimeout: "0" # (可选)预准备SQL语句执行超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。 + createdAt: "created_at" # (可选)自动创建时间字段名称 + updatedAt: "updated_at" # (可选)自动更新时间字段名称 + deletedAt: "deleted_at" # (可选)软删除时间字段名称 + timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效 +``` +:::note +使用该配置方式时, **为保证数据库安全,默认底层不支持多行 `SQL` 语句执行**。为了得到更多配置项控制,请参考推荐的`link`简化配置,并了解清楚简化配置项中每个连接参数的功能作用,以及对应驱动的官方额外配置参数。 +::: + +配置示例: +```yaml +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + extra: "charset=utf8mb4&parseTime=True&loc=Local" + mysql: + type: "mysql" + host: "127.0.0.1" + port: "3306" + user: "root" + pass: "12345678" + name: "test" + charset: "utf8mb4" + timezone: "Local" + maxIdle: "10" + maxOpen: "100" + maxLifetime: "30s" + extra: "parseTime=True" +``` + +## 集群模式 + +`gdb` 的配置支持集群模式,数据库配置中每一项分组配置均可以是多个节点,支持负载均衡权重策略,例如: + +```yaml +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "slave" + + user: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "slave" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + role: "slave" +``` + +以上数据库配置示例中包含两个数据库分组 `default` 和 `user`,其中 `default` 分组包含一主一从, `user` 分组包含一主两从。在代码中可以通过 `g.DB()` 和 `g.DB("user")` 获取对应的数据库连接对象。 + +## 日志配置 + +`gdb` 支持日志输出,内部使用的是 `glog.Logger` 对象实现日志管理,并且可以通过配置文件对日志对象进行配置。默认情况下 `gdb` 关闭了 `DEBUG` 日志输出,如果需要打开 `DEBUG` 信息需要将数据库的 `debug` 参数设置为 `true`。以下是为一个配置文件示例: + +```yaml +database: + logger: + path: "/var/log/gf-app/sql" + level: "all" + stdout: true + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user_center" + debug: true +``` + +其中 `database.logger` 即为 `gdb` 的日志配置,这是一项特殊的配置项,当该配置不存在时,将会使用日志组件的默认配置, +具体请参考 [日志组件-配置管理](../../../../docs/核心组件/日志组件/日志组件-配置管理.md) 章节。 +:::warning +需要注意哦:由于 `ORM` 底层都是采用安全的预处理执行方式,提交到底层的 `SQL` 与参数其实是分开的,因此日志中记录的完整 `SQL` 仅作参考方便人工阅读,并不是真正提交到底层的 `SQL` 语句。 +::: diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..9bb15321e25 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256-\351\205\215\347\275\256\346\226\271\346\263\225.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/core/gdb-config-funcs' +title: 'ORM使用配置-配置方法' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,ORM使用配置,数据库节点管理,连接属性配置,数据库集群,关系型数据库,读写分离,负载均衡,配置管理特点,GoFrame框架] +description: '在GoFrame框架中配置管理gdb数据库模块。主要涵盖了数据结构设计、配置特点、以及具体的配置方法。通过配置ConfigNode和ConfigGroup,开发者可以灵活地管理多节点数据库集群,实现高效的负载均衡和读写分离。此外,还提供了一些默认的分组和自定义全局配置的示例。' +--- +:::tip +以下为数据库底层管理配置介绍,如果您对数据库的底层配置管理比较感兴趣,可继续阅读后续章节。 +::: +## 数据结构 + +`gdb` 数据库管理模块的内部配置管理数据结构如下: + +`ConfigNode` 用于存储一个数据库节点信息; `ConfigGroup` 用于管理多个数据库节点组成的配置分组(一般一个分组对应一个业务数据库集群); `Config` 用于管理多个 `ConfigGroup` 配置分组。 + +**配置管理特点:** + +1. 支持多节点数据库集群管理; +2. 每个节点可以单独配置连接属性; +3. 采用单例模式管理数据库实例化对象; +4. 支持对数据库集群分组管理,按照分组名称获取实例化的数据库操作对象; +5. 支持多种关系型数据库管理,可通过 `ConfigNode.Type` 属性进行配置; +6. 支持 `Master-Slave` 读写分离,可通过 `ConfigNode.Role` 属性进行配置; +7. 支持客户端的负载均衡管理,可通过 `ConfigNode.Weight` 属性进行配置,值越大,优先级越高; + +```go +type Config map[string]ConfigGroup // 数据库配置对象 +type ConfigGroup []ConfigNode // 数据库分组配置 +// 数据库配置项(一个分组配置对应多个配置项) +type ConfigNode struct { + Host string // 地址 + Port string // 端口 + User string // 账号 + Pass string // 密码 + Name string // 数据库名称 + Type string // 数据库类型:mysql, sqlite, mssql, pgsql, oracle + Link string // (可选)自定义链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name)将失效(该字段是一个扩展功能) + Extra string // (可选)不同数据库的额外特性配置,由底层数据库driver定义 + Role string // (可选,默认为master)数据库的角色,用于主从操作分离,至少需要有一个master,参数值:master, slave + Debug bool // (可选)开启调试模式 + Charset string // (可选,默认为 utf8)编码,默认为 utf8 + Prefix string // (可选)表名前缀 + Weight int // (可选)用于负载均衡的权重计算,当集群中只有一个节点时,权重没有任何意义 + MaxIdleConnCount int // (可选)连接池最大闲置的连接数 + MaxOpenConnCount int // (可选)连接池最大打开的连接数 + MaxConnLifetime time.Duration // (可选,单位秒)连接对象可重复使用的时间长度 +} +``` + +特别说明, `gdb` 的配置管理最大的 **特点** 是,(同一进程中)所有的数据库集群信息都使用同一个配置管理模块进行统一维护, **不同业务的数据库集群配置使用不同的分组名称** 进行配置和获取。 + +## 配置方法 + +这是原生调用 `gdb` 模块来配置管理数据库。如果开发者想要自行控制数据库配置管理可以参考以下方法。若无需要可忽略该章节。 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +```go +// 添加一个数据库节点到指定的分组中 +func AddConfigNode(group string, node ConfigNode) +// 添加一个配置分组到数据库配置管理中(同名覆盖) +func AddConfigGroup(group string, nodes ConfigGroup) + +// 添加一个数据库节点到默认的分组中(默认为default,可修改) +func AddDefaultConfigNode(node ConfigNode) +// 添加一个配置分组到数据库配置管理中(默认分组为default,可修改) +func AddDefaultConfigGroup(nodes ConfigGroup) + +// 设置默认的分组名称,获取默认数据库对象时将会自动读取该分组配置 +func SetDefaultGroup(groupName string) + +// 设置数据库配置为定义的配置信息,会将原有配置覆盖 +func SetConfig(c Config) +``` + +默认分组表示,如果获取数据库对象时不指定配置分组名称,那么 `gdb` 默认读取的配置分组。例如: `gdb.NewByGroup()` 可获取一个默认分组的数据库对象。简单的做法,我们可以通过 `gdb` 包的 `SetConfig` 配置管理方法进行自定义的数据库全局配置,例如: + +```go +gdb.SetConfig(gdb.Config { + "default" : gdb.ConfigGroup { + gdb.ConfigNode { + Host : "192.168.1.100", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "master", + Weight : 100, + }, + gdb.ConfigNode { + Host : "192.168.1.101", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "slave", + Weight : 100, + }, + }, + "user-center" : gdb.ConfigGroup { + gdb.ConfigNode { + Host : "192.168.1.110", + Port : "3306", + User : "root", + Pass : "123456", + Name : "test", + Type : "mysql", + Role : "master", + Weight : 100, + }, + }, +}) +``` + +随后,我们可以使用 `gdb.NewByGroup("数据库分组名称")` 来获取一个数据库操作对象。该对象用于后续的数据库一系列方法/链式操作。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..836a9864945 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\344\275\277\347\224\250\351\205\215\347\275\256/ORM\344\275\277\347\224\250\351\205\215\347\275\256.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-config' +title: 'ORM使用配置' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM配置,数据库配置,gdb,配置指南,ORM使用,GoFrame数据库,GoFrame ORM,数据存储] +description: '在GoFrame框架中进行ORM配置,为开发者提供详细的数据库配置指南,帮助用户更好地使用GoFrame进行数据存储管理。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..6885e11a9b1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/core/gdb-faq' +title: 'ORM常见问题' +sidebar_position: 11 +hide_title: true +keywords: [ORM常见问题,GoFrame,GoFrame框架,数据库连接池,MaxLifeTime,SQL查询,数据库驱动,调试日志,空数组条件,字符集设置] +description: '在使用GoFrame框架进行ORM操作时可能遇到的几种常见问题及其解决方案,包括数据库连接池过期导致的连接错误、update和insert操作不生效、无法找到数据库驱动、查询条件带有WHERE 0=1的问题以及MySQL表情存储乱码问题等。同时给出了一些配置建议以优化使用体验。' +--- + +## `ORM`是否可以直接执行SQL文件 + +1. 首先,`ORM`底层依赖的数据库`driver`从安全性考虑,通常默认不支持同时执行多条`SQL`语句。你可以读取`SQL`文件内容,将内容拆分为单条`SQL`语句(通过`;`分隔符号分隔多条`SQL`语句),然后调用`ORM`的`Exec`方法来执行。 +2. 其次,如果你想要允许底层`driver`一次性执行多条`SQL`语句,可以参考底层`driver`的配置,比如`mysql`的配置,可以设置`multiStatements=true`(参考[mysql-driver配置](https://github.com/go-sql-driver/mysql?tab=readme-ov-file#multistatements)),这样`ORM`的`Exec`方法就可以执行多条`SQL`语句了。配置项示例: + ```yaml + database: + default: + link: "mysql:root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local&multiStatements=true" + ``` + + + +## `driver: bad connection` + +![](/markdown/7b384b6f57115b11938d9c0a30dde732.png) + +如果数据库执行出现该错误,可能是由于本地数据库连接池的连接已经过期,可以检查一下客户端配置的 `MaxLifeTime` 配置是否超过数据库服务端设置的连接最大超时时间。更多客户端配置请参考章节: [ORM使用配置](./ORM使用配置/ORM使用配置.md) + +## `update/insert` 操作不生效 + +使用 `orm` 时,配置文件中: + +```toml +dryRun = "(可选)ORM空跑(只读不写)" +``` + +这行配置一定要删掉或者设置为0 + +否则出现 `update insert` 操作不生效的现象。 + +## `cannot find database driver for specified database type "xxx", did you misspell type name "xxx" or forget importing the database driver?` + +程序代码没有引入依赖的数据库驱动,需要注意从 `GoFrame v2.1` 版本开始,需要手动引入社区驱动,请参考: + +- [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## 数据库打开 `DEBUG` 日志后,查询的 `SQL` 语句中发现出现 `WHERE 0=1` 的语句 + +出现 `WHERE 0=1` 的情况是由于查询条件中存在数组条件,并且数组的长度为 `0`。这种情况 `ORM` 无法自动过滤这种空数组条件(这种条件过滤可能会引起业务异常),需要开发者根据业务场景,显示调用 `OmitEmpty` 或者 `OmitEmptyWhere` 来告诉 `ORM` 可以过滤这些空数组的条件。 + +## MYSQL中的表情,用SQL查询后,乱码问题 + +![](/markdown/867e951b823bb2652a6b7d62f70a1ff3.png) + +解决办法: + +`config.toml` 文件 数据库配置的 `charset` 设置为 `utf8mb4` 默认是 `utf8` + +`MySQL` 存储表情时注意: + +- 数据库编码 `utf8mb4` +- 表的编码是 `utf8mb4` +- 表中内容字段是 `utf8mb4` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..7a1dfb8845e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\345\233\236\350\260\203\345\244\204\347\220\206.md" @@ -0,0 +1,94 @@ +--- +slug: '/docs/core/gdb-interface-callback' +title: 'ORM接口开发-回调处理' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,ORM接口,SQL语句,自定义回调,日志记录,鉴权操作,数据库驱动,MySQL驱动,gdb接口,框架覆盖] +description: '在使用GoFrame框架进行ORM接口开发时,通过自定义回调处理来对SQL语句进行日志记录或鉴权。通过实现并覆盖DoQuery、DoExec等接口方法,开发者可以注入自定义逻辑到默认实现中。示例中展示了如何自定义MySQL驱动以记录执行的SQL语句,并配置gdb以使用该驱动。' +--- + +## 基本介绍 + +自定义回调处理是最常见的接口开发实现,开发中只需要对接口中的部分方法进行 **替换与修改**,在驱动默认实现逻辑中注入自定义逻辑。参考接口关系图( [ORM接口开发](ORM接口开发.md))我们可以知道,所有的 `SQL` 语句执行必定会通过 `DoQuery` 或者 `DoExec` 或者 `DoFilter` 接口,根据需求在自定义的驱动中 **实现并覆盖** 相关接口方法实现所需功能即可。 + +其中,最长见的使用场景是在 `ORM` 底层实现对 `SQL` 的 **日志记录或者鉴权等统一判断操作**。 + +## 使用示例 + +我们来看一个自定义回调处理的示例,现需要将所有执行的 `SQL` 语句记录到 `monitor` 表中,以方便于进行 `SQL` 审计。因此通过自定义 `Driver` 然后覆盖 `ORM` 的底层接口方法来实现是最简单的。为简化示例编写,以下代码实现了一个自定义的 `MySQL` 驱动,该驱动继承于 `drivers` 下 `mysql` 模块内已经实现的 `Driver`。 + +```go +package driver + +import ( + "context" + + "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/os/gtime" +) + +// MyDriver is a custom database driver, which is used for testing only. +// For simplifying the unit testing case purpose, MyDriver struct inherits the mysql driver +// gdb.Driver and overwrites its functions DoQuery and DoExec. +// So if there's any sql execution, it goes through MyDriver.DoQuery/MyDriver.DoExec firstly +// and then gdb.Driver.DoQuery/gdb.Driver.DoExec. +// You can call it sql "HOOK" or "HiJack" as your will. +type MyDriver struct { + *mysql.Driver +} + +var ( + // customDriverName is my driver name, which is used for registering. + customDriverName = "MyDriver" +) + +func init() { + // It here registers my custom driver in package initialization function "init". + // You can later use this type in the database configuration. + if err := gdb.Register(customDriverName, &MyDriver{}); err != nil { + panic(err) + } +} + +// New creates and returns a database object for mysql. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *MyDriver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &MyDriver{ + &mysql.Driver{ + Core: core, + }, + }, nil +} + +// DoCommit commits current sql and arguments to underlying sql driver. +func (d *MyDriver) DoCommit(ctx context.Context, in gdb.DoCommitInput) (out gdb.DoCommitOutput, err error) { + tsMilliStart := gtime.TimestampMilli() + out, err = d.Core.DoCommit(ctx, in) + tsMilliFinished := gtime.TimestampMilli() + _, _ = in.Link.ExecContext(ctx, + "INSERT INTO `monitor`(`sql`,`cost`,`time`,`error`) VALUES(?,?,?,?)", + gdb.FormatSqlWithArgs(in.Sql, in.Args), + tsMilliFinished-tsMilliStart, + gtime.Now(), + err, + ) + return +} +``` + +我们看到,这里在包初始化方法 `init` 中使用了 `gdb.Register("MyDriver", &MyDriver{})` 来注册了了一个自定义名称的驱动。我们也可以通过 `gdb.Register("mysql", &MyDriver{})` 来覆盖已有的框架 `mysql` 驱动为自己的驱动。 +:::tip +驱动名称 `mysql` 为框架默认的 `DriverMysql` 驱动的名称。 +::: +由于这里我们使用了一个新的驱动名称 `MyDriver`,因此在 `gdb` 配置中的 `type` 数据库类型时,需要填写该驱动名称。以下是一个使用配置的示例: + +```yaml +database: + default: + - link: "MyDriver:root:12345678@tcp(127.0.0.1:3306)/user" +``` + +## 注意事项 + +在接口方法实现中,需要使用接口的 `Link` 输入对象参数来操作数据库,如果使用 `g.DB` 方法获取数据库对象来操作可能会引起死锁问题。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..9192c2cd9cf --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221-\351\251\261\345\212\250\345\274\200\345\217\221.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/core/gdb-interface-driver' +title: 'ORM接口开发-驱动开发' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据库驱动,自定义驱动,接口开发,驱动开发,数据库组件,gdb模块,Driver接口,ORM] +description: '在GoFrame框架中进行ORM接口开发,特别是数据库驱动的开发和注册。通过实现gdb模块的接口,可以新增GoFrame默认不支持的第三方数据库驱动或对已有支持的驱动进行定制化修改,实现上层业务操作的一致性。本文档提供了详细的步骤和示例代码,帮助开发者快速上手。' +--- + +框架数据库组件的驱动意义在于,数据库上层业务使用的各种方法操作不用变化,只需要修改配置中的数据库类型即可切换支持到新的数据库。 + +我们可以通过数据库组件的接口设计实现:新增框架默认不支持的第三方数据库驱动、对已有支持的驱动进行定制化修改等。驱动的开发并不是完整地开发一类数据库的协议实现代码,而是使用第三方已有的数据库驱动,通过实现框架数据库组件的接口,将该第三方数据库驱动对接到框架数据库组件上来,保证上层操作的一致。 + +## 驱动注册 + +之前我们有提到 `Driver` 的驱动接口,在实现该接口之后,我们可以通过以下方法注册自定义驱动到 `gdb` 模块: + +```go +// Register registers custom database driver to gdb. +func Register(name string, driver Driver) error +``` + +其中的驱动名称 `name` 可以是已有的驱动名称,例如 `mysql`, `mssql`, `pgsql` 等等,当出现同名的驱动注册时,新的驱动将会覆盖老的驱动。 + +## 驱动实现 + +开发一个自定义的驱动并注册到 `gdb` 模块中非常简单,可以参考 `gdb` 模块源码中已对接的数据库类型代码示例: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +需要说明的是,最常见的驱动开发或者修改方式是直接继承于现有 `*Core` 类型,因为在 `Driver` 接口中会传递该类型的对象,例如: + +```go +// DriverMysql is the driver for mysql database. +type DriverMysql struct { + *Core +} + +// New creates and returns a database object for mysql. +// It implements the interface of gdb.Driver for extra database driver installation. +func (d *DriverMysql) New(core *Core, node *ConfigNode) (DB, error) { + return &DriverMysql{ + Core: core, + }, nil +} +``` + +## 注意事项 + +一个新的驱动至少应该实现以下接口方法: + +| 接口方法 | 说明 | +| --- | --- | +| `Open` | 用于创建数据库连接。 | +| `GetChars` | 用于获取该数据库的安全/转义符号。 | +| `Tables` | 返回当前/指定数据库的数据表列表。 | +| `TableFields` | 返回指定数据表的字段列表信息。 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..191033b0bff --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\216\245\345\217\243\345\274\200\345\217\221/ORM\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/core/gdb-interface' +title: 'ORM接口开发' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM接口,gdb模块,数据库操作,接口设计,Open方法,Driver接口,自定义实现,SQL提交] +description: 'GoFrame框架中gdb模块的ORM接口开发,通过灵活的接口设计,开发者可以方便地自定义数据库操作实现。DB接口作为核心接口,提供了数据库连接创建、查询与执行等方法,而Driver接口则允许用户定义自己的驱动实现。详细的接口文档和方法说明将帮助您快速上手并进行二次开发。' +--- + +`gdb` 模块使用了非常灵活且扩展性强的接口设计,接口设计允许开发者可以非常方便地自定义实现和替换接口定义中的任何方法。 + +## `DB` 接口 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB) + +`DB` 接口是数据库操作的核心接口,也是我们通过 `ORM` 操作数据库时最常用的接口,这里主要对接口的几个重要方法做说明: + +1. `Open` 方法用于创建特定的数据库连接对象,返回的是标准库的 `*sql.DB` 通用数据库对象。 +2. `Do*` 系列方法的第一个参数 `link` 为 `Link` 接口对象,该对象在 `master-slave` 模式下可能是一个主节点对象,也可能是从节点对象,因此如果在继承的驱动对象实现中使用该 `link` 参数时,注意当前的运行模式。 `slave` 节点在大部分的数据库主从模式中往往是不可写的。 +3. `HandleSqlBeforeCommit` 方法将会在每一条 `SQL` 提交给数据库服务端执行时被调用做一些提交前的回调处理。 +4. 其他接口方法详见接口文档或者源码文件。 + +## `DB` 接口关系 + +![](/markdown/1f5e48cc947e21dbed2745f69254935a.png) + +`GoFrame ORM Relations` + +## `Driver` 接口 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Driver](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Driver) + +开发者自定义的驱动需要实现以下接口: + +```go +// Driver is the interface for integrating sql drivers into package gdb. +type Driver interface { + // New creates and returns a database object for specified database server. + New(core *Core, node *ConfigNode) (DB, error) +} +``` + +其中的 `New` 方法用于根据 `Core` 数据库基础对象以及 `ConfigNode` 配置对象创建驱动对应的数据库操作对象,需要注意的是,返回的数据库对象需要实现 `DB` 接口。而数据库基础对象 `Core` 已经实现了 `DB` 接口,因此开发者只需要”继承” `Core` 对象,然后根据需要覆盖对应的接口实现方法即可。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" new file mode 100644 index 00000000000..57f74b00646 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\226\271\346\263\225\346\223\215\344\275\234-\345\216\237\347\224\237.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/gdb-funcs' +title: 'ORM方法操作(原生)' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,SQL操作,数据库查询,数据插入,数据更新,数据删除,批量操作,链式操作] +description: '使用GoFrame框架进行原生SQL的ORM方法操作。详细讲解了通过方法操作来执行复杂SQL的方法,包括数据库查询、数据插入、更新、删除和批量操作的具体用法,同时提供了具体的代码示例。' +--- + +## 方法操作 + +方法操作用于原生 `SQL` 执行,相对链式操作更偏底层操作一些,在 `ORM` 链式操作执行不了太过于复杂的 `SQL` 操作时,可以交给方法操作来处理。 + +**接口文档:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#DB) + +**常用方法:** + +本文档的方法列表可能滞后于于代码,详细的方法列表请查看接口文档,以下方法仅供参考。 + +```go +// SQL操作方法,返回原生的标准库sql对象 +Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) +Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) +Prepare(ctx context.Context, query string) (*sql.Stmt, error) + +// 数据表记录查询: +// 查询单条记录、查询多条记录、获取记录对象、查询单个字段值(链式操作同理) +GetAll(ctx context.Context, sql string, args ...interface{}) (Result, error) +GetOne(ctx context.Context, sql string, args ...interface{}) (Record, error) +GetValue(ctx context.Context, sql string, args ...interface{}) (Value, error) +GetArray(ctx context.Context, sql string, args ...interface{}) ([]Value, error) +GetCount(ctx context.Context, sql string, args ...interface{}) (int, error) +GetScan(ctx context.Context, objPointer interface{}, sql string, args ...interface{}) error + +// 数据单条操作 +Insert(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) +Replace(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) +Save(ctx context.Context, table string, data interface{}, batch...int) (sql.Result, error) + +// 数据修改/删除 +Update(ctx context.Context, table string, data interface{}, condition interface{}, args ...interface{}) (sql.Result, error) +Delete(ctx context.Context, table string, condition interface{}, args ...interface{}) (sql.Result, error) +``` + +**简要说明:** + +1. `Query` 是原始的数据查询方法,返回的是原生的标准库的结果集对象,需要自行解析。推荐使用 `Get*` 方法,会对结果自动做解析。 +2. `Exec` 方法用于写入/更新的 `SQL` 的操作。 +3.  在执行数据查询时推荐使用 `Get*` 系列查询方法。 +4. `Insert`/ `Replace`/ `Save` 方法中的 `data` 参数支持的数据类型为: `string/map/slice/struct/*struct`,当传递为 `slice` 类型时,自动识别为批量操作,此时 `batch` 参数有效。 + +## 操作示例 + +### 1\. `ORM` 对象 + +```go +// 获取默认配置的数据库对象(配置名称为"default") +db := g.DB() + +// 获取配置分组名称为"user-center"的数据库对象 +db := g.DB("user-center") + +// 使用原生单例管理方法获取数据库对象单例 +db, err := gdb.Instance() +db, err := gdb.Instance("user-center") + +// 注意不用的时候不需要使用Close方法关闭数据库连接(并且gdb也没有提供Close方法), +// 数据库引擎底层采用了链接池设计,当链接不再使用时会自动关闭 +``` + +### 2\. 数据写入 + +```go +r, err := db.Insert(ctx, "user", gdb.Map { + "name": "john", +}) +``` + +### 3\. 数据查询(列表) + +```go +list, err := db.GetAll(ctx, "select * from user limit 2") +list, err := db.GetAll(ctx, "select * from user where age > ? and name like ?", g.Slice{18, "%john%"}) +list, err := db.GetAll(ctx, "select * from user where status=?", g.Slice{1}) +``` + +### 4\. 数据查询(单条) + +```go +one, err := db.GetOne(ctx, "select * from user limit 2") +one, err := db.GetOne(ctx, "select * from user where uid=1000") +one, err := db.GetOne(ctx, "select * from user where uid=?", 1000) +one, err := db.GetOne(ctx, "select * from user where uid=?", g.Slice{1000}) +``` + +### 5\. 数据保存 + +```go +r, err := db.Save(ctx, "user", gdb.Map { + "uid" : 1, + "name" : "john", +}) +``` + +### 6\. 批量操作 + +其中 `batch` 参数用于指定批量操作中分批写入条数数量(默认是 `10`)。 + +```go +_, err := db.Insert(ctx, "user", gdb.List { + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, + {"name": "john_4"}, +}, 10) +``` + +### 7\. 数据更新/删除 + +```go +// db.Update/db.Delete 同理 +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", gdb.Map {"name": "john"}, "uid=?", 10000) +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", "name='john'", "uid=10000") +// UPDATE `user` SET `name`='john' WHERE `uid`=10000 +r, err := db.Update(ctx, "user", "name=?", "uid=?", "john", 10000) +``` + +注意,参数域支持并建议使用预处理模式(使用 `?` 占位符)进行输入,避免 `SQL` 注入风险。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..356d93cba7b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\227\266\345\214\272\345\244\204\347\220\206.md" @@ -0,0 +1,99 @@ +--- +slug: '/docs/core/gdb-timezone' +title: 'ORM时区处理' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame框架,ORM时区处理,MySQL时区,time.Time,数据库驱动,时区转换,loc参数,时区设置,配置文件,time.Parse] +description: '在GoFrame框架中处理ORM操作中的时区问题,特别是使用MySQL数据库时的时区转换。我们讲解了如何通过设置loc参数来控制time.Time对象在提交到数据库时的时区处理,并提供了相关代码示例和配置建议,帮助开发者在处理数据库操作时更好地管理时区。' +--- + +## 基本介绍 + +这个问题由于大家问得比较多,因此单独开了一个章节详细介绍一下 `ORM` 中的时区处理是怎么一回事。我们这里以 `MySQL` 数据库为例来介绍时区转换的事情,本地时区我们设定为 `+8` 时区,数据库时区也是 `+8` 时区。 + +`MySQL` 数据库驱动用得最多的是这个第三方包: [https://github.com/go-sql-driver/mysql](https://github.com/go-sql-driver/mysql) ,在这个第三方包中有这么一个参数: + +![](/markdown/86813361650854a9b17490267709df8a.png) + +大概的意思是,当你提交的时间参数为 `time.Time` 时,该参数用来转换参数时区的。当你在连接数据库时,该参数传递 `loc=Local`,那么该 `driver` 将会自动将你提交的 `time.Time` 参数转换为本地程序设置的时区,没有手动设置时,那么该时区为 `UTC` 时区。那么我们来看两个例子。 + +## 转换示例 + +### 示例1,设置 `loc=Local` + +**配置文件** + +```yaml +database: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local" +``` + +**代码示例** + +```go +t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 10:00:00") +t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 11:00:00") +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 18:00:00' AND create_time<'2020-10-27 19:00:00' +``` + +这里由于通过 `time.Parse` 创建的 `time.Time` 时间对象是 `UTC` 时区,那么提交到数据库执行时将会被底层的 `driver` 修改为 `+8` 时区。 + +```go +t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 10:00:00", time.Local) +t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 11:00:00", time.Local) +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 10:00:00' AND create_time<'2020-10-27 11:00:00' +``` + +这里由于通过 `time.ParseInLocation` 创建的 `time.Time` 时间对象是 `+8` 时区,和 `loc=Local` 的时区一致,那么提交到数据库执行时不会被底层的 `driver` 修改。 +:::warning +注意在写入数据中包含 `time.Time` 参数时,也需要注意时区转换的问题。 +::: +### 示例2,不设置 `loc` 参数 + +**配置文件** + +```yaml +database: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +``` + +**代码示例** + +```go +t1, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 10:00:00") +t2, _ := time.Parse("2006-01-02 15:04:05", "2020-10-27 11:00:00") +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 10:00:00' AND create_time<'2020-10-27 11:00:00' +``` + +这里由于通过 `time.Parse` 创建的 `time.Time` 时间对象是 `UTC` 时区,那么提交到数据库执行时将不会被底层的 `driver` 修改。 + +```go +t1, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 10:00:00", time.Local) +t2, _ := time.ParseInLocation("2006-01-02 15:04:05", "2020-10-27 11:00:00", time.Local) +db.Model("user").Ctx(ctx).Where("create_time>? and create_time'2020-10-27 02:00:00' AND create_time<'2020-10-27 03:00:00' +``` + +这里由于通过 `time.ParseInLocation` 创建的 `time.Time` 时间对象是 `+8` 时区,那么提交到数据库执行时会被底层的 `driver` 修改为 `UTC` 时区。 +:::warning +注意在写入数据中包含 `time.Time` 参数时,也需要注意时区转换的问题。 +::: +## 改进建议 + +建议在配置中统一加上 `locl` 配置,例如(MySQL): `loc=Local&parseTime=true`。以下是一个可供参考的配置: + +```yaml +database: + logger: + level: "all" + stdout: true + default: + link: "mysql:root:12345678@tcp(192.168.1.10:3306)/mydb?loc=Local&parseTime=true" + debug: true + order: + link: "mysql:root:12345678@tcp(192.168.1.20:3306)/order?loc=Local&parseTime=true" + debug: true +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..23d442d941b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/ORM\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,15 @@ +--- +slug: '/docs/core/gdb-practice' +title: 'ORM最佳实践' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据库操作,ORM最佳实践,数据库组件,GoFrame数据库,业务项目,灵活操作,最佳实践案例,参考学习] +description: '使用GoFrame框架进行数据库操作的最佳实践。GoFrame提供了强大而灵活的数据库组件,支持各种复杂业务项目中的数据库操作。本文档中包含多个最佳实践案例,供开发者参考和学习,帮助您更高效地实现数据库操作。' +--- + + +数据库操作往往是业务项目中最复杂的部分, `GoFrame` 的数据库组件其实提供了非常强大和灵活的操作方式,我们在这里提供了一些最佳实践的案例,方便大家参考学习。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..5a2a018e4e3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\210\251\347\224\250\346\214\207\351\222\210\345\261\236\346\200\247\345\222\214do\345\257\271\350\261\241\345\256\236\347\216\260\347\201\265\346\264\273\347\232\204\344\277\256\346\224\271\346\216\245\345\217\243.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/core/gdb-practice-using-pointer-and-do-for-update-api' +title: '利用指针属性和do对象实现灵活的修改接口' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,do对象,指针,API,用户信息修改,数据库更新,请求参数,业务逻辑,自动过滤] +description: '利用GoFrame框架中的指针和do对象实现灵活的修改接口API。通过指针类型的属性参数和do对象,开发者可以轻松实现用户信息的修改操作,包括密码、昵称、状态等字段的更新,从而有效简化数据库更新的复杂度。' +--- + +大家都知道框架自带的开发工具可以生成 `do` 对象代码,该 `do` 对象主要用于查询、修改、写入等操作时对操作字段的自动 `nil` 过滤。 + +今天教给大家一个新的玩法,通过指针结合 `do` 对象快速实现灵活、便捷的修改操作 `API` 实现。 + +## 数据结构 + +以下是我们使用的用户表数据结构: + +```sql +CREATE TABLE `user`( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `passport` varchar(32) NOT NULL COMMENT '账号', + `password` varchar(32) NOT NULL COMMENT '密码', + `nickname` varchar(32) NOT NULL COMMENT '昵称', + `status` varchar(32) NOT NULL COMMENT '状态', + `brief` varchar(512) NOT NULL COMMENT '备注信息', + `create_at` datetime DEFAULT NULL COMMENT '创建时间', + `update_at` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uniq_passport` (`passport`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +其中的用户状态,我们使用了单独的类型定义,用以实现枚举值: + +```go +type Status string + +const ( + StatusEnabled Status = "enabled" + StatusDisabled Status = "disabled" +) +``` + +通过 `gf gen dao` 命令,我们自动生成的 `do` 对象如下: + +```go +type User struct { + g.Meta `table:"user" orm:"do:true"` + Id interface{} + Passport interface{} + Password interface{} + Nickname interface{} + Status interface{} + Brief interface{} + CreatedAt interface{} + UpdatedAt interface{} +} +``` + +## 请求API定义 + +我们来实现一个用户信息修改的 `API` 接口,这是一个运维管理接口,可以通过用户账号名称来修改用户信息。该 `API` 的定义如下: + +```go +type UpdateReq struct { + g.Meta `path:"/user/{Id}" method:"post" summary:"修改用户信息"` + Passport string `v:"required" dc:"用户账号"` + Password *string `dc:"修改用户密码"` + Nickname *string `dc:"修改用户昵称"` + Status *Status `dc:"修改用户状态"` + Brief *string `dc:"修改用户描述"` +} +``` + +其中,用户的可修改信息为密码、昵称、状态和描述,可能同时修改一项或者多项。这里使用了 **指针类型** 的属性参数, **用于实现:当传递该参数时执行修改,不传递时不修改。** + +## 业务逻辑实现 + +为了简化实例,我们这里直接在控制器中将指针参数传递给 `do` 对象。我们知道当调用端没有传递该参数时,该参数为 `nil`,那么传递给 `do` 对象的字段时,仍然是 `nil`,这个时候执行数据库更新操作时, `do` 对象中的 `nil` 字段将会被自动过滤掉。 + +```go +func (c *Controller) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Password: req.Password, + Nickname: req.Nickname, + Status: req.Status, + Brief: req.Brief, + }).Where(do.User{ + Passport: req.Passport, + }).Update() + return +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" new file mode 100644 index 00000000000..f65d8ebc2af --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\345\244\215\346\235\202\347\261\273\345\236\213\345\260\275\351\207\217\344\275\277\347\224\250json\345\255\230\345\202\250\357\274\214\344\276\277\344\272\216Scan\345\210\260\345\257\271\350\261\241\346\227\266\350\207\252\345\212\250\345\214\226\350\275\254\346\215\242\357\274\214\351\201\277\345\205\215\350\207\252\345\256\232\344\271\211\350\247\243\346\236\220.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/core/gdb-practice-using-json-for-complicated-field' +title: '复杂类型尽量使用json存储,便于Scan到对象时自动化转换,避免自定义解析' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,JSON存储,数据自动化转换,复杂类型,数据库设计,ORM组件,Go语言,产品售卖规格,数据查询,数据结构] +description: '在数据库设计中使用JSON格式存储复杂类型数据的优势,主要通过GoFrame框架实现自动化转换,从而简化代码。以产品售卖规格为例,通过定义和使用Go结构体实现数据库的增删查改,避免自定义解析的复杂操作。此外,详细展示了如何在Go应用中进行数据写入和查询,确保高效率的数据处理过程。' +--- + +举一个🌰。假如我们需要实现产品售卖规格列表,其中包含可选择的分片数量、分片容量以及副本数量,如下图(非现网代码,仅供示例学习): + +![](/markdown/9876558f2195bcdad4d03060e9a15161.png) + +我们的表设计如下: + +```sql +CREATE TABLE `sell_spec` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键', + `product` varchar(45) NOT NULL COMMENT '产品名称', + `resources` json NOT NULL COMMENT '资源规格(cpu:memory),例如:["0:0.25", "0:1", "1:2"]', + `disk_min` int(10) DEFAULT NULL COMMENT '磁盘最小容量', + `disk_max` int(10) DEFAULT NULL COMMENT '磁盘最大容量', + `disk_step` int(10) DEFAULT NULL COMMENT '磁盘递增大小', + `shards` json NOT NULL COMMENT '分片规格,例如:[1,3,5,8,12,16,24,32,40,48,64,80,96,128]', + `replicas` json NOT NULL COMMENT '副本规格,例如:[1,2,3,4,5,6,7,8,9,12]', + `created_at` datetime DEFAULT NULL COMMENT '创建时间', + `updated_at` datetime DEFAULT NULL COMMENT '更新时间', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='售卖规格配置'; +``` + +其中的 `resources, shards, replicas` 我们定义为 `json` 格式,目的是可以存储自定义的资源、分片、副本规格列表(非顺序性)。那么我们的 `go struct` 定义如下: + +```go +// SellSpec 是通过GoFrame工具自动生成的数据结构,由工具维护。 +type SellSpec struct { + Id uint `description:"主键"` + Product string `description:"产品名称"` + Resources string `description:"资源规格(cpu:memory),例如:[\"0:0.25\", \"0:1\", \"1:2\"]"` + DiskMin int `description:"磁盘最小容量"` + DiskMax int `description:"磁盘最大容量"` + DiskStep int `description:"磁盘递增大小"` + Shards string `description:"分片规格,例如:[1,3,5,8,12,16,24,32,40,48,64,80,96,128]"` + Replicas string `description:"副本规格,例如:[1,2,3,4,5,6,7,8,9,12]"` + CreatedAt *gtime.Time `description:"创建时间"` + UpdatedAt *gtime.Time `description:"更新时间"` +} + +// SellSpecItem 是扩展entity的自定义数据结构, +// 其中部分字段Resources/Shards/Replicas被覆盖为了数组类型,方便ORM操作时自动进行类型转换。 +type SellSpecItem struct { + entity.SellSpec + Resources []string `dc:"资源规格"` + Shards []int `dc:"分片规格"` + Replicas []int `dc:"副本规格"` +} +``` + +那么在程序中我们可以这么来写入和查询数据记录。 + +数据写入: + +```go +_, err = dao.SellSpec.Ctx(ctx).Data(v1.SellSpecItem{ + SellSpec: entity.SellSpec{ + Product: "redis", + DiskMin: 50, + DiskMax: 1000, + DiskStep: 10, + }, + Resources: []string{"1:2", "2:4", "4:8"}, + Shards: []int{1, 3, 5, 8, 12, 16, 24, 32, 40, 48, 64, 80, 96, 128}, + Replicas: []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 12}, +}).Insert() +``` + +数据查询, `ORM` 组件将会自动地将数据表中的记录转换为 `go struct` 对应的数组类型属性: + +```go +var items []v1.SellSpecItem +err = dao.SellSpec.Ctx(ctx).Scan(&items) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" new file mode 100644 index 00000000000..6f597870f5b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\234\200\344\275\263\345\256\236\350\267\265/\346\237\245\350\257\242\346\227\266\351\201\277\345\205\215\350\277\224\345\233\236\345\257\271\350\261\241\345\210\235\345\247\213\345\214\226\345\217\212sql.ErrNoRows\345\210\244\346\226\255.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gdb-practice-avoid-object-initialization-and-sql-errnorows-error' +title: '查询时避免返回对象初始化及sql.ErrNoRows判断' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,SQL查询,对象初始化,sql.ErrNoRows,错误处理,指针判断,ORM结果处理,对象内存,代码复杂度] +description: '在使用GoFrame框架进行SQL查询时,避免对象初始化以及sql.ErrNoRows错误判断问题。通过不初始化查询结果对象,使用指针为nil判断的方法,统一项目中对空查询结果的处理逻辑,从而降低代码对错误处理的复杂度。' +--- + +## 查询时避免返回对象初始化及 `sql.ErrNoRows` 判断 + +执行SQL查询时,请避免提前将查询结果初始化,以避免结构对象默认值的影响,避免创建不必要的对象内存。通过返回对象指针 `nil` 判断避免 `sql.ErrNoRows` 使用,降低代码对 `error` 处理的复杂度、统一项目中对空查询结果处理逻辑。 + +一个反面例子: + +```go +func (s *sTask) GetOne(ctx context.Context, id uint64) (out *entity.ResourceTask, err error) { + out = new(model.TaskDetail) + err = dao.ResourceTask.Ctx(ctx).WherePri(id).Scan(out) + if err != nil { + if err == sql.ErrNoRows { + err = gerror.Newf(`record not found for "%d"`, id) + } + return + } + return +} +``` + +在该例子中,实际返回的 `out` 对象由于对象初始化的缘故有了默认值(无论SQL是否查询到数据),并且 `sql.ErrNoRows` 的判断增加了代码逻辑中对 `error` 处理的复杂度。 + +建议改进如下: + +```go +func (s *sTask) GetOne(ctx context.Context, id uint64) (out *entity.ResourceTask, err error) { + err = dao.ResourceTask.Ctx(ctx).WherePri(id).Scan(&out) + if err != nil { + return + } + if out == nil { + err = gerror.Newf(`record not found for "%d"`, id) + } + return +} +``` +:::warning +注意代码中 `&out` 的使用。 +::: +更多的介绍请参考: [ORM结果处理-为空判断](../ORM结果处理/ORM结果处理-为空判断.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" new file mode 100644 index 00000000000..3899790838f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\346\250\241\345\236\213\347\224\237\346\210\220.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-model-generating' +title: 'ORM模型生成' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,模型生成,数据表,gf工具链,gen dao,开发工具,自动生成,数据库] +description: 'GoFrame框架提供了简单的ORM数据表模型自动生成功能,通过gf gen dao/model命令实现,适用于开发者快速生成数据库模型。具体使用方法可参考相关开发工具章节,优化开发效率。' +--- + +## 模型自动生成 + +`GoFrame` 框架支持非常便捷的数据表模型生成功能,通过 `gf gen dao/model` 工具链命令实现,具体请参考 [开发工具](../../开发工具/开发工具.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" new file mode 100644 index 00000000000..5dbece095aa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\344\270\272\347\251\272\345\210\244\346\226\255.md" @@ -0,0 +1,151 @@ +--- +slug: '/docs/core/gdb-result-empty-check' +title: 'ORM结果处理-为空判断' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,结果处理,为空判断,数据集合,数据记录,数据字段值,Struct对象,Struct数组] +description: '使用GoFrame框架进行ORM结果处理中的为空判断。包括处理数据集合、多条数据记录、数据字段值,以及Struct对象和Struct数组的结果处理方法。通过使用IsEmpty和IsNil等方法,可以轻松地判断查询结果是否为空。' +--- + +使用 `GoFrame ORM` 对返回结果为空判断非常简便,大部分场景下直接判断返回的数据是否为 `nil` 或者长度为 `0`,或者使用 `IsEmpty/IsNil` 方法。 + +## 一、数据集合(多条) + +```go +r, err := g.Model("order").Where("status", 1).All() +if err != nil { + return err +} +if len(r) == 0 { + // 结果为空 +} +``` + +也可以使用 `IsEmpty` 方法: + +```go +r, err := g.Model("order").Where("status", 1).All() +if err != nil { + return err +} +if r.IsEmpty() { + // 结果为空 +} +``` + +## 二、数据记录(单条) + +```go +r, err := g.Model("order").Where("status", 1).One() +if err != nil { + return err +} +if len(r) == 0 { + // 结果为空 +} +``` + +也可以使用 `IsEmpty` 方法: + +```go +r, err := g.Model("order").Where("status", 1).One() +if err != nil { + return err +} +if r.IsEmpty() { + // 结果为空 +} +``` + +## 三、数据字段值 + +返回的是一个"泛型"变量,这个只能使用 `IsEmpty` 来判断是否为空了。 + +```go +r, err := g.Model("order").Where("status", 1).Value() +if err != nil { + return err +} +if r.IsEmpty() { + // 结果为空 +} +``` + +## 四、字段值数组 + +查询返回字段值数组本身类型为 `[]gdb.Value` 类型,因此直接判断长度是否为 `0` 即可。 + +```go +// Array/FindArray +r, err := g.Model("order").Fields("id").Where("status", 1).Array() +if err != nil { + return err +} +if len(r) == 0 { + // 结果为空 +} +``` + +## 五、 `Struct` 对象(🔥注意🔥) + +关于 `Struct` 转换对象来说 **会有一点不一样**,我们直接看例子吧。 + +当传递的对象 **本身就是一个空指针时**,如果查询到数据,那么会在内部 **自动创建这个对象**;如果没有查询到数据,那么这个空指针仍旧是一个空指针,内部并不会做任何处理。 + +```go +var user *User +err := g.Model("order").Where("status", 1).Scan(&user) +if err != nil { + return err +} +if user == nil { + // 结果为空 +} +``` + +当传递的对象 **本身已经是一个初始化的对象**,如果查询到数据,那么会在内部将数据赋值给这个对象; **如果没有查询到数据,那么此时就没办法将对象做 `nil` 判断空结果**。因此 `ORM` 会返回一个 `sql.ErrNoRows` 错误,提醒开发者没有查询到任何数据并且对象没有做任何赋值,对象的所有属性还是给定的初始化数值,以便开发者可以做进一步的空结果判断。 + +```go +var user = new(User) +err := g.Model("order").Where("status", 1).Scan(&user) +if err != nil && err != sql.ErrNoRows { + return err +} +if err == sql.ErrNoRows { + // 结果为空 +} +``` +:::tip +所以我们推荐开发者不要传递一个初始化过后的对象给 `ORM`,而是直接传递一个对象的指针的指针类型( `**struct` 类型), `ORM` 内部会根据查询结果智能地做自动初始化。 +::: +## 六、 `Struct` 数组 + +当传递的对象数组本身是一个空数组(长度为 `0`),如果查询到数据,那么会在内部自动赋值给数组;如果没有查询到数据,那么这个空数组仍旧是一个空数组,内部并不会做任何处理。 + +```go +var users []*User +err := g.Model("order").Where("status", 1).Scan(&users) +if err != nil { + return err +} +if len(users) == 0 { + // 结果为空 +} +``` + +当传递的对象数组本身不是空数组,如果查询到数据,那么会在内部自动从索引 `0` 位置覆盖到数组上;如果没有查询到数据,那么此时就没办法将数组做长度为 `0` 判断空结果。因此 `ORM` 会返回一个 `sql.ErrNoRows` 错误,提醒开发者没有查询到任何数据并且数组没有做任何赋值,以便开发者可以做进一步的空结果判断。 + +```go +var users = make([]*User, 100) +err := g.Model("order").Where("status", 1).Scan(&users) +if err != nil { + return err +} +if err == sql.ErrNoRows { + // 结果为空 +} +``` +:::warning +由于 `struct` 转换利用了 `Golang` 反射特性,执行性能会有一定的损耗。如果您涉及到大量查询结果数据的 `struct` 数组对象转换,并且需要提高转换性能,请参考自定义实现对应 `struct` 的 `UnmarshalValue` 方法: +[类型转换-UnmarshalValue](../../类型转换/类型转换-UnmarshalValue.md) +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" new file mode 100644 index 00000000000..42a254a02c5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\251\272\346\225\260\347\273\204\347\273\223\346\236\204\350\277\224\345\233\236.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/gdb-result-empty-array' +title: 'ORM结果处理-空数组结构返回' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,ORM,Go语言,数据库操作,JSON编码,前端开发,数据处理,后端开发,空数组,GoFrame框架] +description: '在GoFrame框架中处理ORM查询结果,当未查询到数据时,通过初始化空数组避免返回null值,从而增强与前端的友好交互。这种改进可以在数据需要展示于网页时,确保返回格式的预测性与稳定性。' +--- + +## 痛点描述 + +经过前面的章节介绍,如果给定一个未初始化的数组(值为 `nil`),在 `ORM` 根据给定条件未查询到数据时,并不会自动初始化该数组。因此该未初始化的数组结果如果通过 `JSON` 进行编码后,会被转换为 `null` 值。 + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + type User struct { + Id uint64 // 主键 + Passport string // 账号 + Password string // 密码 + NickName string // 昵称 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + } + type Response struct { + Users []User + } + var res = &Response{} + err := g.Model("user").WhereGT("id", 10).Scan(&res.Users) + fmt.Println(err) + fmt.Println(gjson.MustEncodeString(res)) +} +``` + +执行后,终端展示结果为: + +```html + +{"Users":null} +``` + +在大部分场景下, `ORM` 查询的数据需要渲染展示在浏览器页面上,也就意味着返回的数据需要给前端 `JS` 进行处理。为了对前端 `JS` 处理后端返回数据时更加友好,如果在后端查询不到数据时,期望返回一个空的数组结构,而不是返回一个 `null` 属性值。 + +## 改进方案 + +针对这种场景,可以给 `ORM` 的 `Scan` 方法一个初始化的空数组即可。当 `ORM` 查询不到数据时,该数组属性仍然是一个空数组,而不是 `nil`,返回 `JSON` 编码后也不会是 `null` 值。 + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + type User struct { + Id uint64 // 主键 + Passport string // 账号 + Password string // 密码 + NickName string // 昵称 + CreatedAt *gtime.Time // 创建时间 + UpdatedAt *gtime.Time // 更新时间 + } + type Response struct { + Users []User + } + var res = &Response{ + Users: make([]User, 0), + } + err := g.Model("user").WhereGT("id", 10).Scan(&res.Users) + fmt.Println(err) + fmt.Println(gjson.MustEncodeString(res)) +} +``` + +执行后,终端展示结果为: + +```html + +{"Users":[]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..ce39ad08c14 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206-\347\273\223\346\236\234\347\261\273\345\236\213.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/core/gdb-result-types' +title: 'ORM结果处理-结果类型' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,数据结构,ORM,结果类型,Record,Result,gdb,数据库,Go语言,数据处理] +description: 'GoFrame框架中ORM结果处理的几种结果类型,包括Value、Record和Result的数据结构定义。通过示例详细讲解了如何将数据表记录转换为struct对象,以及Result/Record类型在特定字段检索场景下的应用。' +--- + +## 一、数据结构 + +查询结果的数据结构如下: + +```go +type Value = *gvar.Var // 返回数据表记录值 +type Record map[string]Value // 返回数据表记录键值对 +type Result []Record // 返回数据表记录列表 +``` + +1. `Value/Record/Result` 用于ORM操作的结果数据类型。 +2. `Result` 表示 **数据表记录列表**, `Record` 表示 **一条数据表记录**, `Value` 表示记录中的 **一条键值数据**。 +3. `Value` 是 `*gvar.Var` 类型的别名类型,是一个运行时泛型,以便支持数据表不同的字段类型,方便于后续的数据类型转换。 + +举个🌰: + +![](/markdown/c4af671f6f43d161fc776afdffaaa047.png) + +![](/markdown/73f857180655a5dc19eb8deb79d3a774.png) + +![](/markdown/d8aedba99def08d9ad5e244dd0bde66a.png) + +## 二、 `Record` 数据记录 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +`gdb` 为数据表记录操作提供了极高的灵活性和简便性,除了支持以 `map` 的形式访问/操作数据表记录以外,也支持将数据表记录转换为 `struct` 进行处理。我们以下使用一个简单的示例来演示该特性。 + +首先,我们的用户表结构是这样的(简单设计的示例表): + +```sql +CREATE TABLE `user` ( + `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '昵称', + `site` varchar(255) NOT NULL DEFAULT '' COMMENT '主页', + PRIMARY KEY (`uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +其次,我们的表数据如下: + +``` +uid name site +1 john https://goframe.org +``` + +最后,我们的示例程序如下: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int + Name string +} + +func main() { + var ( + user *User + ctx = gctx.New() + ) + err := g.DB().Model("user").Where("uid", 1).Scan(&user) + if err != nil { + g.Log().Header(false).Fatal(ctx, err) + } + if user != nil { + g.Log().Header(false).Print(ctx, user) + } +} +``` + +执行后,输出结果为: + +```json +{"Uid":1,"Name":"john"} +``` + +这里,我们自定义了一个 `struct`,里面只包含了 `Uid` 和 `Name` 属性,可以看到它的属性并不和数据表的字段一致,这也是 `ORM` 灵活的特性之一:支持指定属性获取。 + +通过 `gdb.Model.Scan` 方法可以将查询到的数据记录转换为 `struct` 对象或者 `struct` 对象数组。由于这里传递的参数为 `&user` 即 `**User` 类型,那么将会转换为一个 **结构体对象**,如果传递为 `[]*User` 类型的参数,将会转换为 **结构体数组** 结果,请查看后续示例。具体方法介绍请查看链式操作章节。 + +**属性字段映射规则:** + +需要注意的是, `map` 中的键名为 `uid,name,site`,而 `struct` 中的属性为 `Uid,Name`,那么他们之间是如何执行映射的呢?主要是以下几点简单的规则: + +1. `struct` 中需要匹配的属性必须为 `公开属性`(首字母大写); +2. 记录结果中键名会自动按照 **`不区分大小写`** 且 **忽略 `-/_/空格` 符号** 的形式与 `struct` 属性进行匹配; +3. 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值; + +以下是几个匹配的示例: + +```html +记录键名 struct属性 是否匹配 +name Name match +Email Email match +nickname NickName match +NICKNAME NickName match +Nick-Name NickName match +nick_name NickName match +nick_name Nick_Name match +NickName Nick_Name match +Nick-Name Nick_Name match +``` +:::tip +由于数据库结果集转 `struct` 的底层是依靠 `gconv.Struct` 方法实现的,因此如果想要实现 **自定义的属性转换**,以及更详细的映射规则说明,请参考 [类型转换-Struct转换](../../类型转换/类型转换-Struct转换.md) 章节。 +::: +## 三、 `Result` 数据集合 + +`Result/Record` 数据类型根据数据结果集操作的需要,往往需要根据记录中 **特定的字段** 作为键名进行数据检索,因此它包含多个用于转换 `Map/List` 的方法,同时也包含了常用数据结构 `JSON/XML` 的转换方法。 + +接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +由于方法比较简单,这里便不再举例说明。需要注意的是两个方法 `Record.Map` 及 `Result.List`,这两个方法也是使用比较频繁的方法,用以将 `ORM` 查询结果信息转换为可做展示的数据类型。由于结果集字段值底层为 `[]byte` 类型,虽然使用了新的 `Value` 类型做了封装,并且也提供了数十种常见的类型转换方法(具体请阅读 [泛型类型-gvar](../../../组件列表/数据结构/泛型类型-gvar/泛型类型-gvar.md) 章节),但是大多数时候需要直接将结果 `Result` 或者 `Record` 直接作为 `json` 或者 `xml` 数据结构返回,就需要做转换才行。 + +使用示例: + +```go +package main + +import ( + "database/sql" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int + Name string + Site string +} + +func main() { + var ( + user []*User + ctx = gctx.New() + ) + err := g.DB().Model("user").Where("uid", 1).Scan(&user) + if err != nil && err != sql.ErrNoRows { + g.Log().Header(false).Fatal(ctx, err) + } + if user != nil { + g.Log().Header(false).Print(ctx, user) + } +} +``` + +执行后,输出结果为: + +```json +[{"Uid":1,"Name":"john","Site":"https://goframe.org"}] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..e902a5d55d3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\347\273\223\346\236\234\345\244\204\347\220\206/ORM\347\273\223\346\236\234\345\244\204\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-result' +title: 'ORM结果处理' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM结果处理,数据库处理,gdb,数据检索,结果转换,SQL查询,数据操作,Web开发] +description: '使用GoFrame框架中提供的功能进行ORM结果的处理,详细讲解了数据库结果的检索与转换过程,帮助开发者更高效地进行数据操作与管理,提升Web应用的开发效率。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..09869ab3afa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Handler\347\211\271\346\200\247.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gdb-chaining-handler' +title: 'ORM链式操作-Handler特性' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM链式操作,Handler特性,查询示例,分页示例,代码复用,数据库操作,Go语言,gdb库] +description: '在GoFrame框架中使用ORM链式操作实现Handler特性,通过示例展示了如何复用常见的查询逻辑和分页操作,从而简化代码,提高开发效率。Handler特性允许开发者定义通用逻辑并应用于数据库模型,实现更为简洁和可维护的代码结构。' +--- + +`Handler` 特性允许您轻松地复用常见的逻辑。 + +## 示例1,查询 + +```go +func AmountGreaterThan1000(m *gdb.Model) *gdb.Model { + return m.WhereGT("amount", 1000) +} + +func PaidWithCreditCard(m *gdb.Model) *gdb.Model { + return m.Where("pay_mode_sign", "credit_card") +} + +func PaidWithCod(m *gdb.Model) *gdb.Model { + return m.Where("pay_mode_sign", "cod") +} + +func OrderStatus(statuses []string) func(m *gdb.Model) *gdb.Model { + return func(m *gdb.Model) *gdb.Model { + return m.Where("status", statuses) + } +} + +var ( + m = g.Model("product_order") +) + +m.Handler(AmountGreaterThan1000, PaidWithCreditCard).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `pay_mode_sign`='credit_card' +// 查找所有金额大于 1000 的信用卡订单 + +m.Handler(AmountGreaterThan1000, PaidWithCod).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `pay_mode_sign`='cod' +// 查找所有金额大于 1000 的 COD 订单 + +m.Handler(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Scan(&orders) +// SELECT * FROM `product_order` WHERE `amount`>1000 AND `status` IN('paid','shipped') +// 查找所有金额大于1000 的已付款或已发货订单 +``` + +## 示例2,分页 + +```go +func Paginate(r *ghttp.Request) func(m *gdb.Model) *gdb.Model { + return func(m *gdb.Model) *gdb.Model { + type Pagination struct { + Page int + Size int + } + var pagination Pagination + _ = r.Parse(&pagination) + switch { + case pagination.Size > 100: + pagination.Size = 100 + + case pagination.Size <= 0: + pagination.Size = 10 + } + return m.Page(pagination.Page, pagination.Size) + } +} + +m.Handler(Paginate(r)).Scan(&users) +m.Handler(Paginate(r)).Scan(&articles) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..c415800389e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-Hook\347\211\271\346\200\247.md" @@ -0,0 +1,67 @@ +--- +slug: '/docs/core/gdb-chaining-hook' +title: 'ORM链式操作-Hook特性' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame框架,ORM链式操作,Hook特性,CRUD钩子,模型,数据库查询,gdb,Go语言,编程示例,代码优化] +description: '在GoFrame框架中使用Hook特性,为Model对象绑定CRUD钩子,从而实现对数据库操作的增强和优化。文中详细介绍了相关定义、Hook注册方法以及使用示例,通过挂钩函数对查询操作进行了演示。' +--- + +`Hook` 特性允许我们对特性的 `Model` 绑定 `CRUD` 钩子处理。 + +## 相关定义 + +相关 `Hook` 函数: + +```go +type ( + HookFuncSelect func(ctx context.Context, in *HookSelectInput) (result Result, err error) + HookFuncInsert func(ctx context.Context, in *HookInsertInput) (result sql.Result, err error) + HookFuncUpdate func(ctx context.Context, in *HookUpdateInput) (result sql.Result, err error) + HookFuncDelete func(ctx context.Context, in *HookDeleteInput) (result sql.Result, err error) +) + +// HookHandler manages all supported hook functions for Model. +type HookHandler struct { + Select HookFuncSelect + Insert HookFuncInsert + Update HookFuncUpdate + Delete HookFuncDelete +} +``` + +`Hook` 注册方法: + +```go +// Hook sets the hook functions for current model. +func (m *Model) Hook(hook HookHandler) *Model +``` + +## 使用示例 + +查询 `birth_day` 字段时,同时计算出当前用户的年龄: + +```go +// Hook function definition. +var hook = gdb.HookHandler{ + Select: func(ctx context.Context, in *gdb.HookSelectInput) (result gdb.Result, err error) { + result, err = in.Next(ctx) + if err != nil { + return + } + for i, record := range result { + if !record["birth_day"].IsEmpty() { + age := gtime.Now().Sub(record["birth_day"].GTime()).Hours() / 24 / 365 + record["age"] = gvar.New(age) + } + result[i] = record + } + return + }, +} +// It registers hook function, creates and returns a new `model`. +model := g.Model("user").Hook(hook) + +// The hook function takes effect on each ORM operation when using the `model`. +all, err := model.Where("status", "online").OrderAsc(`id`).All() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" new file mode 100644 index 00000000000..560e3f223ff --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\270\273\344\273\216\345\210\207\346\215\242.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/core/gdb-chaining-master-slave' +title: 'ORM链式操作-主从切换' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,gdb,主从切换,读写分离,数据库配置,ORM链式操作,SQL请求,节点切换,数据库压力分摊] +description: '在GoFrame框架中使用gdb实现应用层的主从配置和读写分离。通过简单的配置,gdb可以自动进行主从切换,大幅度提高数据库的运行效率和可用性。本文还提供了一些使用Master和Slave方法自定义节点操作的示例,帮助开发者更好地应对主从同步延迟带来的问题,确保数据的即时性和准确性。' +--- + +从之前的介绍我们知道 `gdb` 支持基于应用层的主从配置以及读写分离,并且所有的特性仅需要通过简单的配置即可实现, `gdb` 内部将会对SQL请求自动地进行主从切换。以下是一个简单的主从配置,包含一主一从: + +```yaml +database: + default: + - type: "mysql" + link: "root:12345678@tcp(192.168.1.1:3306)/test" + role: "master" + - type: "mysql" + link: "root:12345678@tcp(192.168.1.2:3306)/test" + role: "slave" +``` + +在大部分的场景中,我们的写入请求是到 `Master` 主节点,而读取请求是到 `Slave` 从节点,这样的好处是能够对数据库的请求进行压力分摊,并提高数据库的可用性。但在某些场景中,我们期望读取操作在 `Master` 节点上执行,特别是一些对于即时性要求比较高的场景(因为主从节点之间的数据同步是有延迟的)。 + +开发者可以通过 `Master` 和 `Slave` 方法自定义决定当前链式操作执行在哪个节点上。 + +我们来一个简单的示例。我们有一个订单系统,每天的流量比较大,因此数据库在主从同步时往往会存在 `1-500ms` 时间的延迟。在业务需求中,创建订单后需要立即展示订单列表页面。可以预料到如果该订单列表页面默认往从节点读取数据的话,很有可能用户在创建订单后在订单列表页面看不到最新创建的订单(因为数据库主从同步延迟)。这个问题,我们可以在订单列表页面设置为往主节点读取数据即可解决。 + +1. 在订单创建的时候,没有必要指定操作的节点,因为写入操作默认是在主节点上执行的。为简化示例,我们这里仅展示关键的代码: + + ```go + g.Model("order").Data(g.Map{ + "uid" : 1000, + "price" : 99.99, + // ... + }).Insert() + ``` + +2. 在订单列表页面查询时,我们需要使用 `Master` 方法指定查询操作是在主节点上进行,以避免读取延迟。 + + ```go + g.Model("order").Master().Where("uid", 1000).All() + ``` + + +` +` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..65dedfd596c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\344\272\213\345\212\241\345\244\204\347\220\206.md" @@ -0,0 +1,111 @@ +--- +slug: '/docs/core/gdb-chaining-transaction' +title: 'ORM链式操作-事务处理' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,事务处理,ORM链式操作,GoFrame框架,Transaction,TX接口,数据库对象,事务对象,链式操作,Commit/Rollback] +description: '在GoFrame框架中使用事务处理对于ORM链式操作的方法。通过Transaction和TX接口,可以实现对数据库的事务性操作,确保数据的一致性和可靠性。详细讲解了使用TX接口创建Model对象的方法及其事务处理中Commit和Rollback的机制。' +--- + +`Model` 对象也可以通过 `TX` 事务接口创建,通过事务对象创建的 `Model` 对象与通过 `DB` 数据库对象创建的 `Model` 对象功能是一样的,只不过前者的所有操作都是基于事务,而当事务提交或者回滚后,对应的 `Model` 对象不能被继续使用,否则会返回错误。因为该 `TX` 接口不能被继续使用,一个事务对象仅对应于一个事务流程, `Commit`/ `Rollback` 后即结束。 + +本章节仅对链式操作涉及到的事务处理方法做简单介绍,更详细的介绍请参考 [ORM事务处理](../ORM事务处理/ORM事务处理.md) 章节。 + +## 示例1,通过 `Transaction` + +为方便事务操作, `gdb` 提供了事务的闭包操作,通过 `Transaction` 方法实现,该方法定义如下: + +```go +func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error) +``` + +当给定的闭包方法返回的 `error` 为 `nil` 时,那么闭包执行结束后当前事务自动执行 `Commit` 提交操作;否则自动执行 `Rollback` 回滚操作。 +:::tip +如果闭包内部操作产生 `panic` 中断,该事务也将进行回滚。 +::: +```go +func Register() error { + return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + var ( + result sql.Result + err error + ) + // 写入用户基础数据 + result, err = tx.Table("user").Insert(g.Map{ + "name": "john", + "score": 100, + //... + }) + if err != nil { + return err + } + // 写入用户详情数据,需要用到上一次写入得到的用户uid + result, err = tx.Table("user_detail").Insert(g.Map{ + "uid": result.LastInsertId(), + "phone": "18010576258", + //... + }) + return err + }) +} +``` + +## 示例2,通过 `TX` 链式操作 + +我们也可以在链式操作中通过 `TX` 方法切换绑定的事务对象。多次链式操作可以绑定同一个事务对象,在该事务对象中执行对应的链式操作。 + +```go +func Register() error { + var ( + uid int64 + err error + ) + tx, err := g.DB().Begin() + if err != nil { + return err + } + // 方法退出时检验返回值, + // 如果结果成功则执行tx.Commit()提交, + // 否则执行tx.Rollback()回滚操作。 + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + // 写入用户基础数据 + uid, err = AddUserInfo(tx, g.Map{ + "name": "john", + "score": 100, + //... + }) + if err != nil { + return err + } + // 写入用户详情数据,需要用到上一次写入得到的用户uid + err = AddUserDetail(tx, g.Map{ + "uid": uid, + "phone": "18010576259", + //... + }) + return err +} + +func AddUserInfo(tx gdb.TX, data g.Map) (int64, error) { + result, err := g.Model("user").TX(tx).Data(data).Insert() + if err != nil { + return 0, err + } + uid, err := result.LastInsertId() + if err != nil { + return 0, err + } + return uid, nil +} + +func AddUserDetail(tx gdb.TX, data g.Map) error { + _, err := g.Model("user_detail").TX(tx).Data(data).Insert() + return err +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" new file mode 100644 index 00000000000..872d2c1c35b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\206\231\345\205\245\344\277\235\345\255\230.md" @@ -0,0 +1,187 @@ +--- +slug: '/docs/core/gdb-chaining-insert-save' +title: 'ORM链式操作-写入保存' +sidebar_position: 1 +hide_title: true +keywords: [ORM链式操作,数据写入,GoFrame,Insert,Replace,Save,数据库,批量操作,SQL,RawSQL] +description: 'GoFrame框架中ORM链式操作的写入保存方法,包括Insert、Replace、Save等方法的使用。这些方法支持自动单条或批量的数据写入,适用于多种数据库环境,并提供详细示例帮助理解如何结合使用Data方法进行数据操作。' +--- + +## 常用方法 + +### `Insert/Replace/Save` + +这几个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,区别如下: + +1. `Insert` + + 使用 `INSERT INTO` 语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据。 + +2. `Replace` + + 使用 `REPLACE INTO` 语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录。 + +3. `Save` + + 使用 `INSERT INTO` 语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据。对于部分数据库,例如 `PgSQL, SQL server, Oracle` 可以使用 `OnConflict` 方法指定冲突键。 + + ```go + db.Model(table).Data(g.Map{ + "id": 1, + "passport": "p1", + "password": "pw1", + }).OnConflict("id").Save() + ``` + + +> 在部分数据库类型中,并不支持 `Replace/Save` 方法,具体请参考 [链式操作](../ORM链式操作/ORM链式操作.md) 介绍章节。 + +这几个方法需要结合 `Data` 方法使用,该方法用于传递数据参数,用于数据写入/更新等写操作。 + +### `InsertIgnore` + +用于写入数据时如果写入的数据中存在主键或者唯一索引时,忽略错误继续执行写入。该方法定义如下: + +```go +func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error) +``` + +### `InsertAndGetId` + +用于写入数据时并直接返回自增字段的 `ID`。该方法定义如下: + +```go +func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error) +``` + +### `OnDuplicate/OnDuplicateEx` + +`OnDuplicate/OnDuplicateEx` 方法需要结合 `Save` 方法一起使用,用于指定 `Save` 方法的更新/不更新字段,参数为字符串、字符串数组、 `Map`。例如: + +```go +OnDuplicate("nickname, age") +OnDuplicate("nickname", "age") +OnDuplicate(g.Map{ + "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"), +}) +OnDuplicate(g.Map{ + "nickname": "passport", +}) +``` + +其中 `OnDuplicateEx` 用于排除指定忽略更新的字段,排除的字段需要在写入的数据集合中。 + +## 使用示例 + +### 示例1,基本使用 + +数据写入/保存方法需要结合 `Data` 方法使用,方法的参数类型可以为 `Map/Struct/Slice`: + +```go +// INSERT INTO `user`(`name`) VALUES('john') +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`uid`,`name`) VALUES(10000,'john') +g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`uid`,`name`) VALUES(10000,'john') +g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`uid`,`name`) VALUES(10001,'john') ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`) +g.Model("user").Data(g.Map{"uid": 10001, "name": "john"}).Save() +``` + +也可以不使用 `Data` 方法,而给写入/保存方法直接传递数据参数: + +```go +g.Model("user").Insert(g.Map{"name": "john"}) +g.Model("user").Replace(g.Map{"uid": 10000, "name": "john"}) +g.Model("user").Save(g.Map{"uid": 10001, "name": "john"}) +``` + +数据参数也常用 `struct` 类型,例如当表字段为 `uid/name/site` 时: + +```go +type User struct { + Uid int `orm:"uid"` + Name string `orm:"name"` + Site string `orm:"site"` +} +user := &User{ + Uid: 1, + Name: "john", + Site: "https://goframe.org", +} +// INSERT INTO `user`(`uid`,`name`,`site`) VALUES(1,'john','https://goframe.org') +g.Model("user").Data(user).Insert() +``` + +### 示例2,数据批量写入 + +通过给 `Data` 方法输入 `Slice` 数组类型的参数,用以实现批量写入。数组元素需要为 `Map` 或者 `Struct` 类型,以便于数据库组件自动获取字段信息并生成批量操作 `SQL`。 + +```go +// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2'),('john_3') +g.Model("user").Data(g.List{ + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, +}).Insert() +``` + +可以通过 `Batch` 方法指定批量操作中分批写入条数数量(默认是 `10`),以下示例将会被拆分为两条写入请求: + +```go +// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2') +// INSERT INTO `user`(`name`) VALUES('john_3') +g.Model("user").Data(g.List{ + {"name": "john_1"}, + {"name": "john_2"}, + {"name": "john_3"}, +}).Batch(2).Insert() +``` + +### 示例3,数据批量保存 + +批量保存操作与单条保存操作原理是一样的,当写入的数据中存在主键或者唯一索引时将会更新原有记录值,否则新写入一条记录。 + +> `oracle, dm, mssql` 不支持批量保存。 + +```go +// INSERT INTO `user`(`uid`,`name`) VALUES(10000,'john_1'),(10001,'john_2'),(10002,'john_3') +// ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`) +g.Model("user").Data(g.List{ + {"uid":10000, "name": "john_1"}, + {"uid":10001, "name": "john_2"}, + {"uid":10002, "name": "john_3"}, +}).Save() +``` + +## `RawSQL` 语句嵌入 + +`gdb.Raw` 是字符串类型,该类型的参数将会直接作为 `SQL` 片段嵌入到提交到底层的 `SQL` 语句中,不会被自动转换为字符串参数类型、也不会被当做预处理参数。更详细的介绍请参考章节: [ORM高级特性-RawSQL](../ORM高级特性/ORM高级特性-RawSQL.md)。例如: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES('id+2','john','123456','now()') +g.Model("user").Data(g.Map{ + "id": "id+2", + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": "now()", +}).Insert() +// 执行报错:Error Code: 1136. Column count doesn't match value count at row 1 +``` + +使用 `gdb.Raw` 改造后: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES(id+2,'john','123456',now()) +g.Model("user").Data(g.Map{ + "id": gdb.Raw("id+2"), + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": gdb.Raw("now()"), +}).Insert() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" new file mode 100644 index 00000000000..65cf1e819ae --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\216\267\345\217\226.md" @@ -0,0 +1,44 @@ +--- +slug: '/docs/core/gdb-chaining-fields-retrieving' +title: 'ORM链式操作-字段获取' +sidebar_position: 7 +hide_title: true +keywords: [ORM,链式操作,字段获取,FieldsStr,FieldsExStr,GoFrame,GoFrame框架,数据库字段,字段前缀,字段排除] +description: '使用GoFrame框架中的ORM链式操作来获取数据库表字段的技巧,包括使用FieldsStr和FieldsExStr方法获取指定表的字段及排除字段的用法,支持字段前缀自定义,提升开发效率和代码可读性。' +--- + +## `FieldsStr/FieldsExStr` 字段获取 + +1. `FieldsStr` 用于获取指定表的字段,并可给定字段前缀,字段之间使用" `,`"符号连接成字符串返回; +2. `FieldsExStr` 用于获取指定表中例外的字段,并可给定字段前缀,字段之间使用" `,`"符号连接成字符串返回; + +### `FieldsStr` 示例 + +1. 假如 `user` 表有4个字段 `uid`, `nickname`, `passport`, `password`。 +2. 查询字段 + ```go + // uid,nickname,passport,password + g.Model("user").FieldsStr() + ``` + +3. 查询字段给指定前缀 + ```go + // gf_uid,gf_nickname,gf_passport,gf_password + g.Model("user").FieldsStr("gf_") + ``` + + +### `FieldsExStr` 示例 + +1. 假如 `user` 表有4个字段 `uid`, `nickname`, `passport`, `password`。 +2. 查询字段排除 + ```go + // uid,nickname + g.Model("user").FieldsExStr("passport, password") + ``` + +3. 查询字段排除并给定前缀 + ```go + // gf_uid,gf_nickname + g.Model("user").FieldsExStr("passport, password", "gf_") + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" new file mode 100644 index 00000000000..37b9a1def28 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\255\227\346\256\265\350\277\207\346\273\244.md" @@ -0,0 +1,237 @@ +--- +slug: '/docs/core/gdb-chaining-fields-filtering' +title: 'ORM链式操作-字段过滤' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,字段过滤,Fields,FieldsEx,OmitEmpty,OmitNil,GoFrame DAO,数据表] +description: '在使用GoFrame框架进行数据库操作时如何进行字段过滤。详细描述了Fields和FieldsEx方法的用途与示例,并深入探讨了OmitEmpty及OmitNil特性如何帮助在数据库写入过程中过滤空值数据。此外,还探讨了在数据查询过程中空值对条件参数的影响。' +--- + +## `Fields/FieldsEx` 字段过滤 + +1. `Fields` 用于指定需要操作的表字段,包括查询字段、写入字段、更新字段等过滤; +2. `FieldsEx` 用于例外的字段指定,可用于查询字段、写入字段、更新字段等过滤; + +### `Fields` 示例 + +1. 假如 `user` 表有4个字段 `uid`, `nickname`, `passport`, `password`。 +2. 查询字段过滤 + ```go + // SELECT `uid`,`nickname` FROM `user` ORDER BY `uid` asc + g.Model("user").Fields("uid, nickname").Order("uid asc").All() + ``` + +3. 写入字段过滤 + ```go + m := g.Map{ + "uid" : 10000, + "nickname" : "John Guo", + "passport" : "john", + "password" : "123456", + } + g.Model(table).Fields("nickname,passport,password").Data(m).Insert() + // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456') + ``` +4. 支持`gdb.Raw`输入 + ```go + // SELECT 1 FROM `user` WHERE `id`=10 + g.Model("user").Fields(gdb.Raw("1")).Where("id", 10).Value() + ``` + +### `FieldsEx` 示例 + +1. 假如 `user` 表有4个字段 `uid`, `nickname`, `passport`, `password`。 +2. 查询字段排除 + + ```go + // SELECT `uid`,`nickname` FROM `user` + g.Model("user").FieldsEx("passport, password").All() + ``` + +1. 写入字段排除 + ```go + m := g.Map{ + "uid" : 10000, + "nickname" : "John Guo", + "passport" : "john", + "password" : "123456", + } + g.Model(table).FieldsEx("uid").Data(m).Insert() + // INSERT INTO `user`(`nickname`,`passport`,`password`) VALUES('John Guo','john','123456') + ``` + + +## `OmitEmpty` 空值过滤 + +当 `map`/ `struct` 中存在空值如 `nil`, `""`, `0` 时,默认情况下, `gdb` 将会将其当做正常的输入参数,因此这些参数也会被更新到数据表。 `OmitEmpty` 特性可以在将数据写入到数据库之前过滤空值数据的字段。 + +相关方法: + +```go +func (m *Model) OmitEmpty() *Model +func (m *Model) OmitEmptyWhere() *Model +func (m *Model) OmitEmptyData() *Model +``` + +`OmitEmpty` 方法会同时过滤 `Where` 及 `Data` 中的空值数据,而通过 `OmitEmptyWhere/OmitEmptyData` 方法可以执行特定的字段过滤。 + +### 写入/更新操作 + +空值会影响于写入/更新操作方法,如 `Insert`, `Replace`, `Update`, `Save` 操作。如以下操作(以 `map` 为例, `struct` 同理): + +```go +// UPDATE `user` SET `name`='john',update_time=null WHERE `id`=1 +g.Model("user").Data(g.Map{ + "name" : "john", + "update_time" : nil, +}).Where("id", 1).Update() +``` + +针对空值情况,我们可以通过 `OmitEmpty` 方法来过滤掉这些空值。例如,以上示例可以修改为: + +```go +// UPDATE `user` SET `name`='john' WHERE `id`=1 +g.Model("user").OmitEmpty().Data(g.Map{ + "name" : "john", + "update_time" : nil, +}).Where("id", 1).Update() +``` + +对于 `struct` 数据参数,我们也可以进行空值过滤。操作示例: + +```go +type User struct { + Id int `orm:"id"` + Passport string `orm:"passport"` + Password string `orm:"password"` + NickName string `orm:"nickname"` + CreateTime string `orm:"create_time"` + UpdateTime string `orm:"update_time"` +} +user := User{ + Id : 1, + NickName : "john", + UpdateTime: gtime.Now().String(), +} +g.Model("user").OmitEmpty().Data(user).Insert() +// INSERT INTO `user`(`id`,`nickname`,`update_time`) VALUES(1,'john','2019-10-01 12:00:00') +``` +:::warning +注意哟,批量写入/更新操作中 `OmitEmpty` 方法将会失效,因为在批量操作中,必须保证每个写入记录的字段是统一的。 +::: +关于 `omitempty` 标签与 `OmitEmpty` 方法: + +1. 针对于 `struct` 的空值过滤大家会想到 `omitempty` 的标签。该标签常用于 `json` 转换的空值过滤,也在某一些第三方的 `ORM` 库中用作 `struct` 到数据表字段的空值过滤,即当属性为空值时不做转换。 +2. `omitempty` 标签与 `OmitEmpty` 方法所达到的效果是一样的。在 `ORM` 操作中,我们不建议对 `struct` 使用 `omitempty` 的标签来控制字段的空值过滤,而建议使用 `OmitEmpty` 方法来做控制。因为该标签一旦加上之后便绑定到了 `struct` 上,没有办法做灵活控制;而通过 `OmitEmpty` 方法使得开发者可以选择性地、根据业务场景对 `struct` 做空值过滤,操作更加灵活。 + +### 数据查询操作 + +空值也会影响数据查询操作,主要是影响 `where` 条件参数。我们可以通过 `OmitEmpty` 方法过滤条件参数中的空值。 + +使用示例: + +```go +// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1 +r, err := g.Model("user").Where(g.Map{ + "nickname" : "", + "passport" : "john", +}).OmitEmpty().One() +``` + +```go +type User struct { + Id int `orm:"id"` + Passport string `orm:"passport"` + Password string `orm:"password"` + NickName string `orm:"nickname"` + CreateTime string `orm:"create_time"` + UpdateTime string `orm:"update_time"` +} +user := User{ + Passport : "john", +} +r, err := g.Model("user").OmitEmpty().Where(user).One() +// SELECT * FROM `user` WHERE `passport`='john' LIMIT 1 +``` + +## `OmitNil` 空值过滤 + +### 基本介绍 + +当 `map`/ `struct` 中存在空值如 `nil` 时,默认情况下, `gdb` 将会将其当做正常的输入参数,因此这些参数也会被更新到数据表。 `OmitNil` 特性可以在将数据写入到数据库之前过滤空值数据的字段。与 `OmitEmpty` 特性的区别在于, `OmitNil` 只会过滤值为 `nil` 的空值字段,其他空值如 `""`, `0` 并不会被过滤。 + +相关方法: + +```go +func (m *Model) OmitNil() *Model +func (m *Model) OmitNilWhere() *Model +func (m *Model) OmitNilData() *Model +``` + +`OmitEmpty` 方法会同时过滤 `Where` 及 `Data` 中的空值数据,而通过 `OmitEmptyWhere/OmitEmptyData` 方法可以执行特定的字段过滤。 + +### 使用 `do` 对象进行字段过滤 + +如果使用 `GoFrame` 工程目录,通过 `gf gen dao` 或者 `make dao` 指令会自动根据配置的数据库生成对应的数据表 `dao/entity/do` 文件,如果在数据库操作中使用 `do` 对象,那么将会自动过滤未赋值的字段。例如: + +生成的 `do` 对象结构体定义 + +```go +// User is the golang structure of table user for DAO operations like Where/Data. +type User struct { + g.Meta `orm:"table:user, do:true"` + Id interface{} // User ID + Passport interface{} // User Passport + Password interface{} // User Password + Nickname interface{} // User Nickname + CreateAt *gtime.Time // Created Time + UpdateAt *gtime.Time // Updated Time +} +``` + +数据写入: + +```go +dao.User.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Passport: in.Passport, + Password: in.Password, + Nickname: in.Nickname, + }).Insert() + return err +}) +``` + +数据查询: + +```go +var user *entity.User +err = dao.User.Ctx(ctx).Where(do.User{ + Passport: in.Passport, + Password: in.Password, +}).Scan(&user) +``` + +## `Filter` 字段过滤(已内置) + +~~`gdb` 可以自动同步 **数据表结构** 到程序缓存中(缓存不过期,直至程序重启/重新部署),并且可以过滤提交参数中不符合表结构的数据项,该特性可以使用 `Filter` 方法实现。常用于新增/删除操作中输入 `map/struct/[]map/[]string` 参数类型的场景。~~ + +~~使用示例,假如 `user` 表有4个字段 `uid`, `nickname`, `passport`, `password`:~~ + +```go +r, err := g.Model("user").Filter().Data(g.Map{ + "id" : 1, + "uid" : 1, + "passport" : "john", + "password" : "123456", +}).Insert() +// INSERT INTO user(uid,passport,password) VALUES(1, "john", "123456") +``` + +~~其中 `id` 为不存在的字段,在写入数据时将会被过滤掉,不至于被构造成写入SQL中产生执行错误。~~ +:::tip +~~数据库没有设计为 `Data` 方法做自动过滤,而是需要开发者调用 `Filter` 方法来手动指定过滤,目的是友好地提醒开发者可能误写/传递错误了字段名称。如果强制性的自动过滤可能会引起难以预料的业务逻辑异常,例如,由于字段名称拼写错误导致自动过滤了本来需要输入的字段,导致写入数据库的数据不完整。~~ +::: +:::warning +从 `GoFrame v1.15.7` 版本开始,根据社区整体反馈,为提高组件易用性, `filter` 特性被设置为默认开启,不再需要显示调用, `Filter` 方法已被标记废弃。 +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" new file mode 100644 index 00000000000..35c5f4d7701 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\345\257\271\350\261\241\350\276\223\345\205\245.md" @@ -0,0 +1,34 @@ +--- +slug: '/docs/core/gdb-chaining-object-parameter' +title: 'ORM链式操作-对象输入' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM链式操作,对象输入,gdb,struct映射,数据库ORM,映射关系,Go语言,类型转换] +description: '该文档介绍了如何在GoFrame框架中使用链式操作的对象输入功能,支持不同类型的数据参数,使得gdb具有极高的灵活性。详细说明了使用struct对象进行参数输入时的映射关系以及标签的优先级,以实现数据库ORM的有效转换。' +--- + +`Data/Where/WherePri/And/Or` 方法支持任意的 `string/map/slice/struct/*struct` 数据类型参数,该特性为 `gdb` 提供了很高的灵活性。当使用 `struct`/ `*struct` 对象作为输入参数时,将会被自动解析为 `map` 类型,只有 `struct` 的 **公开属性** 能够被转换,并且支持 `orm`/ `gconv`/ `json` 标签,用于定义转换后的键名,即与表字段的映射关系。 + +例如: + +```go +type User struct { + Uid int `orm:"user_id"` + Name string `orm:"user_name"` + NickName string `orm:"nick_name"` +} +// 或者 +type User struct { + Uid int `gconv:"user_id"` + Name string `gconv:"user_name"` + NickName string `gconv:"nick_name"` +} +// 或者 +type User struct { + Uid int `json:"user_id"` + Name string `json:"user_name"` + NickName string `json:"nick_name"` +} +``` + +其中, `struct` 的属性应该是公开属性(首字母大写), `orm` 标签对应的是数据表的字段名称。表字段的对应关系标签既可以使用 `orm`,也可以用 `gconv`,还可以使用传统的 `json` 标签,但是当三种标签都存在时, `orm` 标签的优先级更高。为避免将 `struct` 对象转换为 `JSON` 数据格式返回时与 `JSON` 编码标签冲突,推荐使用 `orm` 标签来实现数据库 `ORM` 的映射关系。更详细的转换规则请查看 [类型转换-Map转换](../../类型转换/类型转换-Map转换.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" new file mode 100644 index 00000000000..a1282b12818 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\202\262\350\247\202\351\224\201 & \344\271\220\350\247\202\351\224\201.md" @@ -0,0 +1,59 @@ +--- +slug: '/docs/core/gdb-chaining-locks' +title: 'ORM链式操作-悲观锁 & 乐观锁' +sidebar_position: 15 +hide_title: true +keywords: [悲观锁,乐观锁,GoFrame,GoFrame框架,链式操作,SQL,共享锁,FOR UPDATE,LOCK IN SHARE MODE,事务] +description: '在GoFrame框架中如何通过链式操作实现悲观锁和乐观锁。悲观锁用于在每次数据访问时上锁以避免冲突,常用于高并发场景;而乐观锁则通过版本号机制检查数据更新,适用于多读少写的场合。详细分析了适用场景、实现方法及锁机制的优缺点,帮助开发者优化数据库性能。' +--- + +`悲观锁(Pessimistic Lock)`,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁、表锁、读锁、写锁等,都是在做操作之前先上锁。 + +`乐观锁(Optimistic Lock)`,顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。 + +### 悲观锁使用 + +相关方法: + +```go +func (m *Model) LockUpdate() *Model +func (m *Model) LockShared() *Model +``` + +`gdb` 模块的链式操作提供了两个方法帮助您在 `SQL` 语句中实现“悲观锁”。可以在查询中使用 `LockShared` 方法从而在运行语句时带一把”共享锁“。共享锁可以避免被选择的行被修改直到事务提交: + +```go +g.Model("users").Ctx(ctx).Where("votes>?", 100).LockShared().All(); +``` + +上面这个查询等价于下面这条 SQL 语句: + +```sql +SELECT * FROM `users` WHERE `votes` > 100 LOCK IN SHARE MODE +``` + +此外你还可以使用 `LockUpdate` 方法。该方法用于创建 `FOR UPDATE` 锁,避免选择行被其它共享锁修改或删除: + +```go +g.Model("users").Ctx(ctx).Where("votes>?", 100).LockUpdate().All(); +``` + +上面这个查询等价于下面这条 SQL 语句: + +```sql +SELECT * FROM `users` WHERE `votes` > 100 FOR UPDATE +``` + +`FOR UPDATE` 与 `LOCK IN SHARE MODE` 都是用于确保被选中的记录值不能被其它事务更新(上锁),两者的区别在于 `LOCK IN SHARE MODE` 不会阻塞其它事务读取被锁定行记录的值,而 `FOR UPDATE` 会阻塞其他锁定性读对锁定行的读取(非锁定性读仍然可以读取这些记录, `LOCK IN SHARE MODE` 和 `FOR UPDATE` 都是锁定性读)。 + +这么说比较抽象,我们举个计数器的例子:在一条语句中读取一个值,然后在另一条语句中更新这个值。使用 `LOCK IN SHARE MODE` 的话可以允许两个事务读取相同的初始化值,所以执行两个事务之后最终计数器的值 `+1`;而如果使用 `FOR UPDATE` 的话,会锁定第二个事务对记录值的读取直到第一个事务执行完成,这样计数器的最终结果就是 `+2` 了。 + +### 乐观锁使用 + +乐观锁,大多是基于数据版本 ( `Version`)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 " `version`" 字段来实现。 + +读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。 + +### 锁机制总结 + +两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果经常产生冲突,上层应用会不断的进行重试,这样反倒是降低了性能,所以这种情况下用悲观锁就比较合适。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" new file mode 100644 index 00000000000..b979147bbe5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\345\272\223\345\210\207\346\215\242.md" @@ -0,0 +1,37 @@ +--- +slug: '/docs/core/gdb-chaining-schema' +title: 'ORM链式操作-数据库切换' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,链式操作,数据库切换,DB对象,Model对象,配置分组,Schema方法,跨域操作] +description: '在使用GoFrame框架进行ORM链式操作时切换数据库。我们通过不同的配置分组、运行时更改单例对象的数据库配置、使用Schema方法进行链式操作,以及通过表名中带数据库名称来实现多种数据库切换方案。这些方法为开发者提供了灵活的数据库操作方式。' +--- + +我们知道数据库的配置中有支持对默认数据库的配置,因此 `DB` 对象及 `Model` 对象在初始化的时候已经绑定到了特定的数据库上。运行时切换数据库有几种方案(假如我们的数据库有 `user` 用户数据库和 `order` 订单数据库): + +1. 通过不同的配置分组来实现。这需要在配置文件中配置不同的分组配置,随后在程序中可以通过 `g.DB("分组名称")` 来获取特定数据库的单例对象。 +2. 通过运行时 `DB.SetSchema` 方法切换单例对象的数据库,需要注意的是由于修改的是单例对象的数据库配置,因此影响是全局的: + ```go + g.DB().SetSchema("user-schema") + g.DB().SetSchema("order-schema") + ``` + +3. 通过链式操作 `Schema` 方法创建 `Schema` 数据库对象,并通过该数据库对象创建模型对象并执行后续链式操作: + ```go + g.DB().Schema("user-schema").Model("user").All() + g.DB().Schema("order-schema").Model("order").All() + ``` + +4. 也可以通过链式操作 `Model.Schema` 方法设置当前链式操作对应的数据库,没有设置的情况下使用的是其 `DB` 或者 `TX` 默认连接的数据库: + ```go + g.Model("user").Schema("user-schema").All() + g.Model("order").Schema("order-schema").All() + ``` + :::tip + 注意两种使用方式的差别,前一种方式来自于 `Schema` 对象创建 `Model` 对象后执行操作;后一种方式是通过修改当前 `Model` 对象操作的数据库名称达到切换数据库的目的。 + ::: +5. 此外,假如当前数据库操作配置的用户有权限,那么可以直接通过表名中带数据库名称实现跨域操作,甚至跨域关联查询: + ```go + // SELECT * FROM `order`.`order` o LEFT JOIN `user`.`user` u ON (o.uid=u.id) WHERE u.id=1 LIMIT 1 + g.Model("order.order o").LeftJoin("user.user u", "o.uid=u.id").Where("u.id", 1).One() + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" new file mode 100644 index 00000000000..81284febc84 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-AllAndCount.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/core/gdb-chaining-query-all-and-count' +title: 'ORM查询-AllAndCount' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM查询,AllAndCount,分页查询,数据查询,总数量查询,v2.5.0,记录列表,查询逻辑简化] +description: '该文档介绍了GoFrame框架中从v2.5.0版本开始提供的AllAndCount方法,该方法用于在分页查询场景中同时检索数据记录列表及总数量,简化查询逻辑。通过在查询时忽略Limit/Page操作,AllAndCount方法能够提供一种便捷的方式对数据进行检索和计数。' +--- + +## 基本介绍 +该方法从 `v2.5.0` 版本开始提供,用于同时查询数据记录列表及总数量,一般用于分页查询场景中,简化分页查询逻辑。 + +方法定义: + +```go +// AllAndCount retrieves all records and the total count of records from the model. +// If useFieldForCount is true, it will use the fields specified in the model for counting; +// otherwise, it will use a constant value of 1 for counting. +// It returns the result as a slice of records, the total count of records, and an error if any. +// The where parameter is an optional list of conditions to use when retrieving records. +// +// Example: +// +// var model Model +// var result Result +// var count int +// where := []interface{}{"name = ?", "John"} +// result, count, err := model.AllAndCount(true) +// if err != nil { +// // Handle error. +// } +// fmt.Println(result, count) +func (m *Model) AllAndCount(useFieldForCount bool) (result Result, totalCount int, err error) +``` + +在方法内部查询总数量时,将会忽略查询中的 `Limit/Page` 操作。 + +## 使用示例 + +```go +// SELECT `uid`,`name` FROM `user` WHERE `status`='deleted' LIMIT 0,10 +// SELECT COUNT(`uid`,`name`) FROM `user` WHERE `status`='deleted' +all, count, err := Model("user").Fields("uid", "name").Where("status", "deleted").Limit(0, 10).AllAndCount(true) + +// SELECT `uid`,`name` FROM `user` WHERE `status`='deleted' LIMIT 0,10 +// SELECT COUNT(1) FROM `user` WHERE `status`='deleted' +all, count, err := Model("user").Fields("uid", "name").Where("status", "deleted").Limit(0, 10).AllAndCount(false) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" new file mode 100644 index 00000000000..6734cb8f4af --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Exist.md" @@ -0,0 +1,42 @@ +--- +slug: '/docs/core/gdb-chaining-query-exist' +title: 'ORM查询-Exist' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM查询,Exist方法,数据检索,MySQL表结构,模型查询,Where条件,Go语言,查询效率] +description: '使用GoFrame框架的Exist方法能够有效判断满足特定条件的数据是否存在而无需获取完整数据结果。配合MySQL表结构,通过SELECT 1方式提升查询效率,降低不必要的数据传输。本文包含方法定义、MySQL表结构示例及实际使用情境,帮助开发者更好地优化模型查询环节。' +--- + +`Exist`方法可以更高效地检索所给的`Where`条件数据是否存在,而不是查询完整的数据结果后返回。 + +方法定义: +```go +func (m *Model) Exist(where ...interface{}) (bool, error) +``` + +## 示例SQL +这是后续示例代码中用到的`MySQL`表结构。 + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +## 使用示例 + +查询完整数据: +```go +// SELECT * FROM `user` WHERE (id > 1) AND `deleted_at`=0 +g.Model("user").Where("id > ?", 1).All() +``` + +使用`Exist`方法: +```go +// SELECT 1 FROM `user` WHERE (id > 1) AND `deleted_at`=0 LIMIT 1 +g.Model("user").Where("id > ?", 1).Exist() +``` + +可以看到底层是使用`SELECT 1`来查询结果,即如果结果存在则返回`1`,否则什么也不返回。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" new file mode 100644 index 00000000000..8d83d25f1f3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Join\346\237\245\350\257\242.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/gdb-chaining-query-join' +title: 'ORM查询-Join查询' +sidebar_position: 5 +hide_title: true +keywords: [ORM查询,LeftJoin,RightJoin,InnerJoin,GoFrame,关联查询,数据表别名,字段操作符,联表查询,数据聚合] +description: '在GoFrame框架中如何使用ORM进行LeftJoin、RightJoin和InnerJoin查询,包括使用不同的关联查询方法及其应用场景。文中强调在大数据量和高并发环境中谨慎使用Join操作,推荐使用代码实现数据聚合。此外,还提供了通过自定义数据表别名和字段操作符进行join查询的示例,并结合dao展示具体的使用方法。' +--- + +## `*Join`系列方法 + +1. `LeftJoin` 左关联查询。 +2. `RightJoin` 右关联查询。 +3. `InnerJoin` 内关联查询。 +:::note +其实我们并不推荐使用 `Join` 进行联表查询,特别是在数据量比较大、并发请求量比较高的场景中,容易产生性能问题,也容易提高维护的复杂度。建议您在确定有此必要的场景下使用。 +此外,您也可以参考 +[ORM链式操作-模型关联](../ORM%E9%93%BE%E5%BC%8F%E6%93%8D%E4%BD%9C-%E6%A8%A1%E5%9E%8B%E5%85%B3%E8%81%94/%E6%A8%A1%E5%9E%8B%E5%85%B3%E8%81%94-%E5%8A%A8%E6%80%81%E5%85%B3%E8%81%94-ScanList.md) +章节,数据库只负责存储数据和简单的单表操作,通过 `ORM` 提供的功能在代码层面实现数据聚合。 +::: +使用示例: + +```go +// 查询符合条件的单条记录(第一条) +// SELECT u.*,ud.site FROM user u LEFT JOIN user_detail ud ON u.uid=ud.uid WHERE u.uid=1 LIMIT 1 +g.Model("user u").LeftJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.site").Where("u.uid", 1).One() + +// 查询指定字段值 +// SELECT ud.site FROM user u RIGHT JOIN user_detail ud ON u.uid=ud.uid WHERE u.uid=1 LIMIT 1 +g.Model("user u").RightJoin("user_detail ud", "u.uid=ud.uid").Fields("ud.site").Where("u.uid", 1).Value() + +// 分组及排序 +// SELECT u.*,ud.city FROM user u INNER JOIN user_detail ud ON u.uid=ud.uid GROUP BY city ORDER BY register_time asc +g.Model("user u").InnerJoin("user_detail ud", "u.uid=ud.uid").Fields("u.*,ud.city").Group("city").Order("register_time asc").All() + +// 不使用join的联表查询 +// SELECT u.*,ud.city FROM user u,user_detail ud WHERE u.uid=ud.uid +g.Model("user u,user_detail ud").Where("u.uid=ud.uid").Fields("u.*,ud.city").All() +``` + +## 自定义数据表别名 + +```go +// SELECT * FROM `user` AS u LEFT JOIN `user_detail` as ud ON(ud.id=u.id) WHERE u.id=1 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 1).One() +g.Model("user").As("u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 1).One() +``` + +## `*JoinOnFields`系列方法 + +`LeftJoinOnFields/RightJoinOnFields/InnerJoinOnFields`这三个方法可以指定字段和操作符进行`join`查询,使用示例: + +```go +// 查询符合条件的单条记录(第一条) +// SELECT user.*,user_detail.address FROM user LEFT JOIN user_detail ON (user.id = user_detail.uid) WHERE user.id=1 LIMIT 1 +g.Model("user").LeftJoinOnFields("user_detail", "id", "=", "uid").Fields("user.*,user_detail.address").Where("id", 1).One() + +// 查询多条记录 +// SELECT user.*,user_detail.address FROM user RIGHT JOIN user_detail ON (user.id = user_detail.uid) +g.Model("user").RightJoinOnFields("user_detail", "id", "=", "uid").Fields("user.*,user_detail.address").All() +``` + +## 结合 `dao` 使用示例 + +```go +// SELECT resource_task_schedule.id,...,time_window.time_window +// FROM `resource_task_schedule` +// LEFT JOIN `time_window` ON (`resource_task_schedule`.`resource_id`=`time_window`.`resource_id`) +// WHERE (time_window.`status`="valid") AND (`time_window`.`start_time` <= 3600) +var ( + orm = dao.ResourceTaskSchedule.Ctx(ctx) + tsTable = dao.ResourceTaskSchedule.Table() + tsCls = dao.ResourceTaskSchedule.Columns() + twTable = dao.TimeWindow.Table() + twCls = dao.TimeWindow.Columns() + scheduleItems []scheduleItem +) +orm = orm.FieldsPrefix(tsTable, tsCls) +orm = orm.FieldsPrefix(twTable, twCls.TimeWindow) +orm = orm.LeftJoinOnField(twTable, twCls.ResourceId) +orm = orm.WherePrefix(twTable, twCls.Status, "valid") +orm = orm.WherePrefixLTE(twTable, twCls.StartTime, 3600) +err = orm.Scan(&scheduleItems) +``` + +```go +// SELECT DISTINCT resource_info.* FROM `resource_info` +// LEFT JOIN `resource_network` ON (`resource_info`.`resource_id`=`resource_network`.`resource_id`) +// WHERE (`resource_info`.`resource_id` like '%10.0.1.3%') +// or (`resource_info`.`resource_name` like '%10.0.1.3%') +// or (`resource_network`.`vip`like '%10.0.1.3%') +// ORDER BY `id` Desc LIMIT 0,2 +var ( + orm = dao.ResourceInfo.Ctx(ctx).OmitEmpty() + rTable = dao.ResourceInfo.Table() + rCls = dao.ResourceInfo.Columns() + nTable = dao.ResourceNetwork.Table() + nCls = dao.ResourceNetwork.Columns() +) +orm = orm.LeftJoinOnField(nTable, rCls.ResourceId) +orm = orm.WherePrefix(rTable, do.ResourceInfo{ + AppId: req.AppIds, + ResourceId: req.ResourceIds, + Region: req.Regions, + Zone: req.Zones, + ResourceName: req.ResourceNames, + Status: req.Statuses, + BusinessType: req.Products, + Engine: req.Engines, + Version: req.Versions, +}) +orm = orm.WherePrefix(nTable, do.ResourceNetwork{ + Vip: req.Vips, + VpcId: req.VpcIds, + SubnetId: req.SubnetIds, +}) +// Fuzzy like querying. +if req.Key != "" { + var ( + keyLike = "%" + req.Key + "%" + ) + whereFormat := fmt.Sprintf( + "(`%s`.`%s` like ?) or (`%s`.`%s` like ?) or (`%s`.`%s`like ?) ", + rTable, rCls.ResourceId, + rTable, rCls.ResourceName, + nTable, nCls.Vip, + ) + orm = orm.Where(whereFormat, keyLike, keyLike, keyLike) +} +// Resource items. +err = orm.Distinct().FieldsPrefix(rTable, "*").Order(req.Order, req.OrderDirection).Limit(req.Offset, req.Limit).Scan(&res.Items) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" new file mode 100644 index 00000000000..b7bb2dcb44e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-ScanAndCount.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gdb-chaining-query-scan-and-count' +title: 'ORM查询-ScanAndCount' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM查询,ScanAndCount,分页查询,Limit,Page,数据查询,总数量查询,链式查询] +description: '在使用GoFrame框架进行ORM查询时简化分页查询场景。通过ScanAndCount方法,可以在一次操作中完成数据查询和总数量查询,有效减少代码冗余,提高开发效率。适用于需要同时获取数据和其总数量的情况,如分页查询。' +--- + +## 基本介绍 + +在分页查询场景中,我们往往需要先调用 `Scan` 方法结合 `Limit/Page` 链式操作方法查询列表,随后再去掉 `Limit/Page` 链式操作方法查询总数量。这一过程较为繁琐,因此从 `v2.5.0` 版本开始,框架提供了 `ScanAndCount` 方法,用于简化分页查询的场景。 + +## 使用示例 +:::tip +示例代码来源于业务项目案例,仅供参考理解,无法独立运行。 +::: +使用传统的分页查询逻辑代码: + +```go +// GetList 获取实例的用户列表. +func (s sUserInfo) GetList(ctx context.Context, in model.UserInfoGetListInput) (items []entity.UserInfo, total int, err error) { + items = make([]entity.UserInfo, 0) + orm := dao.UserInfo.Ctx(ctx).Where(do.UserInfo{ + ResourceId: in.ResourceId, + Status: in.Statuses, + }) + err = orm.Order(in.OrderBy, in.OrderDirection).Limit(in.Offset, in.Limit).Scan(&items) + if err != nil { + return + } + total, err = orm.Count() + return +} +``` + +使用 `ScanAndCount` 方法实现分页查询: + +```go +// GetList 获取实例的用户列表. +func (s sUserInfo) GetList(ctx context.Context, in model.UserInfoGetListInput) (items []entity.UserInfo, total int, err error) { + items = make([]entity.UserInfo, 0) + err = dao.UserInfo.Ctx(ctx).Where(do.UserInfo{ + ResourceId: in.ResourceId, + Status: in.Statuses, + }). + Order(in.OrderBy, in.OrderDirection). + Limit(in.Offset, in.Limit). + ScanAndCount(&items, &total, false) + return +} +``` + +## 注意事项 + +- 仅用于需要同时查询数据和总数量的场景,一般为分页场景。 +- `ScanAndCount` 的第 `3` 个参数 `useFieldForCount` 表示是否在执行 `Count` 操作的时候将 `Fields` 作为 `Count` 参数,一般为 `false` 即可,表示执行 `COUNT(1)` 查询总数量。传递 `true` 表示执行使用查询的字段作为 `COUNT` 方法的参数。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" new file mode 100644 index 00000000000..8a6c064e30c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Scan\346\230\240\345\260\204.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gdb-chaining-query-scan' +title: 'ORM查询-Scan' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM查询,Scan方法,结构体转换,struct数组,gdb,查询结果,结构体对象,Go框架] +description: '在GoFrame框架中使用Scan方法进行ORM查询的技巧,主要包括如何将查询结果转换为struct对象和struct数组。通过示例代码说明了Scan方法的用法,如单条记录转换为struct对象、多条记录转换为struct数组等,帮助用户有效地处理数据库查询结果。' +--- + +`Scan` 方法支持将查询结果转换为结构体或者结构体数组, `Scan` 方法将会根据给定的参数类型自动识别执行的转换类型。 + +## `struct` 对象 + +`Scan` 支持将查询结果转换为一个 `struct` 对象,查询结果应当是特定的一条记录,并且 `pointer` 参数应当为 `struct` 对象的指针地址( `*struct` 或者 `**struct`),使用方式例如: + +```go +type User struct { + Id int + Passport string + Password string + NickName string + CreateTime *gtime.Time +} +user := User{} +g.Model("user").Where("id", 1).Scan(&user) +``` + +或者 + +```go +var user = User{} +g.Model("user").Where("id", 1).Scan(&user) +``` + +前两种方式都是预先初始化对象(提前分配内存),推荐的方式: + +```go +var user *User +g.Model("user").Where("id", 1).Scan(&user) +``` + +这种方式只有在查询到数据的时候才会执行初始化及内存分配。注意在用法上的区别,特别是传递参数类型的差别(前两种方式传递的参数类型是 `*User`,这里传递的参数类型其实是 `**User`)。 + +## `struct` 数组 + +`Scan` 支持将多条查询结果集转换为一个 `[]struct/[]*struct` 数组,查询结果应当是多条记录组成的结果集,并且 `pointer` 应当为数组的指针地址,使用方式例如: + +```go +var users []User +g.Model("user").Scan(&users) +``` + +或者 + +```go +var users []*User +g.Model("user").Scan(&users) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" new file mode 100644 index 00000000000..2d1ca4f7de1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-UnionUnionAll.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/core/gdb-chaining-query-union' +title: 'ORM查询-Union/UnionAll' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,Union,UnionAll,链式操作,方法操作,查询优化,MySQL,SQL] +description: '使用GoFrame框架中的ORM组件进行Union和UnionAll查询操作。使用Union操作符可以删除重复数据,而UnionAll操作符则保留所有数据。通过链式操作或者方法操作可以轻松实现这两种查询方式。文章还介绍了如何在MySQL中进行组合查询,并提供了详细的代码示例。' +--- + +`GoFrame ORM` 组件支持 `Union/UnionAll` 操作, `Union/UnionAll` 操作符用于连接两个以上的 `SELECT` 语句的结果组合到一个结果集合中,关于 `Union/UnionAll` 组合查询的相关介绍可以参考MySQL的官方文档介绍 [https://dev.mysql.com/doc/refman/8.0/en/union.html](https://dev.mysql.com/doc/refman/8.0/en/union.html) 。我们可以通过链式操作或者方法操作来实现 `Union/UnionAll` 操作。 + +## 方法定义 + +```go +// Union does "(SELECT xxx FROM xxx) UNION (SELECT xxx FROM xxx) ..." statement. +func (c *Core) Union(unions ...*Model) *Model + +// UnionAll does "(SELECT xxx FROM xxx) UNION ALL (SELECT xxx FROM xxx) ..." statement. +func (c *Core) UnionAll(unions ...*Model) *Model +``` + +## `Union` + +使用 `Union` 操作符,多个 `SELECT` 语句会删除重复的数据。 + +```go +// 获取默认配置的数据库对象(配置名称为"default") +db := g.DB() + +db.Union( + db.Model("user").Where("id", 1), + db.Model("user").Where("id", 2), + db.Model("user").WhereIn("id", g.Slice{1, 2, 3}), +).OrderDesc("id").All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION +// (SELECT * FROM `user` WHERE `id`=2) +// UNION +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +也可以通过 `dao` 链式操作实现: + +```go +dao.User.Union( + dao.User.Where(dao.User.Columns.Id, 1), + dao.User.Where(dao.User.Columns.Id, 2), + dao.User.WhereIn(dao.User.Columns.Id, g.Slice{1, 2, 3}), +).OrderDesc(dao.User.Columns.Id).All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION +// (SELECT * FROM `user` WHERE `id`=2) +// UNION +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +## `UnionAll` + +使用 `UnionAll` 操作符,多个 `SELECT` 语句不会删除重复的数据。 + +```go +db.UnionAll( + db.Model("user").Where("id", 1), + db.Model("user").Where("id", 2), + db.Model(table).WhereIn("id", g.Slice{1, 2, 3}), +).OrderDesc("id").All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION ALL +// (SELECT * FROM `user` WHERE `id`=2) +// UNION ALL +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` + +也可以通过 `dao` 链式操作实现: + +```go +dao.User.UnionAll( + dao.User.Where(dao.User.Columns.Id, 1), + dao.User.Where(dao.User.Columns.Id, 2), + dao.User.WhereIn(dao.User.Columns.Id, g.Slice{1, 2, 3}), +).OrderDesc(dao.User.Columns.Id).All() +// (SELECT * FROM `user` WHERE `id`=1) +// UNION ALL +// (SELECT * FROM `user` WHERE `id`=2) +// UNION ALL +// (SELECT * FROM `user` WHERE `id` IN (1,2,3) +// ORDER BY `id` DESC) ORDER BY `id` DESC +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" new file mode 100644 index 00000000000..837848e7e58 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-Where\346\235\241\344\273\266.md" @@ -0,0 +1,248 @@ +--- +slug: '/docs/core/gdb-chaining-query-where' +title: 'ORM查询-Where条件' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,ORM,查询,Where条件,条件查询,数据库,条件方法,Go语言,数据处理,GoFrame框架] +description: 'GoFrame框架中ORM组件提供的多种条件查询方法,详细阐述了Where、WhereOr、Wheref等方法的使用方式,及它们如何进行条件组合作用。通过示例展示了如何利用这些方法进行复杂数据库查询,并探讨了使用主键查询的优势。' +--- + +`ORM` 组件提供了一些常用的条件查询方法,并且条件方法支持多种数据类型输入。 + +```go +func (m *Model) Where(where interface{}, args...interface{}) *Model +func (m *Model) Wheref(format string, args ...interface{}) *Model +func (m *Model) WherePri(where interface{}, args ...interface{}) *Model +func (m *Model) WhereBetween(column string, min, max interface{}) *Model +func (m *Model) WhereLike(column string, like interface{}) *Model +func (m *Model) WhereIn(column string, in interface{}) *Model +func (m *Model) WhereNull(columns ...string) *Model +func (m *Model) WhereLT(column string, value interface{}) *Model +func (m *Model) WhereLTE(column string, value interface{}) *Model +func (m *Model) WhereGT(column string, value interface{}) *Model +func (m *Model) WhereGTE(column string, value interface{}) *Model + +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereNotLike(column string, like interface{}) *Model +func (m *Model) WhereNotIn(column string, in interface{}) *Model +func (m *Model) WhereNotNull(columns ...string) *Model + +func (m *Model) WhereOr(where interface{}, args ...interface{}) *Model +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrLike(column string, like interface{}) *Model +func (m *Model) WhereOrIn(column string, in interface{}) *Model +func (m *Model) WhereOrNull(columns ...string) *Model +func (m *Model) WhereOrLT(column string, value interface{}) *Model +func (m *Model) WhereOrLTE(column string, value interface{}) *Model +func (m *Model) WhereOrGT(column string, value interface{}) *Model +func (m *Model) WhereOrGTE(column string, value interface{}) *Model + +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model +func (m *Model) WhereOrNotNull(columns ...string) *Model +``` + +下面我们对其中的几个常用方法做简单介绍,其他条件查询方法用法类似。 + +## `Where/WhereOr` 查询条件 + +### 基本介绍 + +这两个方法用于传递查询条件参数,支持的参数为任意的 `string/map/slice/struct/*struct` 类型。 + +`Where` 条件参数推荐使用字符串的参数传递方式(并使用 `?` 占位符预处理),因为 `map`/ `struct` 类型作为查询参数无法保证顺序性,且在部分情况下(数据库有时会帮助你自动进行查询索引优化),数据库的索引和你传递的查询条件顺序有一定关系。 + +当使用多个 `Where` 方法连接查询条件时,多个条件之间使用 `And` 进行连接。 此外,当存在多个查询条件时, `gdb` 会默认将多个条件分别使用 `()` 符号进行包含,这种设计可以非常友好地支持查询条件分组。 + +使用示例: + +```go +// WHERE `uid`=1 +Where("uid=1") +Where("uid", 1) +Where("uid=?", 1) +Where(g.Map{"uid" : 1}) +// WHERE `uid` <= 1000 AND `age` >= 18 +Where(g.Map{ + "uid <=" : 1000, + "age >=" : 18, +}) + +// WHERE (`uid` <= 1000) AND (`age` >= 18) +Where("uid <=?", 1000).Where("age >=?", 18) + +// WHERE `level`=1 OR `money`>=1000000 +Where("level=? OR money >=?", 1, 1000000) + +// WHERE (`level`=1) OR (`money`>=1000000) +Where("level", 1).WhereOr("money >=", 1000000) + +// WHERE `uid` IN(1,2,3) +Where("uid IN(?)", g.Slice{1,2,3}) +``` + +使用 `struct` 参数的示例,其中 `orm` 的 `tag` 用于指定 `struct` 属性与表字段的映射关系: + +```go +type Condition struct{ + Sex int `orm:"sex"` + Age int `orm:"age"` +} +Where(Condition{1, 18}) +// WHERE `sex`=1 AND `age`=18 +``` + +### 使用示例 + +`Where + string`,条件参数使用字符串和预处理。 + +```go +// 查询多条记录并使用Limit分页 +// SELECT * FROM user WHERE uid>1 LIMIT 0,10 +g.Model("user").Where("uid > ?", 1).Limit(0, 10).All() + +// 使用Fields方法查询指定字段 +// 未使用Fields方法指定查询字段时,默认查询为* +// SELECT uid,name FROM user WHERE uid>1 LIMIT 0,10 +g.Model("user").Fields("uid,name").Where("uid > ?", 1).Limit(0, 10).All() + +// 支持多种Where条件参数类型 +// SELECT * FROM user WHERE uid=1 LIMIT 1 +g.Model("user").Where("uid=1").One() +g.Model("user").Where("uid", 1).One() +g.Model("user").Where("uid=?", 1).One() + +// SELECT * FROM user WHERE (uid=1) AND (name='john') LIMIT 1 +g.Model("user").Where("uid", 1).Where("name", "john").One() +g.Model("user").Where("uid=?", 1).Where("name=?", "john").One() + +// SELECT * FROM user WHERE (uid=1) OR (name='john') LIMIT 1 +g.Model("user").Where("uid=?", 1).WhereOr("name=?", "john").One() +``` + +`Where + slice`,预处理参数可直接通过 `slice` 参数给定。 + +```go +// SELECT * FROM user WHERE age>18 AND name like '%john%' +g.Model("user").Where("age>? AND name like ?", g.Slice{18, "%john%"}).All() + +// SELECT * FROM user WHERE status=1 +g.Model("user").Where("status=?", g.Slice{1}).All() +``` + +`Where + map`,条件参数使用任意 `map` 类型传递。 + +```go +// SELECT * FROM user WHERE uid=1 AND name='john' LIMIT 1 +g.Model("user").Where(g.Map{"uid" : 1, "name" : "john"}).One() + +// SELECT * FROM user WHERE uid=1 AND age>18 LIMIT 1 +g.Model("user").Where(g.Map{"uid" : 1, "age>" : 18}).One() +``` + +`Where + struct/*struct`, `struct` 标签支持 `orm/json`,映射属性到字段名称关系。 + +```go +type User struct { + Id int `json:"uid"` + UserName string `orm:"name"` +} +// SELECT * FROM user WHERE uid =1 AND name='john' LIMIT 1 +g.Model("user").Where(User{ Id : 1, UserName : "john"}).One() + +// SELECT * FROM user WHERE uid =1 LIMIT 1 +g.Model("user").Where(&User{ Id : 1}).One() +``` + +以上的查询条件相对比较简单,我们来看一个比较复杂的查询示例。 + +```go +condition := g.Map{ + "title like ?" : "%九寨%", + "online" : 1, + "hits between ? and ?" : g.Slice{1, 10}, + "exp > 0" : nil, + "category" : g.Slice{100, 200}, +} +// SELECT * FROM article WHERE title like '%九寨%' AND online=1 AND hits between 1 and 10 AND exp > 0 AND category IN(100,200) +g.Model("article").Where(condition).All() +``` + +## `Wheref` 格式化条件字符串 + +在某些场景中,在输入带有字符串的条件语句时,往往需要结合 `fmt.Sprintf` 来格式化条件(当然,注意在字符串中使用占位符代替变量的输入而不是直接将变量格式化),因此我们提供了 `Where+fmt.Sprintf` 结合的便捷方法 `Wheref`。使用示例: + +```go +// WHERE score > 100 and status in('succeeded','completed') +Wheref(`score > ? and status in (?)`, 100, g.Slice{"succeeded", "completed"}) +``` + +## `WherePri` 支持主键的查询条件 + +`WherePri` 方法的功能同 `Where`,但提供了对表主键的智能识别,常用于根据主键的便捷数据查询。假如 `user` 表的主键为 `uid`,我们来看一下 `Where` 与 `WherePri` 的区别: + +```go +// WHERE `uid`=1 +Where("uid", 1) +WherePri(1) + +// WHERE `uid` IN(1,2,3) +Where("uid", g.Slice{1,2,3}) +WherePri(g.Slice{1,2,3}) +``` + +可以看到,当使用 `WherePri` 方法且给定参数为单一的参数基本类型或者 `slice` 类型时,将会被识别为主键的查询条件值。 + +## `WhereBuilder` 复杂条件组合 + +`WhereBuilder` 用以组合生成复杂的 `Where` 条件。 + +### 对象创建 + +我们可以使用 `Model` 的 `Builder` 方法生成 `WhereBuilder` 对象。该方法定义如下: + +```go +// Builder creates and returns a WhereBuilder. +func (m *Model) Builder() *WhereBuilder +``` + +### 使用示例 + +```go +// SELECT * FROM `user` WHERE `id`=1 AND `address`="USA" AND (`status`="active" OR `status`="pending") +m := g.Model("user") +all, err := m.Where("id", 1).Where("address", "USA").Where( + m.Builder().Where("status", "active").WhereOr("status", "pending"), +).All() +``` + +## 注意事项:空数组条件引发的 `0=1` 条件 + +我们来看例子: + +`SQL1`: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{"permitted", "inherited"}).Where("uid", 1).All() +// SELECT * FROM `auth` WHERE (`status` IN('permitted','inherited')) AND (`uid`=1) +``` + +`SQL2`: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{}).Where("uid", 1).All() +// SELECT * FROM `auth` WHERE (0=1) AND (`uid`=1) +``` + +可以看到,当给定的数组条件为空数组时,生成的 `SQL` 出现了 `0=1` 的无效条件,这是为什么呢? + +在开发者没有显示声明可以过滤空数组条件时, `ORM` 不会自动过滤空数组条件,以避免程序逻辑绕过 `SQL` 限制条件,引发不可预知的业务问题。如果开发者确定 `SQL` 限制条件是可以过滤的,那么可以显示调用 `OmitEmpty/OmitEmptyWhere` 方法来执行空条件过滤,如下: + +```go +m := g.Model("auth") +m.Where("status", g.Slice{}).Where("uid", 1).OmitEmpty().All() +// SELECT * FROM `auth` WHERE `uid`=1 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" new file mode 100644 index 00000000000..4110178278e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\210\206\347\273\204\346\216\222\345\272\217\350\277\207\346\273\244.md" @@ -0,0 +1,56 @@ +--- +slug: '/docs/core/gdb-chaining-query-group-order-having' +title: 'ORM查询-分组排序过滤' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,Group,Order,Having,排序,分组查询,条件过滤,数据库查询] +description: '使用GoFrame框架的ORM进行查询操作,包括分组、排序和条件过滤。通过Group方法实现数据分组,通过Order方法进行排序,以及通过Having方法对查询结果进行条件过滤,提供了详细的代码示例和方法说明,帮助用户更好地掌握数据库操作技能。' +--- + +## `Group/Order` 分组与排序 + +`Group` 方法用于查询分组, `Order` 方法用于查询排序。使用示例: + +```go +// SELECT COUNT(*) total,age FROM `user` GROUP BY age +g.Model("user").Fields("COUNT(*) total,age").Group("age").All() + +// SELECT * FROM `student` ORDER BY class asc,course asc,score desc +g.Model("student").Order("class asc,course asc,score desc").All() +``` + +同时, `goframe` 的 `ORM` 提供了一些常用的排序方法: + +```go +// 按照指定字段递增排序 +func (m *Model) OrderAsc(column string) *Model +// 按照指定字段递减排序 +func (m *Model) OrderDesc(column string) *Model +// 随机排序 +func (m *Model) OrderRandom() *Model +``` + +使用示例: + +```go +// SELECT `id`,`title` FROM `article` ORDER BY `created_at` ASC +g.Model("article").Fields("id,title").OrderAsc("created_at").All() + +// SELECT `id`,`title` FROM `article` ORDER BY `views` DESC +g.Model("article").Fields("id,title").OrderDesc("views").All() + +// SELECT `id`,`title` FROM `article` ORDER BY RAND() +g.Model("article").Fields("id,title").OrderRandom().All() +``` + +## `Having` 条件过滤 + +`Having` 方法用于查询结果的条件过滤。使用示例: + +```go +// SELECT COUNT(*) total,age FROM `user` GROUP BY age HAVING total>100 +g.Model("user").Fields("COUNT(*) total,age").Group("age").Having("total>100").All() + +// SELECT * FROM `student` ORDER BY class HAVING score>60 +g.Model("student").Order("class").Having("score>?", 60).All() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..91038e9753b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\255\220\346\237\245\350\257\242\347\211\271\346\200\247.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gdb-chaining-query-sub-query' +title: 'ORM查询-子查询特性' +sidebar_position: 8 +hide_title: true +keywords: [ORM,子查询,Where 子查询,Having 子查询,From 子查询,GoFrame,GoFrame框架,数据库查询,数据模型,子查询语句] +description: 'GoFrame框架中ORM组件支持的三种子查询特性:Where子查询、Having子查询及From子查询。通过示例讲解如何在Where、Having条件以及使用Model方法创建模型时利用子查询提升数据库查询效率。' +--- + +`ORM` 组件目前支持常见的三种语法的子查询: `Where` 子查询、 `Having` 子查询及 `From` 子查询。 + +## `Where` 子查询 + +我们可以在 `Where` 条件中使用子查询语句,示例: + +```go +g.Model("orders").Where("amount > ?", g.Model("orders").Fields("AVG(amount)")).Scan(&orders) +// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders") +``` + +## `Having` 子查询 + +我们可以在 `Having` 条件中使用子查询语句,示例: + +```go +subQuery := g.Model("users").Fields("AVG(age)").WhereLike("name", "name%") +g.Model("users").Fields("AVG(age) as avgage").Group("name").Having("AVG(age) > ?", subQuery).Scan(&results) +// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%") +``` + +## `From` 子查询 + +我们可以在使用 `Model` 方法创建模型时使用子查询语句,示例: + +```go +g.Model("? as u", g.Model("users").Fields("name", "age")).Where("age", 18).Scan(&users) +// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18 + +subQuery1 := g.Model("users").Fields("name") +subQuery2 := g.Model("pets").Fields("name") +g.Model("? as u, ? as p", subQuery1, subQuery2).Scan(&users) +// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..763a9731e05 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\223\215\344\275\234\347\244\272\344\276\213.md" @@ -0,0 +1,280 @@ +--- +slug: '/docs/core/gdb-chaining-query-example' +title: 'ORM查询-常用操作示例' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,ORM查询,数据库操作,链式查询,常用条件,统计方法,字段唯一性,查询示例,GoFrame框架,参数过滤] +description: '使用GoFrame框架进行ORM查询的常用操作示例,包括IN、LIKE、MIN/MAX/AVG/SUM等操作,同时介绍了WhereIn、WhereNotIn、WhereBetween等链式查询方法的使用,通过案例帮助理解不同的查询策略和参数过滤方式。' +--- + +## `in` 查询 + +使用字符串、 `slice` 参数类型。当使用 `slice` 参数类型时,预处理占位符只需要一个 `?` 即可。 + +```go +// SELECT * FROM user WHERE uid IN(100,10000,90000) +g.Model("user").Where("uid IN(?,?,?)", 100, 10000, 90000).All() +g.Model("user").Where("uid", g.Slice{100, 10000, 90000}).All() + +// SELECT * FROM user WHERE gender=1 AND uid IN(100,10000,90000) +g.Model("user").Where("gender=? AND uid IN(?)", 1, g.Slice{100, 10000, 90000}).All() + +// SELECT COUNT(*) FROM user WHERE age in(18,50) +g.Model("user").Where("age IN(?,?)", 18, 50).Count() +g.Model("user").Where("age", g.Slice{18, 50}).Count() +``` + +使用任意 `map` 参数类型。 + +```go +// SELECT * FROM user WHERE gender=1 AND uid IN(100,10000,90000) +g.Model("user").Where(g.Map{ + "gender" : 1, + "uid" : g.Slice{100,10000,90000}, +}).All() +``` + +使用 `struct` 参数类型,注意查询条件的顺序和 `struct` 的属性定义顺序有关。 + +```go +type User struct { + Id []int `orm:"uid"` + Gender int `orm:"gender"` +} +// SELECT * FROM `user` WHERE uid IN(100,10000,90000) AND gender=1 +g.Model("user").Where(User{ + Gender: 1, + Id: []int{100, 10000, 90000}, +}).All() + +``` + +为提高易用性,当传递的 `slice` 参数为空或 `nil` 时,查询并不会报错,而是转换为一个 `false` 条件语句。 + +```go +// SELECT * FROM `user` WHERE 0=1 +g.Model("user").Where("uid", g.Slice{}).All() +// SELECT * FROM `user` WHERE `uid` IS NULL +g.Model("user").Where("uid", nil).All() +``` + +`ORM` 同时也提供了常用条件方法 `WhereIn/WhereNotIn/WhereOrIn/WhereOrNotIn` 方法,用于常用的 `In` 查询条件过滤。方法定义如下: + +```go +func (m *Model) WhereIn(column string, in interface{}) *Model +func (m *Model) WhereNotIn(column string, in interface{}) *Model +func (m *Model) WhereOrIn(column string, in interface{}) *Model +func (m *Model) WhereOrNotIn(column string, in interface{}) *Model +``` + +使用示例: + +```go +// SELECT * FROM `user` WHERE (`gender`=1) AND (`type` IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) AND (`type` NOT IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereNotIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`type` IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereOrIn("type", g.Slice{1,2,3}).All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`type` NOT IN(1,2,3)) +g.Model("user").Where("gender", 1).WhereOrNotIn("type", g.Slice{1,2,3}).All() +``` + +## `like ` 查询 + +```go +// SELECT * FROM `user` WHERE name like '%john%' +g.Model("user").Where("name like ?", "%john%").All() +// SELECT * FROM `user` WHERE birthday like '1990-%' +g.Model("user").Where("birthday like ?", "1990-%").All() + +``` + +从 `goframe v1.16` 版本开始, `goframe` 的 `ORM` 同时也提供了常用条件方法 `WhereLike/WhereNotLike/WhereOrLike/WhereOrNotLike` 方法,用于常用的 `Like` 查询条件过滤。方法定义如下: + +```go +func (m *Model) WhereLike(column string, like interface{}) *Model +func (m *Model) WhereNotLike(column string, like interface{}) *Model +func (m *Model) WhereOrLike(column string, like interface{}) *Model +func (m *Model) WhereOrNotLike(column string, like interface{}) *Model +``` + +使用示例: + +```go +// SELECT * FROM `user` WHERE (`gender`=1) AND (`name` LIKE 'john%') +g.Model("user").Where("gender", 1).WhereLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) AND (`name` NOT LIKE 'john%') +g.Model("user").Where("gender", 1).WhereNotLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`name` LIKE 'john%') +g.Model("user").Where("gender", 1).WhereOrLike("name", "john%").All() + +// SELECT * FROM `user` WHERE (`gender`=1) OR (`name` NOT LIKE 'john%') +g.Model("user").Where("gender", 1).WhereOrNotLike("name", "john%").All() +``` + +## `min/max/avg/sum` + +我们直接将统计方法使用在 `Fields` 方法上,例如: + +```go +// SELECT MIN(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("MIN(score)").Where("uid", 1).Value() + +// SELECT MAX(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("MAX(score)").Where("uid", 1).Value() + +// SELECT AVG(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("AVG(score)").Where("uid", 1).Value() + +// SELECT SUM(score) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Fields("SUM(score)").Where("uid", 1).Value() +``` + +从 `goframe v1.16` 版本开始, `goframe` 的 `ORM` 同时也提供了常用统计方法 `Min/Max/Avg/Sum` 方法,用于常用的字段统计查询。方法定义如下: + +```go +func (m *Model) Min(column string) (float64, error) +func (m *Model) Max(column string) (float64, error) +func (m *Model) Avg(column string) (float64, error) +func (m *Model) Sum(column string) (float64, error) +``` + +上面的示例使用快捷统计方法改造后: + +```go +// SELECT MIN(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Min("score") + +// SELECT MAX(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Max("score") + +// SELECT AVG(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Avg("score") + +// SELECT SUM(`score`) FROM `user` WHERE `uid`=1 LIMIT 1 +g.Model("user").Where("uid", 1).Sum("score") +``` + +## `count` 查询 + +```go +// SELECT COUNT(1) FROM `user` WHERE `birthday`='1990-10-01' +g.Model("user").Where("birthday", "1990-10-01").Count() +// SELECT COUNT(uid) FROM `user` WHERE `birthday`='1990-10-01' +g.Model("user").Fields("uid").Where("birthday", "1990-10-01").Count() + +``` + +从 `goframe v1.16` 版本开始, `goframe` 的 `ORM` 同时也提供了一个按照字段进行 `Count` 的常用方法 `CountColumn`。方法定义如下: + +```go +func (m *Model) CountColumn(column string) (int, error) +``` + +使用示例: + +```go +g.Model("user").Where("birthday", "1990-10-01").CountColumn("uid") +``` + +## `distinct` 查询 + +```go +// SELECT DISTINCT uid,name FROM `user` +g.Model("user").Fields("DISTINCT uid,name").All() +// SELECT COUNT(DISTINCT uid,name) FROM `user` +g.Model("user").Fields("DISTINCT uid,name").Count() + +``` + +从 `goframe v1.16` 版本开始, `goframe` 的 `ORM` 同时也提供了一个字段唯一性过滤标记方法 `Distinct`。方法定义如下: + +```go +func (m *Model) Distinct() *Model +``` + +使用示例: + +```go +// SELECT COUNT(DISTINCT `name`) FROM `user` +g.Model("user").Distinct().CountColumn("name") + +// SELECT COUNT(DISTINCT uid,name) FROM `user` +g.Model("user").Distinct().CountColumn("uid,name") + +// SELECT DISTINCT group,age FROM `user` +g.Model("user").Fields("group, age").Distinct().All() +``` + +## `between` 查询 + +```go +// SELECT * FROM `user` WHERE age between 18 and 20 +g.Model("user").Where("age between ? and ?", 18, 20).All() + +``` + +从 `goframe v1.16` 版本开始, `goframe` 的 `ORM` 同时也提供了常用条件方法 `WhereBetween/WhereNotBetween/WhereOrBetween/WhereOrNotBetween` 方法,用于常用的 `Between` 查询条件过滤。方法定义如下: + +```go +func (m *Model) WhereBetween(column string, min, max interface{}) *Model +func (m *Model) WhereNotBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrBetween(column string, min, max interface{}) *Model +func (m *Model) WhereOrNotBetween(column string, min, max interface{}) *Model +``` + +使用示例: + +```go +// SELECT * FROM `user` WHERE (`gender`=0) AND (`age` BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) AND (`age` NOT BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereNotBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) OR (`age` BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereOrBetween("age", 16, 20).All() + +// SELECT * FROM `user` WHERE (`gender`=0) OR (`age` NOT BETWEEN 16 AND 20) +g.Model("user").Where("gender", 0).WhereOrNotBetween("age", 16, 20).All() +``` + +## `null` 查询 + +`ORM` 提供了常用条件方法 `WhereNull/WhereNotNull/WhereOrNull/WhereOrNotNull` 方法,用于常用的 `Null` 查询条件过滤。方法定义如下: + +```go +func (m *Model) WhereNull(columns ...string) *Model +func (m *Model) WhereNotNull(columns ...string) *Model +func (m *Model) WhereOrNull(columns ...string) *Model +func (m *Model) WhereOrNotNull(columns ...string) *Model +``` + +使用示例: + +```go +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NOT NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNotNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') OR (`inviter` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereOrNull("inviter").All() + +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') OR (`inviter` IS NOT NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereOrNotNull("inviter").All() +``` + +同时,这几个方法的参数支持多个字段输入,例如: + +```go +// SELECT * FROM `user` WHERE (`created_at`>'2021-05-01 00:00:00') AND (`inviter` IS NULL) AND (`creator` IS NULL) +g.Model("user").Where("created_at>?", gtime.New("2021-05-01")).WhereNull("inviter", "creator").All() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..d07cd020ef9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\346\237\245\350\257\242-\345\270\270\347\224\250\346\237\245\350\257\242\346\226\271\346\263\225.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gdb-chaining-query-all-one-array-value-count' +title: 'ORM查询-常用查询方法' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,数据查询,All方法,One方法,Array方法,Value方法,Count方法,CountColumn方法] +description: '使用GoFrame框架中的五个常用数据查询方法:All、One、Array、Value和Count。这些方法允许您轻松地从数据库中获取多条或单条记录,并支持条件参数的直接输入。通过示例代码,您将学习如何在GoFrame中有效地进行数据库操作。' +--- + +## 基本介绍 +数据查询比较常用的几个方法: + +```go +func (m *Model) All(where ...interface{} (Result, error) +func (m *Model) One(where ...interface{}) (Record, error) +func (m *Model) Array(fieldsAndWhere ...interface{}) ([]Value, error) +func (m *Model) Value(fieldsAndWhere ...interface{}) (Value, error) +func (m *Model) Count(where ...interface{}) (int, error) +func (m *Model) CountColumn(column string) (int, error) +``` + +简要说明: + +1. `All` 用于查询并返回多条记录的列表/数组。 +2. `One` 用于查询并返回单条记录。 +3. `Array` 用于查询指定字段列的数据,返回数组。 +4. `Value` 用于查询并返回一个字段值,往往需要结合 `Fields` 方法使用。 +5. `Count` 用于查询并返回记录数。 + +此外,也可以看得到这四个方法定义中也支持条件参数的直接输入,参数类型与 `Where` 方法一致。但需要注意,其中 `Array` 和 `Value` 方法的参数中至少应该输入字段参数。 + +## 使用示例 + +```go +// SELECT * FROM `user` WHERE `score`>60 +Model("user").Where("score>?", 60).All() + +// SELECT * FROM `user` WHERE `score`>60 LIMIT 1 +Model("user").Where("score>?", 60).One() + +// SELECT `name` FROM `user` WHERE `score`>60 +Model("user").Fields("name").Where("score>?", 60).Array() + +// SELECT `name` FROM `user` WHERE `uid`=1 LIMIT 1 +Model("user").Fields("name").Where("uid", 1).Value() + +// SELECT COUNT(1) FROM `user` WHERE `status` IN(1,2,3) +Model("user").Where("status", g.Slice{1,2,3}).Count() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" new file mode 100644 index 00000000000..ba05671bdf2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\225\260\346\215\256\346\237\245\350\257\242.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-chaining-query' +title: 'ORM链式操作-数据查询' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,链式操作,数据查询,gdb,数据库,查询方法,关系映射,开发者指南] +description: '使用GoFrame框架进行数据库操作时的ORM链式数据查询方法。通过链式操作,开发者可以更加方便地构建查询语句,提高查询效率和代码可读性。这些功能为开发者提供了更丰富的查询能力。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" new file mode 100644 index 00000000000..797d28030e0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time' +title: 'ORM链式操作-时间维护' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,链式操作,时间维护,gdb,自动填充,软删除,表现层,数据操作] +description: '使用GoFrame框架中的gdb模块进行ORM链式操作时的时间维护特性。通过自动填充创建、更新和删除时间,有效提高了开发效率。文章详细讲解了如何启用这些特性以及在执行数据库操作如插入、更新和删除时的实现方式。此外,还提供了针对软删除和忽略时间维护等场景的解决方案。' +--- + +## 基本介绍 +:::warning +需要注意,该特性仅对链式操作有效。 +::: +`gdb` 模块支持对数据记录的写入、更新、删除时间自动填充,提高开发维护效率。为了便于时间字段名称、类型的统一维护,如果使用该特性,我们约定: + +- 字段的类型可以为时间类型、数字整型或者布尔型,如: `date`, `datetime`, `timestamp`, `int`, `uint`, `big int`, `bool`等。 +- 字段的名称支持自定义设置,默认名称约定为: + - `created_at` 用于记录创建时更新,仅会写入一次。 + - `updated_at` 用于记录修改时更新,每次记录变更时更新。 + - `deleted_at` 用于记录的软删除特性,只有当记录删除时会写入一次。 +字段名称不区分大小写,也会忽略特殊字符,例如 `CreatedAt`, `UpdatedAt`, `DeletedAt` 也是支持的。 + + + +## 特性配置 + +时间字段名称可以通过配置文件进行自定义修改,并可使用 `timeMaintainDisabled` 配置在数据库实例上完整关闭该特性。 + +在配置文件中对应配置项: + +```yaml +database: + default: # 分组名称,可自定义,默认为default + createdAt: "created_at" # (可选)自动创建时间字段名称 + updatedAt: "updated_time" # (可选)自动更新时间字段名称 + deletedAt: "is_deleted" # (可选)软删除时间字段名称 + timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效 +``` + +:::tip +特别是针对历史项目,本身已经存在不一样的时间字段名称时,可以通过配置项灵活配置时间字段名称。 +::: + +完整的数据库配置请参考 [ORM使用配置](../../ORM使用配置/ORM使用配置.md) 章节。 + +## 特性启用 + +当数据表包含 `created_at`、 `updated_at`、 `deleted_at` 任意一个或多个字段时,或者包含配置文件中对应的配置字段时,该特性自动启用。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" new file mode 100644 index 00000000000..e269d95e304 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-SoftTimeOption.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-option' +title: '时间维护-SoftTimeOption' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,时间维护,SoftTimeOption,数据库操作,时间粒度,毫秒级时间戳,MySQL表结构,ORM操作,时间字段,大整型存储] +description: '介绍如何在GoFrame框架中使用SoftTimeOption控制时间写入粒度,实现秒级到毫秒级的时间戳转换,并提供相关MySQL表结构和示例代码,帮助开发者灵活配置时间字段,支持多种时间粒度选项以满足不同项目需求,并通过ORM方法插入数据' +--- + + +在前面的[整型字段](./时间维护-整型字段.md)的示例中,时间字段写入的都是秒级时间戳,但如果我们想要控制时间写入的粒度,写入毫秒级时间戳怎么做呢? +我们可以使用`SoftTimeOption`来控制写入的时间数值粒度。 + +## 示例SQL +这是后续示例代码中用到的`MySQL`表结构。由于需要写入比秒级更细粒度的数值,因此字段类型使用`big int`来存储。 + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` bigint unsigned DEFAULT NULL, + `updated_at` bigint unsigned DEFAULT NULL, + `deleted_at` bigint unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +如果您尝试测试查看`ORM`操作执行的`SQL`语句,建议您打开`debug`模式(相关文档:[调试模式](../../ORM高级特性/ORM高级特性-调试模式.md)),`SQL`语句将会自动打印到日志输出。 +::: + +## `created_at` 写入时间 + +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731484186556,1731484186556,0) +g.Model("user").SoftTime(gdb.SoftTimeOption{ + SoftTimeType: gdb.SoftTimeTypeTimestampMilli, +}).Data(g.Map{"name": "john"}).Insert() +``` + +其中`SoftTimeType`控制时间粒度,粒度选项如下: +```go +const ( + SoftTimeTypeAuto SoftTimeType = 0 // (Default)Auto detect the field type by table field type. + SoftTimeTypeTime SoftTimeType = 1 // Using datetime as the field value. + SoftTimeTypeTimestamp SoftTimeType = 2 // In unix seconds. + SoftTimeTypeTimestampMilli SoftTimeType = 3 // In unix milliseconds. + SoftTimeTypeTimestampMicro SoftTimeType = 4 // In unix microseconds. + SoftTimeTypeTimestampNano SoftTimeType = 5 // In unix nanoseconds. +) +``` diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..07df3fcec0b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,112 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-example' +title: '时间维护-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,时间维护,软删除,created_at,updated_at,deleted_at,数据库操作,联表查询,Unscoped,时间字段] +description: '本文介绍了使用GoFrame框架管理数据库时间字段的基本方法,包括created_at、updated_at和deleted_at字段的写入和更新机制,以及软删除特性对查询和更新操作的影响,同时展示了联表查询和忽略时间特性Unscoped的方法。通过这些示例,可以有效管理数据的软删除和时间戳,确保数据库记录的准确性。' +--- + + +以下的示例中,我们默认示例中的数据表均包含了`created_at`、 `updated_at`、 `deleted_at`这3个字段,并且字段类型为`datetime`。 + +## 示例SQL +这是后续示例代码中用到的`MySQL`表结构。 +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` datetime DEFAULT NULL, + `updated_at` datetime DEFAULT NULL, + `deleted_at` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `user_detail` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `address` varchar(45) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +- 如果您选择使用时间字段类型,那么您需要将该字段设置为允许为`NULL`,这样软删除才能起作用。 +- 如果您尝试测试查看`ORM`操作执行的`SQL`语句,建议您打开`debug`模式(相关文档:[调试模式](../../ORM高级特性/ORM高级特性-调试模式.md)),`SQL`语句将会自动打印到日志输出。 +::: + +## `created_at` 写入时间 + +在执行 `Insert/InsertIgnore/BatchInsert/BatchInsertIgnore` 方法时自动写入该时间,随后的更新/删除操作不会引起`created_at`字段内容的改变。 +:::warning +需要注意的是 `Replace` 方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。 +::: +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`) VALUES('john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10000,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`) VALUES(10001,'john', `2020-06-06 21:00:00`, `2020-06-06 21:00:00`) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`) +g.Model("user").Data(g.Map{"id": 10001, "name": "john"}).Save() +``` + +## `deleted_at` 数据软删除 + +当软删除存在时(即`deleted_at`字段存在时),所有的查询语句都将会自动加上 `deleted_at` 的条件。 + +```go +// UPDATE `user` SET `deleted_at`='2020-06-06 21:00:00' WHERE id=10 AND `deleted_at` IS NULL +g.Model("user").Where("id", 10).Delete() +``` + +查询的时候会发生一些变化,例如: + +```go +// SELECT * FROM `user` WHERE id>1 AND `deleted_at` IS NULL +g.Model("user").Where("id>?", 1).All() +``` + +可以看到当数据表中存在 `deleted_at` 字段时,所有涉及到该表的查询操作都将自动加上 `deleted_at IS NULL` 的条件。 + +## `updated_at` 更新时间 + +在执行 `Save/Update` 方法时自动写入该时间。 +:::warning +需要注意的是 `Replace` 方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。 +::: +```go +// UPDATE `user` SET `name`='john guo',`updated_at`='2020-06-06 21:00:00' WHERE name='john' AND `deleted_at` IS NULL +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() + +// UPDATE `user` SET `status`=1,`updated_at`='2020-06-06 21:00:00' WHERE `deleted_at` IS NULL ORDER BY `id` ASC LIMIT 10 +g.Model("user").Data("status", 1).OrderAsc("id").Limit(10).Update() + +// INSERT INTO `user`(`id`,`name`,`update_at`) VALUES(1,'john guo','2020-12-29 20:16:14') ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`update_at`=VALUES(`update_at`) +g.Model("user").Data(g.Map{"id": 1, "name": "john guo"}).Save() +``` + +## 联表查询的场景 + +如果关联查询的几个表都启用了软删除特性时,会发生以下这种情况,即条件语句中会增加所有相关表的软删除时间判断。 + +```go +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE (`u`.`id`=10) AND `u`.`deleted_at` IS NULL LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).One() +``` + +## `Unscoped` 忽略时间特性 + +`Unscoped` 用于在链式操作中忽略自动时间更新特性,例如上面的示例,加上 `Unscoped` 方法后: + +```go +// SELECT * FROM `user` WHERE id>1 +g.Model("user").Unscoped().Where("id>?", 1).All() + +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE u.id=10 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).Unscoped().One() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" new file mode 100644 index 00000000000..18bc59e5957 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\345\270\203\345\260\224\345\255\227\346\256\265.md" @@ -0,0 +1,59 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-bool-fields' +title: '时间维护-布尔字段' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,时间字段,布尔字段,ORM组件,自动识别,MySQL表结构,软删除,deleted_at,debug模式] +description: '介绍GoFrame框架中时间字段为布尔字段的支持,通过示例展示如何使用布尔类型的deleted_at字段进行数据软删除。提供MySQL表结构定义以及在GoFrame中使用ORM组件进行创建记录和软删除操作的示例。' +--- + + +从`v2.8`版本开始, +如果时间字段`created_at`、 `updated_at`、 `deleted_at`为布尔字段,ORM组件会自动识别支持,并写入布尔类型数值(写入数值通过`0`和`1`表示)。 +通常布尔字段为`deleted_at`字段,我们这里只演示`deleted_at`字段类型为`bool`的情况。 + +## 示例SQL +这是后续示例代码中用到的`MySQL`表结构。 + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` int(10) unsigned DEFAULT NULL, + `updated_at` int(10) unsigned DEFAULT NULL, + `deleted_at` bit(1) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +我们建议使用`bit(1)`来表示字段的`bool`类型,而非`tinyint(1)`或者`int(1)`。因为`tinyint(1)/int(1)`字段类型表示的范围是`-127~127`,通常可能会被用作状态字段类型。而`bit(1)`的类型范围为`0/1`,可以很好的表示`bool`类型的两个值`false/true`。 + +:::tip +如果您尝试测试查看`ORM`操作执行的`SQL`语句,建议您打开`debug`模式(相关文档:[调试模式](../../ORM高级特性/ORM高级特性-调试模式.md)),`SQL`语句将会自动打印到日志输出。 +::: + +## `created_at` 写入时间 + +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731481488,1731481488,0) +g.Model("user").Data(g.Map{"name": "john"}).Insert() +``` + +## `deleted_at` 数据软删除 + +```go +// UPDATE `user` SET `deleted_at`=1 WHERE (`id`=10) AND `deleted_at`=0 +g.Model("user").Where("id", 10).Delete() +``` + +查询的时候会发生一些变化,例如: + +```go +// SELECT * FROM `user` WHERE (id>1) AND `deleted_at`=0 +g.Model("user").Where("id>?", 1).All() +``` + + + + diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" new file mode 100644 index 00000000000..f0dfe522a19 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\227\266\351\227\264\347\273\264\346\212\244/\346\227\266\351\227\264\347\273\264\346\212\244-\346\225\264\345\236\213\345\255\227\346\256\265.md" @@ -0,0 +1,106 @@ +--- +slug: '/docs/core/gdb-chaining-soft-time-numeric-fields' +title: '时间维护-整型字段' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,时间维护,整型字段,created_at,updated_at,deleted_at,软删除,ORM组件,Insert,Update] +description: '如果时间字段如created_at、updated_at、deleted_at为整型字段,GoFrame框架的ORM组件会自动识别并写入秒级时间戳。插入操作时created_at自动更新,但更新和删除不改变created_at。使用Replace方法会更新所有时间字段。在软删除情况下,所有查询自动包含deleted_at=0条件。' +--- + + +从`v2.8`版本开始,如果时间字段`created_at`、 `updated_at`、 `deleted_at`为整型字段,ORM组件会自动识别支持,并写入**秒级的时间戳**数值。 + +## 示例SQL +这是后续示例代码中用到的`MySQL`表结构。 + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(45) NOT NULL, + `status` tinyint DEFAULT 0, + `created_at` int(10) unsigned DEFAULT NULL, + `updated_at` int(10) unsigned DEFAULT NULL, + `deleted_at` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `user_detail` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `address` varchar(45) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +:::tip +如果您尝试测试查看`ORM`操作执行的`SQL`语句,建议您打开`debug`模式(相关文档:[调试模式](../../ORM高级特性/ORM高级特性-调试模式.md)),`SQL`语句将会自动打印到日志输出。 +::: + +## `created_at` 写入时间 + +在执行 `Insert/InsertIgnore/BatchInsert/BatchInsertIgnore` 方法时自动写入该时间,随后的更新/删除操作不会引起`created_at`字段内容的改变。 +:::warning +需要注意的是 `Replace` 方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。 +::: +```go +// INSERT INTO `user`(`name`,`created_at`,`updated_at`,`deleted_at`) VALUES('john',1731481488,1731481488,0) +g.Model("user").Data(g.Map{"name": "john"}).Insert() + +// INSERT IGNORE INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10000,'john',1731481518,1731481518,0) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).InsertIgnore() + +// REPLACE INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10000,'john',1731481747,1731481747,0) +g.Model("user").Data(g.Map{"id": 10000, "name": "john"}).Replace() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(10001,'john',1731481766,1731481766,0) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`),`deleted_at`=VALUES(`deleted_at`) +g.Model("user").Data(g.Map{"id": 10001, "name": "john"}).Save() +``` + +## `deleted_at` 数据软删除 + +当软删除存在时(即`deleted_at`字段存在时),所有的查询语句都将会自动加上 `deleted_at` 的条件。 + +```go +// UPDATE `user` SET `deleted_at`=1731481948 WHERE (`id`=10) AND `deleted_at`=0 +g.Model("user").Where("id", 10).Delete() +``` + +查询的时候会发生一些变化,例如: + +```go +// SELECT * FROM `user` WHERE (id>1) AND `deleted_at`=0 +g.Model("user").Where("id>?", 1).All() +``` + +可以看到当数据表中存在 `deleted_at` 字段时,所有涉及到该表的查询操作都将自动加上 `deleted_at=0` 的条件。 + + +## `updated_at` 更新时间 + +在执行 `Save/Update` 方法时自动写入该时间。需要注意的是 `Replace` 方法也会更新该字段,因为该操作相当于删除已存在的旧数据并重新写一条数据。 +:::info +如果同时存在`deleted_at`软删除字段,那么更新操作语句中同样会出现`deleted_at` 的条件。 +::: +```go +// UPDATE `user` SET `name`='john guo',`updated_at`=1731481821 WHERE (`name`='john') AND `deleted_at`=0 +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() + +// UPDATE `user` SET `status`=1,`updated_at`=1731481895 WHERE `deleted_at`=0 ORDER BY `id` ASC LIMIT 10 +g.Model("user").Data("status", 1).OrderAsc("id").Limit(10).Update() + +// INSERT INTO `user`(`id`,`name`,`created_at`,`updated_at`,`deleted_at`) VALUES(1,'john guo',1731481915,1731481915,0) ON DUPLICATE KEY UPDATE `id`=VALUES(`id`),`name`=VALUES(`name`),`updated_at`=VALUES(`updated_at`),`deleted_at`=VALUES(`deleted_at`) +g.Model("user").Data(g.Map{"id": 1, "name": "john guo"}).Save() +``` + + +## 联表查询的场景 + +如果关联查询的几个表都启用了软删除特性时,会发生以下这种情况,即条件语句中会增加所有相关表的软删除时间判断。 + +```go +// SELECT * FROM `user` AS `u` LEFT JOIN `user_detail` AS `ud` ON (ud.id=u.id) WHERE (`u`.`id`=10) AND `u`.`deleted_at`=0 LIMIT 1 +g.Model("user", "u").LeftJoin("user_detail", "ud", "ud.id=u.id").Where("u.id", 10).One() +``` + +## 控制写入时间粒度 + +本章节的时间字段数值写入默认是秒级时间戳,但如果我们想要控制时间写入的粒度,写入毫秒级时间戳怎么做呢?我们可以使用[SoftTimeOption](./时间维护-SoftTimeOption.md)。 diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" new file mode 100644 index 00000000000..ab1413717b3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\233\264\346\226\260\345\210\240\351\231\244.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/core/gdb-chaining-update-delete' +title: 'ORM链式操作-更新删除' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM链式操作,数据更新,数据删除,Counter特性,自增操作,自减操作,RawSQL,软删除] +description: '在GoFrame框架中进行ORM链式操作的数据更新和删除。详细阐述了使用Update和Delete方法时需要结合Where条件的重要性。此外,还探讨了通过Counter参数实现字段数值增减的特性,以及使用Increment和Decrement方法对字段进行自增自减的操作。同时,还讲解了嵌入原生SQL语句的技巧和实现软删除的方式,以确保数据处理的灵活性和安全性。' +--- +:::warning +为安全性保证、防止误操作, `Update` 及 `Delete` 方法必须带有 `Where` 条件才能提交执行,否则将会错误返回,错误信息如: `there should be WHERE condition statement for XXX operation`。 `goframe` 是一款用于企业生产级别的框架,各个模块设计严谨,工程实践的细节处理得比较好。 +::: +## `Update` 更新方法 + +`Update` 用于数据的更新,往往需要结合 `Data` 及 `Where` 方法共同使用。 `Data` 方法用于指定需要更新的数据, `Where` 方法用于指定更新的条件范围。同时, `Update` 方法也支持直接给定数据和条件参数。 + +使用示例: + +```go +// UPDATE `user` SET `name`='john guo' WHERE name='john' +g.Model("user").Data(g.Map{"name" : "john guo"}).Where("name", "john").Update() +g.Model("user").Data("name='john guo'").Where("name", "john").Update() + +// UPDATE `user` SET `status`=1 WHERE `status`=0 ORDER BY `login_time` asc LIMIT 10 +g.Model("user").Data("status", 1).Order("login_time asc").Where("status", 0).Limit(10).Update() + +// UPDATE `user` SET `status`=1 WHERE 1 +g.Model("user").Data("status=1").Where(1).Update() +g.Model("user").Data("status", 1).Where(1).Update() +g.Model("user").Data(g.Map{"status" : 1}).Where(1).Update() +``` + +也可以直接给 `Update` 方法传递 `data` 及 `where` 参数: + +```go +// UPDATE `user` SET `name`='john guo' WHERE name='john' +g.Model("user").Update(g.Map{"name" : "john guo"}, "name", "john") +g.Model("user").Update("name='john guo'", "name", "john") + +// UPDATE `user` SET `status`=1 WHERE 1 +g.Model("user").Update("status=1", 1) +g.Model("user").Update(g.Map{"status" : 1}, 1) +``` + +## `Counter` 更新特性 + +可以使用 `Counter` 类型参数对特定的字段进行数值操作,例如:增加、减少操作。 + +`Counter` 数据结构定义: + +```go +// Counter is the type for update count. +type Counter struct { + Field string + Value float64 +} +``` + +`Counter` 使用示例,字段自增: + +```go +updateData := g.Map{ + "views": &gdb.Counter{ + Field: "views", + Value: 1, + }, +} +// UPDATE `article` SET `views`=`views`+1 WHERE `id`=1 +result, err := db.Update("article", updateData, "id", 1) +``` + +`Counter` 也可以实现非自身字段的自增,例如: + +```go +updateData := g.Map{ + "views": &gdb.Counter{ + Field: "clicks", + Value: 1, + }, +} +// UPDATE `article` SET `views`=`clicks`+1 WHERE `id`=1 +result, err := db.Update("article", updateData, "id", 1) +``` + +## `Increment/Decrement` 自增/减 + +我们可以通过 `Increment` 和 `Decrement` 方法实现对指定字段的自增/自减常用操作。两个方法的定义如下: + +```go +// Increment increments a column's value by a given amount. +func (m *Model) Increment(column string, amount float64) (sql.Result, error) + +// Decrement decrements a column's value by a given amount. +func (m *Model) Decrement(column string, amount float64) (sql.Result, error) +``` + +使用示例: + +```go +// UPDATE `article` SET `views`=`views`+10000 WHERE `id`=1 +g.Model("article").Where("id", 1).Increment("views", 10000) +// UPDATE `article` SET `views`=`views`-10000 WHERE `id`=1 +g.Model("article").Where("id", 1).Decrement("views", 10000) +``` + +## `RawSQL` 语句嵌入 + +`gdb.Raw` 是字符串类型,该类型的参数将会直接作为 `SQL` 片段嵌入到提交到底层的 `SQL` 语句中,不会被自动转换为字符串参数类型、也不会被当做预处理参数。更详细的介绍请参考章节: [ORM高级特性-RawSQL](../ORM高级特性/ORM高级特性-RawSQL.md)。例如: + +```go +// UPDATE `user` SET login_count='login_count+1',update_time='now()' WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": "login_count+1", + "update_time": "now()", +}).Where("id", 1).Update() +// 执行报错:Error Code: 1136. Column count doesn't match value count at row 1 +``` + +使用 `gdb.Raw` 改造后: + +```go +// UPDATE `user` SET login_count=login_count+1,update_time=now() WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": gdb.Raw("login_count+1"), + "update_time": gdb.Raw("now()"), +}).Where("id", 1).Update() +``` + +## `Delete` 删除方法 + +`Delete` 方法用于数据的删除。 + +使用示例: + +```go +// DELETE FROM `user` WHERE uid=10 +g.Model("user").Where("uid", 10).Delete() +// DELETE FROM `user` ORDER BY `login_time` asc LIMIT 10 +g.Model("user").Order("login_time asc").Limit(10).Delete() +``` + +也可以直接给 `Delete` 方法传递 `where` 参数: + +```go +// DELETE FROM `user` WHERE `uid`=10 +g.Model("user").Delete("uid", 10) +// DELETE FROM `user` WHERE `score`<60 +g.Model("user").Delete("score < ", 60) +``` + +## 软删除特性 + +软删除特性请查看章节: [ORM链式操作-时间维护](ORM链式操作-时间维护/ORM链式操作-时间维护.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..3ea6ecf5998 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\237\245\350\257\242\347\274\223\345\255\230.md" @@ -0,0 +1,165 @@ +--- +slug: '/docs/core/gdb-chaining-query-cache' +title: 'ORM链式操作-查询缓存' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,ORM,查询缓存,链式操作,缓存管理,Redis,数据库,缓存清理,缓存适配,数据表结构] +description: '使用GoFrame框架中的ORM进行查询缓存操作。它支持对查询结果进行缓存优化,适用于多读少写场景。文中详细介绍了缓存管理和适配,特别是如何通过Redis实现分布式缓存。还提供了示例代码展示数据表结构及其缓存效果,演示了查询缓存的实现与缓存清理功能。' +--- + +## 查询缓存 + +`gdb` 支持对查询结果的缓存处理,常用于多读少写的查询缓存场景,并支持手动的缓存清理。需要注意的是,查询缓存仅支持链式操作,且在事务操作下不可用。 + +相关方法: + +```go +type CacheOption struct { + // Duration is the TTL for the cache. + // If the parameter `Duration` < 0, which means it clear the cache with given `Name`. + // If the parameter `Duration` = 0, which means it never expires. + // If the parameter `Duration` > 0, which means it expires after `Duration`. + Duration time.Duration + + // Name is an optional unique name for the cache. + // The Name is used to bind a name to the cache, which means you can later control the cache + // like changing the `duration` or clearing the cache with specified Name. + Name string + + // Force caches the query result whatever the result is nil or not. + // It is used to avoid Cache Penetration. + Force bool +} + +// Cache sets the cache feature for the model. It caches the result of the sql, which means +// if there's another same sql request, it just reads and returns the result from cache, it +// but not committed and executed into the database. +// +// Note that, the cache feature is disabled if the model is performing select statement +// on a transaction. +func (m *Model) Cache(option CacheOption) *Model +``` + +## 缓存管理 + +### 缓存对象 + +`ORM` 对象默认情况下提供了缓存管理对象,该缓存对象类型为 `*gcache.Cache`,也就是说同时也支持 `*gcache.Cache` 的所有特性。可以通过 `GetCache() *gcache.Cache` 接口方法获得该缓存对象,并通过返回的对象实现自定义的各种缓存操作,例如: `g.DB().GetCache().Keys()`。 + +### 缓存适配( `Redis` 缓存) + +默认情况下 `ORM` 的 `*gcache.Cache` 缓存对象提供的是单进程内存缓存,虽然性能非常高效,但是只能在单进程内使用。如果服务如果采用多节点部署,多节点之间的缓存可能会产生数据不一致的情况,因此大多数场景下我们都是通过 `Redis` 服务器来实现对数据库查询数据的缓存。 `*gcache.Cache` 对象采用了适配器设计模式,可以轻松实现从单进程内存缓存切换为分布式的 `Redis` 缓存。使用示例: + +```go +redisCache := gcache.NewAdapterRedis(g.Redis()) +g.DB().GetCache().SetAdapter(redisCache) +``` + +更多介绍请参考: [缓存管理-Redis缓存](../../缓存管理/缓存管理-Redis缓存.md) + +### 管理方法 + +为简化数据库的查询缓存管理,从 `v2.2.0` 版本开始,提供了两个缓存管理方法: + +```go +// ClearCache removes cached sql result of certain table. +func (c *Core) ClearCache(ctx context.Context, table string) (err error) + +// ClearCacheAll removes all cached sql result from cache +func (c *Core) ClearCacheAll(ctx context.Context) (err error) +``` + +方法介绍如注释。可以看到这两个方法是挂载 `Core` 对象上的,而底层的 `Core` 对象已经通过 `DB` 接口暴露,因此我们这么来获取 `Core` 对象: + +```go +g.DB().GetCore() +``` + +## 使用示例 + +### 数据表结构 + +```sql +CREATE TABLE `user` ( + `uid` int(10) unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(30) NOT NULL DEFAULT '' COMMENT '昵称', + `site` varchar(255) NOT NULL DEFAULT '' COMMENT '主页', + PRIMARY KEY (`uid`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +### 示例代码 + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + db = g.DB() + ctx = gctx.New() + ) + + // 开启调试模式,以便于记录所有执行的SQL + db.SetDebug(true) + + // 写入测试数据 + _, err := g.Model("user").Ctx(ctx).Data(g.Map{ + "name": "john", + "site": "https://goframe.org", + }).Insert() + + // 执行2次查询并将查询结果缓存1小时,并可执行缓存名称(可选) + for i := 0; i < 2; i++ { + r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "vip-user", + Force: false, + }).Where("uid", 1).One() + g.Log().Debug(ctx, r.Map()) + } + + // 执行更新操作,并清理指定名称的查询缓存 + _, err = g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: -1, + Name: "vip-user", + Force: false, + }).Data(gdb.Map{"name": "smith"}).Where("uid", 1).Update() + if err != nil { + g.Log().Fatal(ctx, err) + } + + // 再次执行查询,启用查询缓存特性 + r, _ := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "vip-user", + Force: false, + }).Where("uid", 1).One() + g.Log().Debug(ctx, r.Map()) +} +``` + +执行后输出结果为(测试表数据结构仅供示例参考): + +```html +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"john","site":"https://goframe.org","uid":1} +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"john","site":"https://goframe.org","uid":1} +2022-02-08 17:36:19.817 [DEBU] {c0424c75f1c5d116d0df0f7197379412} [ 0 ms] [default] [rows:1 ] UPDATE `user` SET `name`='smith' WHERE `uid`=1 +2022-02-08 17:36:19.818 [DEBU] {c0424c75f1c5d116d0df0f7197379412} [ 1 ms] [default] [rows:1 ] SELECT * FROM `user` WHERE `uid`=1 LIMIT 1 +2022-02-08 17:36:19.818 [DEBU] {c0424c75f1c5d116d0df0f7197379412} {"name":"smith","site":"https://goframe.org","uid":1} +``` + +可以看到: + +1. 为了方便展示缓存效果,这里开启了数据 `debug` 特性,当有任何的SQL操作时将会输出到终端。 +2. 执行两次 `One` 方法数据查询,第一次走了SQL查询,第二次直接使用到了缓存,SQL没有提交到数据库执行,因此这里只打印了一条查询SQL,并且两次查询的结果也是一致的。 +3. 注意这里为该查询的缓存设置了一个自定义的名称 `vip-user`,以便于后续清空更新缓存。如果缓存不需要清理,那么可以不用设置缓存名称。 +4. 当执行 `Update` 更新操作时,同时根据名称清空指定的缓存。 +5. 随后再执行 `One` 方法数据查询,这时重新缓存新的数据。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" new file mode 100644 index 00000000000..ff392387e1d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-chaining-relation' +title: 'ORM链式操作-模型关联' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,链式操作,模型关联,gdb,数据库,数据处理,开发框架,文档] +description: '在GoFrame框架中使用ORM链式操作来实现模型关联。通过详细的示例和解析,帮助开发者理解和应用GoFrame中的数据库处理能力,从而提高开发效率,实现复杂数据关系的轻松管理。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" new file mode 100644 index 00000000000..19c93e37cab --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\345\212\250\346\200\201\345\205\263\350\201\224-ScanList.md" @@ -0,0 +1,237 @@ +--- +slug: '/docs/core/gdb-chaining-relation-scan-list' +title: '模型关联-动态关联-ScanList' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,动态关联,ScanList,数据写入,数据查询,数据模型,事务处理,绑定数据,关联关系,数据结构] +description: 'GoFrame框架中如何处理模型关联的设计,通过简化标签内容和关联属性,降低心智负担。详细讲解了如何使用ScanList方法进行数据结构的动态关联,展示了用户、用户详情和用户学分之间的1:1和1:N关系,并提供了Go语言的示例代码进行数据查询和写入操作,包括事务处理和数据绑定的实现方式。' +--- + +`gf` 的 `ORM` 没有采用其他 `ORM` 常见的 `BelongsTo`, `HasOne`, `HasMany`, `ManyToMany` 这样的模型关联设计,这样的关联关系维护较繁琐,例如外键约束、额外的标签备注等,对开发者有一定的心智负担。因此 `gf` 框架不倾向于通过向模型结构体中注入过多复杂的标签内容、关联属性或方法,并一如既往地尝试着简化设计,目标是使得模型关联查询尽可能得易于理解、使用便捷。 +:::warning +接下来关于 `gf ORM` 提供的模型关联实现,从 `GoFrame v1.13.6` 版本开始提供,目前属于实验性特性。 +::: +那么我们就使用一个例子来介绍 `gf ORM` 提供的模型关联吧。 + +### 数据结构 + +为简化示例,我们这里设计得表都尽可能简单,每张表仅包含3-4个字段,方便阐述关联关系即可。 + +``` +# 用户表 +CREATE TABLE `user` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# 用户详情 +CREATE TABLE `user_detail` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# 用户学分 +CREATE TABLE `user_scores` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + course varchar(45) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +``` + +### 数据模型 + +根据表定义,我们可以得知: + +1. 用户表与用户详情是 `1:1` 关系。 +2. 用户表与用户学分是 `1:N` 关系。 +3. 这里并没有演示 `N:N` 的关系,因为相比较于 `1:N` 的查询只是多了一次关联、或者一次查询,最终处理方式和 `1:N` 类似。 + +那么 `Golang` 的模型可定义如下: + +```go +// 用户表 +type EntityUser struct { + Uid int `orm:"uid"` + Name string `orm:"name"` +} +// 用户详情 +type EntityUserDetail struct { + Uid int `orm:"uid"` + Address string `orm:"address"` +} +// 用户学分 +type EntityUserScores struct { + Id int `orm:"id"` + Uid int `orm:"uid"` + Score int `orm:"score"` + Course string `orm:"course"` +} +// 组合模型,用户信息 +type Entity struct { + User *EntityUser + UserDetail *EntityUserDetail + UserScores []*EntityUserScores +} +``` + +其中, `EntityUser`, `EntityUserDetail`, `EntityUserScores` 分别对应的是用户表、用户详情、用户学分数据表的数据模型。 `Entity` 是一个组合模型,对应的是一个用户的所有详细信息。 + +### 数据写入 + +写入数据时涉及到简单的数据库事务即可。 + +```go + err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + r, err := tx.Model("user").Save(EntityUser{ + Name: "john", + }) + if err != nil { + return err + } + uid, err := r.LastInsertId() + if err != nil { + return err + } + _, err = tx.Model("user_detail").Save(EntityUserDetail{ + Uid: int(uid), + Address: "Beijing DongZhiMen #66", + }) + if err != nil { + return err + } + _, err = tx.Model("user_scores").Save(g.Slice{ + EntityUserScores{Uid: int(uid), Score: 100, Course: "math"}, + EntityUserScores{Uid: int(uid), Score: 99, Course: "physics"}, + }) + return err + }) +``` + +### 数据查询 + +#### 单条数据记录 + +查询单条模型数据比较简单,直接使用 `Scan` 方法即可,该方法会自动识别绑定查询结果到单个对象属性还是数组对象属性中。例如: + +```go +// 定义用户列表 +var user Entity +// 查询用户基础数据 +// SELECT * FROM `user` WHERE `name`='john' +err := g.Model("user").Scan(&user.User, "name", "john") +if err != nil { + return err +} +// 查询用户详情数据 +// SELECT * FROM `user_detail` WHERE `uid`=1 +err := g.Model("user_detail").Scan(&user.UserDetail, "uid", user.User.Uid) +// 查询用户学分数据 +// SELECT * FROM `user_scores` WHERE `uid`=1 +err := g.Model("user_scores").Scan(&user.UserScores, "uid", user.User.Uid) +``` + +该方法在之前的章节中已经有介绍,因此这里不再赘述。 + +#### 多条数据记录 + +查询多条数据记录并绑定数据到数据模型数组中,需要使用到 `ScanList` 方法,该方法会需要用户指定结果字段与模型属性的关系,随后底层会遍历数组并自动执行数据绑定。例如: + +```go +// 定义用户列表 +var users []Entity +// 查询用户基础数据 +// SELECT * FROM `user` +err := g.Model("user").ScanList(&users, "User") +// 查询用户详情数据 +// SELECT * FROM `user_detail` WHERE `uid` IN(1,2) +err := g.Model("user_detail"). + Where("uid", gdb.ListItemValuesUnique(users, "User", "Uid")). + ScanList(&users, "UserDetail", "User", "uid:Uid") +// 查询用户学分数据 +// SELECT * FROM `user_scores` WHERE `uid` IN(1,2) +err := g.Model("user_scores"). + Where("uid", gdb.ListItemValuesUnique(users, "User", "Uid")). + ScanList(&users, "UserScores", "User", "uid:Uid") +``` + +这其中涉及到两个比较重要的方法: + +**1\. `ScanList`** + +方法定义: + +```go +// ScanList converts to struct slice which contains other complex struct attributes. +// Note that the parameter should be type of *[]struct/*[]*struct. +// Usage example: +// +// type Entity struct { +// User *EntityUser +// UserDetail *EntityUserDetail +// UserScores []*EntityUserScores +// } +// var users []*Entity +// or +// var users []Entity +// +// ScanList(&users, "User") +// ScanList(&users, "UserDetail", "User", "uid:Uid") +// ScanList(&users, "UserScores", "User", "uid:Uid") +// The parameters "User"/"UserDetail"/"UserScores" in the example codes specify the target attribute struct +// that current result will be bound to. +// The "uid" in the example codes is the table field name of the result, and the "Uid" is the relational +// struct attribute name. It automatically calculates the HasOne/HasMany relationship with given +// parameter. +// See the example or unit testing cases for clear understanding for this function. +func (m *Model) ScanList(listPointer interface{}, attributeName string, relation ...string) (err error) +``` + +该方法用于将查询到的数组数据绑定到指定的列表上,例如: + +- `ScanList(&users, "User")` + +表示将查询到的用户信息数组数据绑定到 `users` 列表中每一项的 `User` 属性上。 + +- `ScanList(&users, "UserDetail", "User", "uid:Uid")` + +表示将查询到用户详情数组数据绑定到 `users` 列表中每一项的 `UserDetail` 属性上,并且和另一个 `User` 对象属性通过 `uid:Uid` 的 `字段:属性` 关联,内部将会根据这一关联关系自动进行数据绑定。其中 `uid:Uid` 前面的 `uid` 表示查询结果字段中的 `uid` 字段,后面的 `Uid` 表示目标关联对象中的 `Uid` 属性。 + +- `ScanList(&users, "UserScores", "User", "uid:Uid")` + +表示将查询到用户详情数组数据绑定到 `users` 列表中每一项的 `UserScores` 属性上,并且和另一个 `User` 对象属性通过 `uid:Uid` 的 `字段:属性` 关联,内部将会根据这一关联关系自动进行数据绑定。由于 `UserScores` 是一个数组类型 `[]*EntityUserScores`,因此该方法内部可以自动识别到 `User` 到 `UserScores` 其实是 `1:N` 的关系,自动完成数据绑定。 + +需要提醒的是,如果关联数据中对应的关联属性数据不存在,那么该属性不会被初始化并将保持 `nil`。 + +**2\. `ListItemValues/ListItemValuesUnique`** + +方法定义: + +```go +// ListItemValues retrieves and returns the elements of all item struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// +// The parameter supports types like: +// []map[string]interface{} +// []map[string]sub-map +// []struct +// []struct:sub-struct +// Note that the sub-map/sub-struct makes sense only if the optional parameter is given. +func ListItemValues(list interface{}, key interface{}, subKey ...interface{}) (values []interface{}) + +// ListItemValuesUnique retrieves and returns the unique elements of all struct/map with key . +// Note that the parameter should be type of slice which contains elements of map or struct, +// or else it returns an empty slice. +// See gutil.ListItemValuesUnique. +func ListItemValuesUnique(list interface{}, key string, subKey ...interface{}) []interface{} +``` + +`ListItemValuesUnique` 与 `ListItemValues` 方法的区别在于过滤重复的返回值,保证返回的列表数据中不带有重复值。这两个方法都会在当给定的列表中包含 `struct`/ `map` 数据项时,用于获取指定属性/键名的数据值,构造成数组 `[]interface{}` 返回。示例: + +- `gdb.ListItemValuesUnique(users, "Uid")` 用于获取 `users` 数组中,每一个 `Uid` 属性,构造成 `[]interface{}` 数组返回。这里以便根据 `uid` 构造成 `SELECT...IN...` 查询。 +- `gdb.ListItemValuesUnique(users, "User", "Uid")` 用于获取 `users` 数组中,每一个 `User` 属性项中的 `Uid` 属性,构造成 `[]interface{}` 数组返回。这里以便根据 `uid` 构造成 `SELECT...IN...` 查询。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..affe1c9e861 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\205\263\350\201\224/\346\250\241\345\236\213\345\205\263\350\201\224-\351\235\231\346\200\201\345\205\263\350\201\224-With\347\211\271\346\200\247.md" @@ -0,0 +1,734 @@ +--- +slug: '/docs/core/gdb-chaining-relation-with' +title: '模型关联-静态关联-With特性' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,With特性,ORM,模型关联,数据查询,事务操作,数据结构,Go语言,数据库,SQL] +description: 'GoFrame框架中的With特性,通过示例展示了如何使用With特性实现模型关联、数据查询,介绍了数据结构定义、事务操作、数据写入与查询等方面的内容,帮助开发者更好地理解和使用GoFrame框架进行高效开发。' +--- + +## 一、设计背景 + +大家都知道易用性和易维护性一直是 `goframe` 一直努力建设的,也是 `goframe` 有别其他框架和组件比较大的一点差异。 `goframe` 没有采用其他 `ORM` 常见的 `BelongsTo`,`HasOne`,`HasMany`,`ManyToMany` 这样的模型关联设计,这样的关联关系维护较繁琐,例如外键约束、额外的标签备注等,对开发者有一定的心智负担。因此框架不倾向于通过向模型结构体中注入过多复杂的标签内容、关联属性或方法,并一如既往地尝试着简化设计,目标是使得模型关联查询尽可能得易于理解、使用便捷。因此在之前推出了 `ScanList` 方案,建议大家在继续了解 `With` 特性之前先了解一下 [模型关联-动态关联-ScanList](模型关联-动态关联-ScanList.md) 。 + +经过一系列的项目实践,我们发现 `ScanList` 虽然从运行时业务逻辑的角度来维护了模型关联关系,但是这种关联关系维护也不如期望的简便。因此,我们继续改进推出了可以通过模型简单维护关联关系的 `With` 模型关联特性,当然,这种特性仍然致力于提升整体框架的易用性和维护性,可以把 `With` 特性看做 `ScanList` 与模型关联关系维护的一种结合和改进。 + +:::warning +`With` 特性目前属于实验性特性。 +::: +## 二、举个例子 + +我们先来一个简单的示例,便于大家更好理解 `With` 特性,该示例来自于之前的 `ScanList` 章节的相同示例,改进版。 + +### 1、数据结构 + +```sql +# 用户表 +CREATE TABLE `user` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + name varchar(45) NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# 用户详情 +CREATE TABLE `user_detail` ( + uid int(10) unsigned NOT NULL AUTO_INCREMENT, + address varchar(45) NOT NULL, + PRIMARY KEY (uid) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +# 用户学分 +CREATE TABLE `user_scores` ( + id int(10) unsigned NOT NULL AUTO_INCREMENT, + uid int(10) unsigned NOT NULL, + score int(10) unsigned NOT NULL, + PRIMARY KEY (id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +### 2、数据结构 + +根据表定义,我们可以得知: + +1. 用户表与用户详情是 `1:1` 关系。 +2. 用户表与用户学分是 `1:N` 关系。 +3. 这里并没有演示 `N:N` 的关系,因为相比较于 `1:N` 的查询只是多了一次关联、或者一次查询,最终处理方式和 `1:N` 类似。 + +那么 `Golang` 的模型可定义如下: + +```go +// 用户详情 +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +// 用户学分 +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} +// 用户信息 +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 3、数据写入 + +为简化示例,我们这里创建 `5` 条用户数据,采用事务操作方式写入: + +- 用户信息, `id` 为 `1-5`, `name` 为 `name_1` 到 `name_5`。 +- 同时创建 `5` 条用户详情数据, `address` 数据为 `address_1` 到 `address_5`。 +- 每个用户创建 `5` 条学分信息,学分为 `1-5`。 + +```go +g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { + for i := 1; i <= 5; i++ { + // User. + user := User{ + Name: fmt.Sprintf(`name_%d`, i), + } + lastInsertId, err := g.Model(user).Data(user).OmitEmpty().InsertAndGetId() + if err != nil { + return err + } + // Detail. + userDetail := UserDetail{ + Uid: int(lastInsertId), + Address: fmt.Sprintf(`address_%d`, lastInsertId), + } + _, err = g.Model(userDetail).Data(userDetail).OmitEmpty().Insert() + if err != nil { + return err + } + // Scores. + for j := 1; j <= 5; j++ { + userScore := UserScores{ + Uid: int(lastInsertId), + Score: j, + } + _, err = g.Model(userScore).Data(userScore).OmitEmpty().Insert() + if err != nil { + return err + } + } + } + return nil +}) +``` + +执行成功后,数据库数据如下: + +```text +mysql> show tables; ++----------------+ +| Tables_in_test | ++----------------+ +| user | +| user_detail | +| user_score | ++----------------+ +3 rows in set (0.01 sec) + +mysql> select * from `user`; ++----+--------+ +| id | name | ++----+--------+ +| 1 | name_1 | +| 2 | name_2 | +| 3 | name_3 | +| 4 | name_4 | +| 5 | name_5 | ++----+--------+ +5 rows in set (0.01 sec) + +mysql> select * from `user_detail`; ++-----+-----------+ +| uid | address | ++-----+-----------+ +| 1 | address_1 | +| 2 | address_2 | +| 3 | address_3 | +| 4 | address_4 | +| 5 | address_5 | ++-----+-----------+ +5 rows in set (0.00 sec) + +mysql> select * from `user_score`; ++----+-----+-------+ +| id | uid | score | ++----+-----+-------+ +| 1 | 1 | 1 | +| 2 | 1 | 2 | +| 3 | 1 | 3 | +| 4 | 1 | 4 | +| 5 | 1 | 5 | +| 6 | 2 | 1 | +| 7 | 2 | 2 | +| 8 | 2 | 3 | +| 9 | 2 | 4 | +| 10 | 2 | 5 | +| 11 | 3 | 1 | +| 12 | 3 | 2 | +| 13 | 3 | 3 | +| 14 | 3 | 4 | +| 15 | 3 | 5 | +| 16 | 4 | 1 | +| 17 | 4 | 2 | +| 18 | 4 | 3 | +| 19 | 4 | 4 | +| 20 | 4 | 5 | +| 21 | 5 | 1 | +| 22 | 5 | 2 | +| 23 | 5 | 3 | +| 24 | 5 | 4 | +| 25 | 5 | 5 | ++----+-----+-------+ +25 rows in set (0.00 sec) +``` + +### 4、数据查询 + +新的 `With` 特性下,数据查询相当简便,例如,我们查询一条数据: + +```go +// 重新声明一下,防止大家上下来回拉动 +// 用户详情 +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +// 用户学分 +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} +// 用户信息 +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} + +var user *User +// WithAll 会查询带有with tag的字段,这个例子中,将会查询 UserDetail结构体对应的表和 UserScores结构体对应的表 +g.Model(tableUser).WithAll().Where("id", 3).Scan(&user) +``` + +以上语句您将会查询到用户ID为 `3` 的用户信息、用户详情以及用户学分信息,以上语句将会在数据库中自动执行以下 `SQL` 语句: + +```text +2021-05-02 22:29:52.634 [DEBU] [ 2 ms] [default] SHOW FULL COLUMNS FROM `user` +2021-05-02 22:29:52.635 [DEBU] [ 1 ms] [default] SELECT * FROM `user` WHERE `id`=3 LIMIT 1 +2021-05-02 22:29:52.636 [DEBU] [ 1 ms] [default] SHOW FULL COLUMNS FROM `user_detail` +2021-05-02 22:29:52.637 [DEBU] [ 1 ms] [default] SELECT `uid`,`address` FROM `user_detail` WHERE `uid`=3 LIMIT 1 +2021-05-02 22:29:52.643 [DEBU] [ 6 ms] [default] SHOW FULL COLUMNS FROM `user_score` +2021-05-02 22:29:52.644 [DEBU] [ 0 ms] [default] SELECT `id`,`uid`,`score` FROM `user_score` WHERE `uid`=3 +``` + +执行后,通过 `g.Dump(user)` 打印的用户信息如下: + +```js +{ + Id: 3, + Name: "name_3", + UserDetail: { + Uid: 3, + Address: "address_3", + }, + UserScores: [ + { + Id: 11, + Uid: 3, + Score: 1, + }, + { + Id: 12, + Uid: 3, + Score: 2, + }, + { + Id: 13, + Uid: 3, + Score: 3, + }, + { + Id: 14, + Uid: 3, + Score: 4, + }, + { + Id: 15, + Uid: 3, + Score: 5, + }, + ], +} +``` + +### 5、列表查询 + +我们来一个通过 `With` 特性查询列表的示例: + +```go +var users []*User +// With(UserDetail{}) 只查询User结构体中的UserDetail对应的表 +g.Model(users).With(UserDetail{}).Where("id>?", 3).Scan(&users) +``` + +执行后,通过 `g.Dump(users)` 打印用户数据如下: + +```js +[ + { + Id: 4, + Name: "name_4", + UserDetail: { + Uid: 4, + Address: "address_4", + }, + UserScores: [], + }, + { + Id: 5, + Name: "name_5", + UserDetail: { + Uid: 5, + Address: "address_5", + }, + UserScores: [], + }, +] +``` + +### 6、条件与排序 + +通过 `With` 特性关联时可以指定关联的额外条件,以及在多数据结果下指定排序规则。例如: + +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, where:uid > 3"` + UserScores []*UserScores `orm:"with:uid=id, where:score>1 and score<5, order:score desc"` +} +``` + +通过 `orm` 标签中的 `where` 子标签以及 `order` 子标签指定额外关联条件体积排序规则。 + +### 7、`unscoped`标签 +在`with`结构体标签中,支持使用`unscoped`特性,例如: +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id, unscoped:true"` + UserScores []*UserScore `orm:"with:uid=id, unscoped:true"` +} +``` + +## 三、详细说明 + +想必您一定对上面的某些使用比较好奇,比如 `gmeta` 包、比如 `WithAll` 方法、比如 `orm` 标签中的 `with` 语句、比如 `Model` 方法给定 `struct` 参数识别数据表名等等,那这就对啦,接下来,我们详细聊聊吧。 + +### 1、 `gmeta` 包 + +我们可以看到在上面的结构体数据结构中都使用 `embed` 方式嵌入了一个 `g.Meta` 结构体,例如: + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} +``` + +其实在 `GoFrame` 框架中有很多这种小组件包用以实现特定的便捷功能。 `gmeta` 包的作用主要用于嵌入到用户自定义的结构体中,并且通过标签的形式给 `gmeta` 包的结构体(例如这里的 `g.Meta`)打上自定义的标签内容(列如这里的 `` `orm:"table:user_detail"` ``),并在运行时可以特定方法动态获取这些自定义的标签内容。详情请参考章节: [元数据-gmeta](../../../../组件列表/实用工具/元数据-gmeta.md) + +因此,这里嵌入 `g.Meta` 的目的是为了标记该结构体关联的数据表名称。 + +### 2、模型关联指定 + +在如下结构体中: + +```go +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScore `orm:"with:uid=id"` +} +``` + +我们通过给指定的结构体属性绑定 `orm` 标签,并在 `orm` 标签中通过 `with` 语句指定当前结构体(数据表)与目标结构体(数据表)的关联关系, `with` 语句的语法如下: + +```text +with:当前属性对应表关联字段=当前结构体对应数据表关联字段 +``` + +并且字段名称 **忽略大小写以及特殊字符匹配**,例如以下形式的关联关系都是能够自动识别的: + +```text +with:UID=ID +with:Uid=Id +with:U_ID=id +``` + +如果两个表的关联字段都是同一个名称,那么也可以直接写一个即可,例如: + +```text +with:uid +``` + +在本示例中, `UserDetail` 属性对应的数据表为 `user_detail`, `UserScores` 属性对应的数据表为 `user_score`,两者与当前 `User` 结构体对应的表 `user` 都是使用 `uid` 进行关联,并且目标关联的 `user` 表的对应字段为 `id`。 + +### 3、 `With/WithAll` + +#### 1)基本介绍 + +默认情况下,即使我们的结构体属性中的 `orm` 标签带有 `with` 语句, `ORM` 组件并不会默认启用 `With` 特性进行关联查询,而是需要依靠 `With/WithAll` 方法启用该查询特性。 + +- `With`:指定启用关联查询的数据表,通过给定的属性对象指定。 +- `WithAll`:启用操作对象中所有带有 `with` 语句的属性结构体关联查询。 + +在我们本示例中,使用的是 `WithAll` 方法,因此自动启用了 `User` 表中的所有属性的模型关联查询,只要属性结构体关联了数据表,并且 `orm` 标签中带有 `with` 语句,那么都将会自动查询数据并根据模型结构的关联关系进行数据绑定。假如我们只启用某部分关联查询,并不启用全部属性模型的关联查询,那么可以使用 `With` 方法来指定。并且 `With` 方法可以指定启用多个关联模型的自动查询,在本示例中的 `WithAll` 就相当于: + +```go +var user *User +g.Model(tableUser).With(UserDetail{}, UserScore{}).Where("id", 3).Scan(&user) +``` + +也可以这样: + +```go +var user *User +g.Model(tableUser).With(User{}.UserDetail, User{}.UserScore).Where("id", 3).Scan(&user) +``` + +#### 2)仅关联用户详情模型 + +假如我们只需要查询用户详情,并不需要查询用户学分,那么我们可以使用 `With` 方法来启用指定对象对应数据表的关联查询,例如: + +```go +var user *User +g.Model(tableUser).With(UserDetail{}).Where("id", 3).Scan(&user) +``` + +也可以这样: + +```go +var user *User +g.Model(tableUser).With(User{}.UserDetail).Where("id", 3).Scan(&user) +``` + +执行后,通过 `g.Dump(user)` 打印用户数据如下: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": { + "uid": 3, + "address": "address_3" + }, + "UserScores": [] +} +``` + +#### 3)仅关联用户学分模型 + +我们也可以只关联查询用户学分信息,例如: + +```go +var user *User +g.Model(tableUser).With(UserScore{}).Where("id", 3).Scan(&user) +``` + +也可以这样: + +```go +var user *User +g.Model(tableUser).With(User{}.UserScore).Where("id", 3).Scan(&user) +``` + +执行后,通过 `g.Dump(user)` 打印用户数据如下: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": null, + "UserScores": [ + { + "id": 11, + "uid": 3, + "score": 1 + }, + { + "id": 12, + "uid": 3, + "score": 2 + }, + { + "id": 13, + "uid": 3, + "score": 3 + }, + { + "id": 14, + "uid": 3, + "score": 4 + }, + { + "id": 15, + "uid": 3, + "score": 5 + } + ] +} +``` + +#### 4)不关联任何模型查询 + +假如,我们不需要关联查询,那么更简单,例如: + +```go +var user *User +g.Model(tableUser).Where("id", 3).Scan(&user) +``` + +执行后,通过 `g.Dump(user)` 打印用户数据如下: + +```js +{ + "id": 3, + "name": "name_3", + "UserDetail": null, + "UserScores": [] +} +``` + +## 四、使用限制 + +### 1、字段查询与过滤 + +可以看到,在我们上面的示例中,并没有指定查询的字段,但是在打印的 `SQL` 日志中可以看到查询语句不是简单的 `SELECT *` 而是执行了具体的字段查询。在 `With` 特性下,将会自动按照关联模型对象的属性进行查询,属性的名称将会与数据表的字段做自动映射,并且会自动过滤掉无法自动映射的字段查询。 + +所以,在 `With` 特性下,我们无法做到仅查询属性中对应的某几个字段。如果需要实现仅查询并赋值某几个字段,建议您对 `model` 数据结构按照业务场景进行裁剪,创建满足特定业务场景的数据结构,而不是使用一个数据结构满足不同的多个场景。 + +我们来一个示例更好说明。假如我们有一个实体对象数据结构 `Content`,一个常见的 `CMS` 系统的内容模型如下,该模型与数据表字段一一对应: + +```go +type Content struct { + Id uint `orm:"id,primary" json:"id"` // 自增ID + Key string `orm:"key" json:"key"` // 唯一键名,用于程序硬编码,一般不常用 + Type string `orm:"type" json:"type"` // 内容模型: topic, ask, article等,具体由程序定义 + CategoryId uint `orm:"category_id" json:"category_id"` // 栏目ID + UserId uint `orm:"user_id" json:"user_id"` // 用户ID + Title string `orm:"title" json:"title"` // 标题 + Content string `orm:"content" json:"content"` // 内容 + Sort uint `orm:"sort" json:"sort"` // 排序,数值越低越靠前,默认为添加时的时间戳,可用于置顶 + Brief string `orm:"brief" json:"brief"` // 摘要 + Thumb string `orm:"thumb" json:"thumb"` // 缩略图 + Tags string `orm:"tags" json:"tags"` // 标签名称列表,以JSON存储 + Referer string `orm:"referer" json:"referer"` // 内容来源,例如github/gitee + Status uint `orm:"status" json:"status"` // 状态 0: 正常, 1: 禁用 + ReplyCount uint `orm:"reply_count" json:"reply_count"` // 回复数量 + ViewCount uint `orm:"view_count" json:"view_count"` // 浏览数量 + ZanCount uint `orm:"zan_count" json:"zan_count"` // 赞 + CaiCount uint `orm:"cai_count" json:"cai_count"` // 踩 + CreatedAt *gtime.Time `orm:"created_at" json:"created_at"` // 创建时间 + UpdatedAt *gtime.Time `orm:"updated_at" json:"updated_at"` // 修改时间 +} +``` + +内容的列表页又不需要展示这么详细的内容,特别是其中的 `Content` 字段非常大,我们列表页只需要查询几个字段而已。那么我们可以单独定义一个用于列表的返回数据结构(字段裁剪),而不是直接使用数据表实体对象数据结构。例如: + +```go +type ContentListItem struct { + Id uint `json:"id"` // 自增ID + CategoryId uint `json:"category_id"` // 栏目ID + UserId uint `json:"user_id"` // 用户ID + Title string `json:"title"` // 标题 + CreatedAt *gtime.Time `json:"created_at"` // 创建时间 + UpdatedAt *gtime.Time `json:"updated_at"` // 修改时间 +} +``` + +### 2、必须存在关联字段属性 + +由于 `With` 特性是通过识别数据结构关联关系,并自动执行多条SQL查询来实现的,因此关联的字段也必须作为对象的属性便于关联字段值得自动获取。简单地讲, `with` 标签中的字段必须存在于关联对象的属性上。 + +## 五、递归关联 + +如果关联的模型属性也带有 `with` 标签,那么将会递归执行关联查询。 `With` 特性支持无限层级的递归关联。以下示例,仅供参考: + +```go +// 用户详情 +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +// 用户学分 - 必修课 +type UserScoresRequired struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +// 用户学分 - 选修课 +type UserScoresOptional struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +// 用户学分 +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Required []UserScoresRequired `orm:"with:id, where:type=1"` + Optional []UserScoresOptional `orm:"with:id, where:type=2"` +} + +// 用户信息 +type User struct { + g.Meta `orm:"table:user"` + Id int `json:"id"` + Name string `json:"name"` + UserDetail *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +## 六、模型示例 + +根据当前的数据表,这里给了更多的一些模型编写示例供大家参考。 + +### 1、关联模型嵌套 + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + g.Meta `orm:"table:user"` + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +嵌套的模型也支持嵌套,只要是结构体嵌套的都支持自动数据赋值。例如: + +```go +type UserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserDetailEmbedded struct { + UserDetail +} + +type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + *UserDetailEmbedded `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 2、基础模型嵌套 + +```go +type UserDetail struct { + g.Meta `orm:"table:user_detail"` + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + g.Meta `orm:"table:user_scores"` + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type UserEmbedded struct { + Id int `json:"id"` + Name string `json:"name"` +} + +type User struct { + g.Meta `orm:"table:user"` + UserEmbedded + *UserDetail `orm:"with:uid=id"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +### 3、模型不带 `meta` 信息 + +模型中的 `meta` 结构重要的是指定数据表名称,当不存在 `meta` 信息时,查询的数据表将会自动以结构体名称的 `CaseSnake` 名称。例如, `UserDetail` 将会自动使用 `user_detail` 数据表名称, `UserScores` 将会自动使用 `user_scores` 数据表名称。 + +```go +type UserDetail struct { + Uid int `json:"uid"` + Address string `json:"address"` +} + +type UserScores struct { + Id int `json:"id"` + Uid int `json:"uid"` + Score int `json:"score"` +} + +type User struct { + *UserDetail `orm:"with:uid=id"` + Id int `json:"id"` + Name string `json:"name"` + UserScores []*UserScores `orm:"with:uid=id"` +} +``` + +## 七、后续改进 + +- 目前 `With` 特性仅实现了查询操作,还不支持写入更新等操作。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" new file mode 100644 index 00000000000..ed965a5200b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234-\346\250\241\345\236\213\345\210\233\345\273\272.md" @@ -0,0 +1,140 @@ +--- +slug: '/docs/core/gdb-chaining-model' +title: 'ORM链式操作-模型创建' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,链式操作,模型创建,Model对象,g.Model,Raw方法,链式安全,Clone方法,Safe方法] +description: '使用GoFrame框架中的ORM工具进行链式操作,重点讲解了模型创建、Model对象的使用以及实现链式安全的几种方式。通过使用Model、Raw方法,在默认和切换的数据库配置上创建模型对象,并探讨链式安全的实现,包括默认非链式安全、Clone方法克隆、Safe方法设置链式安全等操作技巧。' +--- + +## 模型创建 + +### `Model` + +`Model` 方法用于创建基于数据表的 `Model` 对象。常见的,也可以使用 `g` 对象管理模块中的 `Model` 方法在默认的数据库配置上创建 `Model` 对象。 + +使用示例: + +```go +g.Model("user") +// 或者 +g.DB().Model("user") +``` + +此外,在某些场景下,我们也可以通过 `DB` 方法切换当前模型的数据库对象,例如: + +```go +m := g.Model("user") +m = m.DB(g.DB("order")) +``` + +其效果与以下操作是一样的: + +```go +m := g.DB("user").Model("user") +``` + +### `Raw` + +`Raw` 方法用于创建一个基于原始 `SQL` 语句的 `Model` 对象。也可以使用 `g` 对象管理模块中的 `ModelRaw` 方法通过给定的 `SQL` 语句在默认的数据库配置上创建 `Model` 对象。 + +```go +s := "SELECT * FROM `user`" +m, _ := g.ModelRaw(s).WhereLT("age", 18).Limit(10).OrderAsc("id").All() +// SELECT * FROM `user` WHERE `age`<18 ORDER BY `id` ASC LIMIT 10 +``` + +```go +s := "SELECT * FROM `user` WHERE `status` IN(?)" +m, _ := g.ModelRaw(s, g.Slice{1,2,3}).WhereLT("age", 18).Limit(10).OrderAsc("id").All() +// SELECT * FROM `user` WHERE `status` IN(1,2,3) AND `age`<18 ORDER BY `id` ASC LIMIT 10 +``` + +## 链式安全 + +`链式安全` 只是模型操作的两种方式区别:一种会修改当前 `model` 对象(不安全,默认),一种不会(安全)但是模型属性修改/条件叠加需要使用赋值操作,仅此而已。 + +### 默认情况 + +在默认情况下, `gdb` 是 `非链式安全` 的,也就是说链式操作的每一个方法都将对当前操作的 `Model` 属性进行修改,因此该 `Model` 对象 **不可以重复使用**。例如,当存在多个分开查询的条件时,我们可以这么来使用 `Model` 对象: + +```go +user := g.Model("user") +user.Where("status", g.Slice{1,2,3}) +if vip { + // 查询条件自动叠加,修改当前模型对象 + user.Where("money>=?", 1000000) +} else { + // 查询条件自动叠加,修改当前模型对象 + user.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := user.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := user.Count() +``` + +可以看到,如果是分开执行链式操作,链式的每一个操作都会修改已有的 `Model` 对象,查询条件会自动叠加,因此 `user` 对象不可重复使用,否则条件会不停叠加。并且在这种使用方式中,每次我们需要操作 `user` 用户表,都得使用 `g.DB().Table("user")` 这样的语法创建一个新的 `user` 模型对象,相对来说会比较繁琐。 + +默认情况下,基于性能以及GC优化考虑,模型对象为 `非链式安全`,防止产生过多的临时模型对象。![(微笑)](/markdown/1f7ee2ac67fc5de100b8fc690d7438ea.svg) +:::tip +不过需要注意的是,如果使用的是cli工具 `gen dao` 生成的 `dao`,如 `user := dao.User.Ctx(ctx)`,此时获取到的 `user` `Model` 对象默认是链式安全的(已自动调用过 `.Safe()`)。 +::: +### `Clone` 方法 + +此外,我们也可以手动调动 `Clone` 方法克隆当前模型,创建一个新的模型来实现链式安全,由于是新的模型对象,因此并不担心会修改已有的模型对象的问题。例如: + +```go +// 定义一个用户模型单例 +user := g.Model("user") +``` + +```go +// 克隆一个新的用户模型 +m := user.Clone() +m.Where("status", g.Slice{1,2,3}) +if vip { + m.Where("money>=?", 1000000) +} else { + m.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := m.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := m.Count() +``` + +### `Safe` 方法 + +当然,我们可以通过 `Safe` 方法设置当前模型为 `链式安全` 的对象,后续的每一个链式操作都将返回一个新的 `Model` 对象,该 `Model` 对象可重复使用。但需要特别注意的是,模型属性的修改,或者操作条件的叠加,需要通过变量赋值的方式( `m = m.xxx`)覆盖原有的模型对象来实现。例如: + +```go +// 定义一个用户模型单例 +user := g.Model("user").Safe() +``` + +```go +m := user.Where("status", g.Slice{1,2,3}) +if vip { + // 查询条件通过赋值叠加 + m = m.Where("money>=?", 1000000) +} else { + // 查询条件通过赋值叠加 + m = m.Where("money= 1000000 +// !vip: SELECT * FROM user WHERE status IN(1,2,3) AND money < 1000000 +r, err := m.All() +// vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money >= 1000000 +// !vip: SELECT COUNT(1) FROM user WHERE status IN(1,2,3) AND money < 1000000 +n, err := m.Count() +``` + +可以看到,示例中的用户模型单例对象 `user` 可以重复使用,而不用担心被“污染”的问题。在这种链式安全的方式下,我们可以创建一个用户单例对象 `user`,并且可以重复使用到后续的各种查询中。但是存在多个查询条件时,条件的叠加需要通过模型赋值操作( `m = m.xxx`)来实现。 +:::tip +使用 `Safe` 方法标记之后,每一个链式操作都将会创建一个新的临时模型对象(内部自动使用 `Clone` 实现模型克隆),从而实现链式安全。这种使用方式在模型操作中比较常见。 +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..20de52d4c11 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\223\276\345\274\217\346\223\215\344\275\234/ORM\351\223\276\345\274\217\346\223\215\344\275\234.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/core/gdb-chaining' +title: 'ORM链式操作(🔥重点🔥)' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame框架,ORM链式操作,gdb,数据库操作,Model方法,事务处理,接口文档,数据库对象,GoFrame,链式调用] +description: 'GoFrame框架中gdb的链式操作方式,这是一种灵活且官方推荐的数据库操作方式。通过db.Model或tx.Model方法,可以基于数据表返回链式操作对象*Model,支持多种数据库操作,如Replace、Save、InsertGetId等,并详细说明了每种数据库的支持情况。' +--- + +## 基本介绍 + +`gdb` 链式操作使用方式简单灵活,是 `GoFrame` 框架官方推荐的数据库操作方式。链式操作可以通过数据库对象的 `db.Model` 方法或者事务对象的 `tx.Model` 方法,基于指定的数据表返回一个链式操作对象 `*Model`,该对象可以执行以下方法。当前方法列表可能滞后于源代码,详细的方法列表请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Model](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb#Model) + +## 部分不支持的操作 + +以下是最新版本的支持情况 + +| 类型 | Replace | Save | InsertIgnore | InsertGetId | LastInsertId | Transaction | RowsAffected | +| --- | --- | --- | --- | --- | --- | --- | --- | +| `mysql` | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| `mariadb` | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| `tidb` | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| `pgsql` | 不支持 | 支持 | 不支持 | 支持 | 支持 | 支持 | 支持 | +| `mssql` | 不支持 | 支持 | 支持 | 支持 | 不支持 | 支持 | 支持 | +| `sqlite` | 不支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| `oracle` | 不支持 | 支持 | 支持 | 支持 | 不支持 | 支持 | 支持 | +| `dm` | 不支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| `clickhouse` | 不支持 | 不支持 | 不支持 | 不支持 | 支持 | 不支持 | 不支持 | + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" new file mode 100644 index 00000000000..9ae51849322 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-RawSQL.md" @@ -0,0 +1,76 @@ +--- +slug: '/docs/core/gdb-senior-raw-sql' +title: 'ORM高级特性-RawSQL' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,ORM,RawSQL,SQL注入,gdb.Raw,数据插入,数据更新,数据查询,预处理模式,SQL语句] +description: '在GoFrame框架中使用ORM的RawSQL特性,通过使用gdb.Raw类型,可以在生成的SQL语句中嵌入自定义的SQL片段,实现更灵活的数据库操作。详细讲解了在Insert、Update和Select操作中使用RawSQL的方法及其示例,确保SQL语句的安全性和灵活性。' +--- + +由于 `ORM` 的安全性保障,所有输入的参数在底层都将使用预处理模式执行,防止常见的 `SQL` 注入风险。在某一些场景中,我们期望在生成执行的SQL语句中嵌入自定义的SQL语句,那么我们可以使用 `ORM` 的 `RawSQL` 特性,通过 `gdb.Raw` 类型来实现。我们来看几个示例。 + +## 在 `Insert` 中使用 `RawSQL` + +`gdb.Raw` 是字符串类型,该类型的参数将会直接作为 `SQL` 片段嵌入到提交到底层的 `SQL` 语句中,不会被自动转换为字符串参数类型、也不会被当做预处理参数。例如: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES('id+2','john','123456','now()') +g.Model("user").Data(g.Map{ + "id": "id+2", + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": "now()", +}).Insert() +// 执行报错:Error Code: 1136. Column count doesn't match value count at row 1 +``` + +使用 `gdb.Raw` 改造后: + +```go +// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES(id+2,'john','123456',now()) +g.Model("user").Data(g.Map{ + "id": gdb.Raw("id+2"), + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", + "create_time": gdb.Raw("now()"), +}).Insert() +``` + +## 在 `Update` 中使用 `RawSQL` + +```go +// UPDATE `user` SET login_count='login_count+1',update_time='now()' WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": "login_count+1", + "update_time": "now()", +}).Where("id", 1).Update() +// 执行报错:Error Code: 1136. Column count doesn't match value count at row 1 +``` + +使用 `gdb.Raw` 改造后: + +```go +// UPDATE `user` SET login_count=login_count+1,update_time=now() WHERE id=1 +g.Model("user").Data(g.Map{ + "login_count": gdb.Raw("login_count+1"), + "update_time": gdb.Raw("now()"), +}).Where("id", 1).Update() +``` + +## 在 `Select` 中使用 `RawSQL` + +时间函数 `now()` 将会被转换为字符串作为 `SQL` 参数执行: + +```go +// SELECT * FROM `user` WHERE `created_at`<'now()' +g.Model("user").WhereLT("created_at", "now()").All() +``` + +使用 `gdb.Raw` 改造后: + +```go +// SELECT * FROM `user` WHERE `created_at` `Value` 类型是 `*gvar.Var` 类型的别名,因此可以使用 `gvar.Var` 数据类型的所有转换方法,具体请查看 [泛型类型-gvar](../../../组件列表/数据结构/泛型类型-gvar/泛型类型-gvar.md) 章节 + +使用示例: + +首先,数据表定义如下: + +``` +# 商品表 +CREATE TABLE `goods` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `title` varchar(300) NOT NULL COMMENT '商品名称', + `price` decimal(10,2) NOT NULL COMMENT '商品价格', + ... + PRIMARY KEY (`id`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +``` + +其次,数据表中的数据如下: + +``` +id title price +1 IPhoneX 5999.99 +``` + +最后,示例代码如下: + +```go +if r, err := g.Model("goods").FindOne(1); err == nil { + fmt.Printf("goods id: %d\n", r["id"].Int()) + fmt.Printf("goods title: %s\n", r["title"].String()) + fmt.Printf("goods proce: %.2f\n", r["price"].Float32()) +} else { + g.Log().Error(gctx.New(), err) +} +``` + +执行后,输出结果为: + +``` +goods id: 1 +goods title: IPhoneX +goods proce: 5999.99 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..294819b353c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\207\252\345\256\232\344\271\211\347\261\273\345\236\213\350\275\254\346\215\242.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/core/gdb-senior-custom-type-converting' +title: 'ORM高级特性-自定义类型转换' +sidebar_position: 10 +hide_title: true +description: '在GoFrame框架中使用ORM的自定义类型转换特性,通过特定接口将查询结果转换为所需类型,无论是直接类型还是struct属性。此功能增强了GoFrame框架的灵活性,提供高效解决方案,助力开发者实现更高级的数据库交互。' +keywords: [GoFrame,GoFrame框架,ORM,自定义类型转换,类型转换接口,查询结果处理,灵活扩展,高效解决方案,struct属性,UnmarshalValue] +--- + +当我们需要将查询的结果转换到自定义的类型中,无论是作为直接转换的类型还是作为 `struct` 的属性,都可以通过实现特定的接口来实现。详细介绍请参考 [类型转换-UnmarshalValue](../../类型转换/类型转换-UnmarshalValue.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..37f9532ebb1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\260\203\350\257\225\346\250\241\345\274\217.md" @@ -0,0 +1,29 @@ +--- +slug: '/docs/core/gdb-senior-debugging' +title: 'ORM高级特性-调试模式' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,调试模式,SQL,日志,数据库,配置示例,SetDebug,Debug配置] +description: '在GoFrame框架中使用ORM高级特性的调试模式。在开发阶段,通过Debug配置文件配置项或SetDebug配置方式,可以开启调试模式,使数据库SQL操作语句以DEBUG级别输出到终端或日志文件中,便于开发者进行问题排查和性能优化。' +--- + +为便于开发阶段调试, `GoFrame ORM` 支持调试模式,可以通过 `Debug` 配置文件配置项或者 `SetDebug` 配置方式开启调试模式, 随后任何的数据库 `SQL` 操作语句都将会由内置的日志对象,以 `DEBUG` 级别输出到终端或者日志文件中。以下是一个开启了调试模式的配置示例: + +```yaml +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user" + debug: true +``` + +输出的日志内容示例: + +```html +2021-05-22 21:12:10.776 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 4 ms] [default] [rows:0 ] [txid:1] BEGIN +2021-05-22 21:12:10.776 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 0 ms] [default] [rows:0 ] [txid:1] SAVEPOINT `transaction0` +2021-05-22 21:12:10.789 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 13 ms] [default] [rows:8 ] [txid:1] SHOW FULL COLUMNS FROM `user` +2021-05-22 21:12:10.790 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:1 ] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(1,'john') +2021-05-22 21:12:10.791 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:0 ] [txid:1] ROLLBACK TO SAVEPOINT `transaction0` +2021-05-22 21:12:10.791 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 0 ms] [default] [rows:1 ] [txid:1] INSERT INTO `user`(`id`,`name`) VALUES(2,'smith') +2021-05-22 21:12:10.792 [DEBU] {38d45cbf2743db16f1062074f7473e5c} [ 1 ms] [default] [rows:0 ] [txid:1] COMMIT +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" new file mode 100644 index 00000000000..a468b7c4d71 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247-\350\277\236\346\216\245\346\261\240\347\212\266\346\200\201.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/core/gdb-senior-connection-pool' +title: 'ORM高级特性-连接池状态' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,连接池,DB Stats,数据库连接,gdb,mysql,GoFrame database,GoFrame gdb] +description: '使用GoFrame框架的DB.Stats方法获取ORM对象的连接池状态。通过示例代码,开发者可以了解如何配置数据库连接,并通过GoFrame获取连接池的详细状态信息。同时,介绍了连接池状态输出的具体字段及其意义。' +--- + +我们可以通过 `DB.Stats` 方法获取 `orm` 对象的连接池状态。我们来看个示例: + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + db, err := gdb.New(gdb.ConfigNode{ + Link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test", + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + err = db.PingMaster() + if err != nil { + g.Log().Fatal(ctx, err) + } + stats := db.Stats(ctx) + g.Dump(stats) +} +``` + +执行后,终端输出如下,可以看到每个链接的数据库节点以及对应的连接池状态信息。 + +```html +[ + { + node: { + Host: "127.0.0.1", + Port: "3306", + User: "root", + Pass: "12345678", + Name: "test", + Type: "mysql", + Link: "", + Extra: "", + Role: "", + Debug: false, + Prefix: "", + DryRun: false, + Weight: 0, + Charset: "utf8", + Protocol: "tcp", + Timezone: "", + Namespace: "", + MaxIdleConnCount: 0, + MaxOpenConnCount: 0, + MaxConnLifeTime: 0, + QueryTimeout: 0, + ExecTimeout: 0, + TranTimeout: 0, + PrepareTimeout: 0, + CreatedAt: "", + UpdatedAt: "", + DeletedAt: "", + TimeMaintainDisabled: false, + }, + stats: { + MaxOpenConnections: 0, + OpenConnections: 1, + InUse: 0, + Idle: 1, + WaitCount: 0, + WaitDuration: 0, + MaxIdleClosed: 0, + MaxIdleTimeClosed: 0, + MaxLifetimeClosed: 0, + }, + }, +] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..5d9edeace2b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/ORM\351\253\230\347\272\247\347\211\271\346\200\247/ORM\351\253\230\347\272\247\347\211\271\346\200\247.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gdb-senior' +title: 'ORM高级特性' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,ORM,高级特性,数据库管理,gdb,Go开发,数据操作,建模,查询优化] +description: 'GoFrame框架的ORM高级特性,主要包括数据库管理和操作的高级功能。这些特性能帮助开发者更高效地处理数据建模、优化查询性能以及简化数据操作流程,进而提高使用GoFrame框架进行开发的效率和质量。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" new file mode 100644 index 00000000000..13c4951f335 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\345\272\223ORM/\346\225\260\346\215\256\345\272\223ORM.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/core/gdb' +title: '数据库ORM🔥' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据库ORM,gdb模块,数据库驱动,链接池,预处理,自动化,可观测性,DAO设计] +description: '在GoFrame框架中使用gdb模块实现数据库的ORM功能,强调了连接池设计、预处理SQL参数以及自动识别Map/Struct的特点。GoFrame ORM组件支持事务嵌套、接口化设计,并兼容主流数据库驱动,具备强大的配置管理和调试特性。' +--- + +## 驱动引入🔥 + +为了将数据库驱动与框架主库解耦,从 `v2.1` 版本开始,所有的数据库驱动都需要通过社区包手动引入。 + +数据库驱动的安装和引入请参考: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## 基本介绍 + +`GoFrame` 框架的 `ORM` 功能由 `gdb` 模块实现,用于常用关系型数据库的 `ORM` 操作。 +:::tip +`gdb` 数据库引擎底层采用了 **链接池设计**,当链接不再使用时会自动关闭,因此链接对象不用的时候不需要显式使用 `Close` 方法关闭数据库连接。 +::: +:::note +注意:为提高数据库操作安全性,在 `ORM` 操作中不建议直接将参数拼接成 `SQL` 字符串执行,建议使用预处理的方式(充分使用 `?` 占位符)来传递 `SQL` 参数。 `gdb` 的底层实现中均采用的是预处理的方式处理开发者传递的参数,以充分保证数据库操作安全性。 +::: +**接口文档:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb](https://pkg.go.dev/github.com/gogf/gf/v2/database/gdb) + +## 组件特性 + +`GoFrame ORM` 组件具有以下显著特点: + +1. 全自动化支持嵌套事务。 +2. 面向接口化设计、易使用易扩展。 +3. 内置支持主流数据库类型驱动,并易于扩展。 +4. 强大的配置管理,使用框架统一的配置组件。 +5. 支持单例模式获取配置同一分组数据库对象。 +6. 支持原生SQL方法操作、ORM链式操作两种方式。 +7. 支持 `OpenTelemetry` 可观测性:链路跟踪、日志记录、指标上报。 +8. 通过 `Scan` 方法自动识别 `Map/Struct` 接收查询结果,自动化查询结果初始化、结构体类型转换。 +9. 通过返回结果 `nil` 识别为空,无需 `sql.ErrNoRows` 识别查询数据为空的情况。 +10. 全自动化的结构体属性-字段映射,无需显示定义结构体标签维护属性-字段映射关系。 +11. 自动化的给定 `Map/Struct/Slice` 参数类型中的字段识别、过滤,大大提高查询条件输入、结果接收。 +12. 完美支持 `GoFrame` 框架层面的 `DAO` 设计,全自动化 `Model/DAO` 代码生成,极大提高开发效率。 +13. 支持调试模式、日志输出、 `DryRun`、自定义 `Handler`、自动类型类型转换、自定义接口转换等等高级特性。 +14. 支持查询缓存、软删除、自动化时间更新、模型关联、数据库集群配置(软件主从模式)等等实用特性。 + + +## 组件关联 + +![](/markdown/cf10ab2ff4d4b341190d5e5a47692061.png) + +`GoFrame ORM Dependencies` + +## `g.DB` 与 `gdb.New`、 `gdb.Instance` + +获取数据库操作对象有三种方式,一种是使用 `g.DB` 方法(推荐),一种是使用原生 `gdb.New` 方法,还有一种是使用包原生单例方法 `gdb.Instance`,而第一种是推荐的使用方式。这三种方式的区别如下: + +1. `g.DB` 对象管理方式获取的是单例对象,整合了配置文件的管理功能,支持配置文件热更新。 +2. `gdb.New` 是根据给定的数据库节点配置创建一个新的数据库对象(非单例),无法使用配置文件。 +3. `gdb.Instance` 是包原生单例管理方法,需要结合配置方法一起使用,通过分组名称(非必需)获取对应配置的数据库单例对象。 +:::tip +有这么多对象获取方式原因在于 `GoFrame` 是一个模块化设计的框架,每个模块皆可单独使用。 +::: +### `New` 创建数据库对象 + +```go +db, err := gdb.New(gdb.ConfigNode{ + Link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test", +}) +``` + +### 获取数据库对象单例 + +```go +// 获取默认配置的数据库对象(配置名称为"default") +db := g.DB() + +// 获取配置分组名称为"user"的数据库对象 +db := g.DB("user") + +// 使用原生单例管理方法获取数据库对象单例 +db, err := gdb.Instance() +db, err := gdb.Instance("user") +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..e28b11494e4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,106 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map-example' +title: 'Map校验-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,参数校验,Map校验,自定义错误提示,默认错误提示,框架使用,验证规则,代码示例,数据验证] +description: '在GoFrame框架中进行Map校验,演示了如何使用默认及自定义错误提示。通过示例代码展示了如何对参数进行验证,以及在验证失败时输出相应的错误信息,帮助开发者更好地实现数据验证和错误处理机制。' +--- + +## 默认错误提示 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +执行后,终端输出: + +```javascript +{ + "passport": { + "required": "The passport field is required", + "length": "The passport value `` length must be between 6 and 16", + }, + "password": { + "same": "The password value `123456` must be the same as field password2", + }, +} +``` + +## 自定义错误提示 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + messages = map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在{min}到{max}之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, + } + ) + + err := g.Validator().Messages(messages).Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +该示例同时也展示了 `messsages` 自定义错误信息传递的两种数据类型, `string` 或者 `map[string]string`。其中 `map[string]string` 类型参数需要指定对应字段、对应规则的错误提示信息,是一个二维的“关联数组”。该示例执行后,终端输出: + +```javascript +{ + "passport": { + "length": "账号长度应当在6到16之间", + "required": "账号不能为空" + }, + "password": { + "same": "两次密码输入不相等" + } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" new file mode 100644 index 00000000000..cef444a625e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/Map\346\240\241\351\252\214-\346\240\241\351\252\214\351\241\272\345\272\217\346\200\247.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map-sequence' +title: 'Map校验-校验顺序性' +sidebar_position: 1 +hide_title: true +keywords: [Map校验,校验顺序性,GoFrame,参数校验,golang,map类型,规则排序,校验错误信息,gogf,数据校验] +description: '在GoFrame框架中实现Map校验的顺序性。通过修改规则参数类型为[]string,可以确保返回的错误信息顺序与设定规则一致,解决了golang中map类型无序性导致的校验结果不固定的问题。本教程提供了详细的示例代码和执行结果,帮助用户理解如何使用GoFrame进行顺序校验。' +--- + +## 基本介绍 + +如果将之前的示例代码多执行几次之后会发现,返回的结果是没有排序的,而且字段及规则输出的先后顺序完全是随机的。即使我们使用 `FirstItem`, `FirstString()` 等其他方法获取校验结果也是一样,返回的校验结果不固定。那是因为校验的规则我们传递的是 `map` 类型,而 `golang` 的 `map` 类型并不具有有序性,因此校验的结果和规则一样是随机的,同一个校验结果的同一个校验方法多次获取结果值返回的可能也不一样了。 + +## 顺序校验 + +我们来改进一下以上的示例:校验结果中如果不满足 `required` 那么返回对应的错误信息,否则才是后续的校验错误信息;也就是说,返回的错误信息应当和我设定规则时的顺序一致。如下: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = []string{ + "passport@required|length:6,16#账号不能为空|账号长度应当在{min}到{max}之间", + "password@required|length:6,16|same:password2#密码不能为空|密码长度应当在{min}到{max}之间|两次密码输入不相等", + "password2@required|length:6,16#", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + fmt.Println(err.Map()) + fmt.Println(err.FirstItem()) + fmt.Println(err.FirstError()) + } +} +``` + +执行后,终端输出: + +``` +map[length:账号长度应当在6到16之间 required:账号不能为空] +passport map[length:账号长度应当在6到16之间 required:账号不能为空] +账号不能为空 +``` + +可以看到,我们想要校验结果满足顺序性,只需要将 `rules` 参数的类型修改为 `[]string`,按照一定的规则设定即可,并且 `msgs` 参数既可以定义到 `rules` 参数中,也可以分开传入(使用第三个参数)。 `rules` 的中的校验规则编写请参考章节 [Struct校验-基本使用](../数据校验-Struct校验/Struct校验-基本使用.md) 。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..f1e7481a08a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Map\346\240\241\351\252\214.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/core/gvalid-parameter-type-map' +title: '数据校验-Map校验' +hide_title: true +keywords: [GoFrame,数据校验,Map校验,参数校验,GoFrame框架,验证规则,输入验证,数据完整性,Golang,开发框架] +description: '使用GoFrame框架进行数据校验,特别是针对Map类型的数据校验方法。通过GoFrame框架,开发者可以轻松实现对输入数据的验证,确保数据的有效性和完整性。页面详细阐述了使用验证规则的步骤,以提升应用程序的安全性和可靠性。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" new file mode 100644 index 00000000000..70220273610 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-Assoc\345\205\263\350\201\224.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct-assoc' +title: 'Struct校验-Assoc关联' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gvalid,Struct校验,Assoc方法,参数校验,结构体校验,goframe v2.0,gvalid tag,客户端请求校验] +description: '在GoFrame框架中,为了避免结构体默认值带来的问题,引入了Assoc方法,可以严格按照给定参数进行结构体校验。此方法特别适用于需要处理客户端请求参数的场景,确保校验规则不受默认值影响。' +--- + +为了避免结构体默认值带来的困惑,从 `goframe v2.0` 版本开始,我们增加了一个 `Assoc` 方法,用于结构体校验时严格按照给定的参数而不是按照结构体的属性值(避免结构体属性默认值的影响),而校验规则同样会自动读取结构体中的 `gvalid tag`。 +:::tip +该特定对接收客户端请求参数校验的场景特别有用。 +::: +## 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` + } + var ( + ctx = gctx.New() + user = User{} + data = g.Map{ + "name": "john", + } + ) + err := g.Validator().Assoc(data).Data(user).Run(ctx) + if err != nil { + g.Dump(err.Items()) + } +} +``` + +执行后,终端输出: + +``` +[ + { + "Type": { + "required": "请选择用户类型" + } + } +] +``` + +可以看到,结构体中的属性 `Type` 校验规则 `required` 并没有受到默认值的影响,仍旧被执行了预期的校验检查。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..67d485e43bd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/Struct\346\240\241\351\252\214-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,225 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct-example' +title: 'Struct校验-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [Struct校验,GoFrame框架,gvalid,参数校验,属性别名,校验规则,链式操作,嵌套校验,递归校验,Go] +description: '使用GoFrame框架进行Struct类型数据的校验,包括对校验tag规则的详细说明以及不同类型的数据校验方法,如基本校验、使用map自定义规则、以及结构体的递归校验示例。通过示例代码展示如何设置属性别名和自定义错误提示信息,实现对struct对象中不同属性的复杂校验逻辑。' +--- + +`Struct` 校验常使用以下链式操作方式: + +```go +g.Validator().Data(object).Run(ctx) +``` + +## 校验 `tag` 规则介绍 + +在开始介绍 `Struct` 参数类型校验之前,我们来介绍一下常用的校验 `tag` 规则。规则如下: + +``` +[属性别名@]校验规则[#错误提示] +``` + +其中: + +- `属性别名` 和 `错误提示` 为 **非必需字段**, `校验规则` 是 **必需字段。** +- `属性别名` 非必需字段,指定在校验中使用的对应 `struct` 属性的别名,同时校验后返回的 `error` 对象中的也将使用该别名返回。例如在处理请求表单时比较有用,因为表单的字段名称往往和 `struct` 的属性名称不一致。大部分场景下不需要设置属性别名,默认直接使用属性名称即可。 +- `校验规则` 则为当前属性的校验规则,多个校验规则请使用 `|` 符号组合,例如: `required|between:1,100`。 +- `错误提示` 非必需字段,表示自定义的错误提示信息,当规则校验时对默认的错误提示信息进行覆盖。 + +## 校验 `tag` 使用示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type User struct { + Uid int `v:"uid @integer|min:1#|请输入用户ID"` + Name string `v:"name @required|length:6,30#请输入用户名称|用户名称长度非法"` + Pass1 string `v:"password1@required|password3"` + Pass2 string `v:"password2@required|password3|same:Pass1#|密码格式不合法|两次密码不一致,请重新输入"` +} + +func main() { + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass1: "Abc123!@#", + Pass2: "123", + } + ) + + err := g.Validator().Data(user).Run(ctx) + if err != nil { + g.Dump(err.Items()) + } +} +``` + +可以看到,我们可以对在 `struct` 定义时使用了 `gvalid` 的 `gvalid tag` 来绑定校验的规则及错误提示信息。在此示例代码中, `same:password1` 规则同使用 `same:Pass1` 规则是一样的效果。 **也就是说,在数据校验中,可以同时使用原有的 `struct` 属性名称,也可以使用别名。但是,返回的结果中只会使用别名返回,这也是别名最大的用途。** 此外,在对 `struct` 对象进行校验时,也可以传递校验或者和错误提示参数,这个时候会覆盖 `struct` 在定义时绑定的对应参数。 + +以上示例执行后,输出结果为: + +``` +[ + { + "uid": { + "min": "请输入用户ID", + }, + }, + { + "name": { + "length": "用户名称长度非法", + }, + }, + { + "password2": { + "password3": "密码格式不合法", + }, + }, +] +``` + +## 使用 `map` 指定校验规则 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Age int + Name string + } + var ( + ctx = gctx.New() + user = User{Name: "john"} + rules = map[string]string{ + "Name": "required|length:6,16", + "Age": "between:18,30", + } + messages = map[string]interface{}{ + "Name": map[string]string{ + "required": "名称不能为空", + "length": "名称长度为{min}到{max}个字符", + }, + "Age": "年龄为18到30周岁", + } + ) + + err := g.Validator().Rules(rules).Messages(messages).Data(user).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +在以上示例中, `Age` 属性由于默认值 `0` 的存在,因此会引起 `required` 规则的失效,因此这里没有使用 `required` 规则而是使用 `between` 规则来进行校验。示例代码执行后,终端输出: + +``` +{ + "Age": { + "between": "年龄为18到30周岁" + }, + "Name": { + "length": "名称长度为6到16个字符" + } +} +``` + +## 结构体递归校验(嵌套校验) + +支持递归的结构体校验(嵌套校验),即如果属性也是结构体(也支持嵌套结构体( `embedded`)),那么将会自动将该属性执行递归校验。使用示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Pass struct { + Pass1 string `v:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"` + Pass2 string `v:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` + } + type User struct { + Pass + Id int + Name string `valid:"name@required#请输入您的姓名"` + } + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + ) + err := g.Validator().Data(user).Run(ctx) + g.Dump(err.Maps()) +} +``` + +或者属性为嵌套结构体( `embedded`)的场景: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Pass struct { + Pass1 string `v:"password1@required|same:password2#请输入您的密码|您两次输入的密码不一致"` + Pass2 string `v:"password2@required|same:password1#请再次输入您的密码|您两次输入的密码不一致"` + } + type User struct { + Id int + Name string `valid:"name@required#请输入您的姓名"` + Pass Pass + } + var ( + ctx = gctx.New() + user = &User{ + Name: "john", + Pass: Pass{ + Pass1: "1", + Pass2: "2", + }, + } + ) + err := g.Validator().Data(user).Run(ctx) + g.Dump(err.Maps()) +} +``` + +执行后,终端输出: + +``` +{ + "password1": { + "same": "您两次输入的密码不一致", + }, + "password2": { + "same": "您两次输入的密码不一致", + }, +} +``` + +更多递归校验的介绍,请参考章节: [数据校验-递归校验](../../数据校验-递归校验.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..3563000d29c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-Struct\346\240\241\351\252\214.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/core/gvalid-parameter-type-struct' +title: '数据校验-Struct校验' +hide_title: true +keywords: [数据校验,Struct校验,GoFrame,GoFrame框架,参数类型,数据验证,输入验证,服务器开发,后端架构,参数检查] +description: '在GoFrame框架中使用Struct校验功能进行数据校验,可以帮助开发者进行输入验证和参数类型检查。通过Struct校验,可以更高效地确保数据的正确性,提高服务器端的可靠性。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..fba7ae4d2b3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\215\225\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,142 @@ +--- +slug: '/docs/core/gvalid-parameter-type-basic' +title: '数据校验-单数据校验' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据校验,单数据校验,校验规则,错误提示,数据长度,校验类型,正则校验,自定义提示] +description: '在GoFrame框架中如何进行单数据校验。它讨论了通过Data方法指定被校验数据,以及通过Rule方法指定校验规则的使用方法。实例演示了校验数据长度、数据类型和大小、以及正则表达式校验等不同的校验场景,包括如何应用多个自定义错误提示。' +--- + +我们可以将给定的变量当做一个完整的参数进行校验,即单数据校验。如果变量是 `Struct/Map` 复杂类型,我们需要校验其内部的属性/键值对的场景,将会在后续章节介绍。单数据校验必须通过 `Data` 方法给定被校验数据, `Rule` 方法给定校验规则。单数据校验比较简单,我们来看几个示例。 + +## 校验数据长度,使用默认的错误提示 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "length:6,16" + ) + + if err := g.Validator().Rules(rule).Data("123456").Run(ctx); err != nil { + fmt.Println(err.String()) + } + if err := g.Validator().Rules(rule).Data("12345").Run(ctx); err != nil { + fmt.Println(err.String()) + } +} +``` + +执行后,终端输出: + +``` +The value `12345` length must be between 6 and 16 +``` + +## 校验数据类型及大小,并且使用自定义的错误提示 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "integer|between:6,16" + messages = "请输入一个整数|参数大小不对啊老铁" + value = 5.66 + ) + + if err := g.Validator().Rules(rule).Messages(messages).Data(value).Run(ctx); err != nil { + g.Dump(err.Map()) + } +} +``` + +执行后,终端输出: + +``` +{ + "integer": "请输入一个整数", + "between": "参数大小不对啊老铁", +} +``` + +可以看到,多个规则以及多个自定义错误提示之间使用英文 `|` 号进行分割,注意自定义错误提示的顺序和多规则的顺序一一对应。 `messages` 参数除了支持 `string` 类型以外,还支持 `map[string]string` 类型,请看以下例子: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = "url|min-length:11" + value = "goframe.org" + messages = map[string]string{ + "url": "请输入正确的URL地址", + "min-length": "地址长度至少为{min}位", + } + ) + if err := g.Validator().Rules(rule).Messages(messages).Data(value).Run(ctx); err != nil { + g.Dump(err.Map()) + } +} +``` + +执行后,终端输出: + +``` +{ + "url": "请输入正确的URL地址", +} +``` + +## 使用自定义正则校验数据格式,使用默认错误提示 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + rule = `regex:\d{6,}|\D{6,}|max-length:16` + ) + + if err := g.Validator().Rules(rule).Data(`123456`).Run(ctx); err != nil { + fmt.Println(err) + } + + if err := g.Validator().Rules(rule).Data(`abcde6`).Run(ctx); err != nil { + fmt.Println(err) + } +} +``` + +执行后,终端输出: + +``` +The value `abcde6` must be in regex of: \d{6,}|\D{6,} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..c8d4087e9a1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\202\346\225\260\347\261\273\345\236\213.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/core/gvalid-parameter-type' +title: '数据校验-参数类型' +sidebar_position: 3 +hide_title: true +keywords: [数据校验,参数类型,GoFrame,GoFrame框架,后端开发,参数验证,输入校验,编程框架,开发工具,验证模块] +description: '使用GoFrame框架进行数据校验的参数类型处理。本章节详细阐述了不同类型参数的数据验证方法,帮助开发者在后端开发中有效地处理和验证用户输入,提升应用程序的可靠性与安全性。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..f8c2434b1a1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\217\257\351\200\211\346\240\241\351\252\214.md" @@ -0,0 +1,161 @@ +--- +slug: '/docs/core/gvalid-optional-rule' +title: '数据校验-可选校验' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据校验,可选校验,校验规则,示例,校验组件,参数校验,编程,GoFrame框架] +description: '使用GoFrame框架进行数据校验,尤其是针对可选校验规则的使用。当校验规则中不包含required时,可选校验规则不会强制校验空字符串或nil值。文档提供了多个实际代码示例,展示了在Go语言中通过GoFrame框架实现可选数据校验的过程和注意事项,适合开发者在项目中应用。' +--- + +## 可选校验规则 + +当给定的数据校验规则中不包含 `required*` 规则时,表示该规则不是一个必须规则。如果当给定的值为 `nil` 或者 `空字符串` 时,将会忽略其校验。 + +## 示例1,空字符串 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId string `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +## 示例2,空指针属性 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId *gvar.Var `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +## 示例3,空整型属性 + +**需要注意的是,如果键值为 `0` 或者 `false`,参数值将仍然会被校验。** + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Params struct { + Page int `v:"required|min:1 # page is required"` + Size int `v:"required|between:1,100 # size is required"` + ProjectId int `v:"between:1,10000 # project id must between {min}, {max}"` + } + var ( + ctx = gctx.New() + obj = &Params{ + Page: 1, + Size: 10, + } + ) + err := g.Validator().Data(obj).Run(ctx) + fmt.Println(err) + + // Output: + // +} +``` + +执行后,终端输出: + +``` +project id must between 1, 10000 +``` + +## 示例4,通过 `map` 传参 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + params = map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules = []string{ + "passport@length:6,16", + "password@required|length:6,16|same:password2", + "password2@required|length:6,16", + } + ) + err := g.Validator().Rules(rules).Data(params).Run(ctx) + if err != nil { + g.Dump(err.Maps()) + } +} +``` + +需要注意,其中的 `passport` 键名并没有 `required` 规则,因此即便给定的 `passport` 参数为空字符串,不满足规则时,也并没有报错,因为校验组件将其看做可选校验规则。 + +执行后,终端输出: + +``` +{ + "password": { + "same": "The password value `123456` must be the same as field password2", + }, +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..0baca83e87a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/gvalid-faq' +title: '数据校验-常见问题' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据校验,Struct默认值,required规则,指针类型,组合校验规则,Assoc联合校验,API输入输出,Server] +description: '在使用GoFrame框架进行数据校验时,Struct默认值对required规则的影响及其解决方案,包括使用指针类型绕过默认值影响、组合校验规则以及Assoc联合校验方法,以确保校验准确性。' +--- + +## `Struct` 默认值对 `required` 规则的影响 + +`Struct` 的属性会有 `默认值`,在某些情况下会引起 `required` 规则的失效。例如: + +```go +type User struct { + Name string `v:"required"` + Age uint `v:"required"` +} +``` + +在该结构体校验中, `Age` 属性的 `required` 校验将会失效,因为 `Age` 即便没有输入也会有默认值 `0`。 + +这里有 **三种** 解决方案: + +1. 将属性改为指针类型,例如 `*int`、 `*float64`、 `*g.Var` 等,通过指针类型默认值为 `nil` 的特点绕过了这个问题。 +2. 使用组合校验规则来弥补默认值对 `required` 规则的影响,例如以上示例中将 `Age` 属性的校验规则修改为 `required|min:1` 将能达到业务校验的效果。 +3. 使用 `Struct` 校验的 `Assoc` 联合校验方法设置联合校验参数,在校验 `Struct` 类型参数时,参数值将以 `Assoc` 方法中给定的参数为准执行校验。如果使用框架的 `Server`,采用结构化的 `API` 输入输出( `XxxReq/XxxRes`),那么 `Server` 将会自动调用 `Assoc` 执行校验,开发者无需担心默认值的影响。文档链接: [Struct校验-Assoc关联](数据校验-参数类型/数据校验-Struct校验/Struct校验-Assoc关联.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..d637df58fd2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,505 @@ +--- +slug: '/docs/core/gvalid-funcs' +title: '数据校验-方法介绍' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame框架,Validator,数据校验,校验规则,自定义校验,I18N国际化,字段比较,校验方法,校验示例,错误提示] +description: 'GoFrame框架中的数据校验功能,详细描述了包括New、Run、Clone、I18n、Bail、Ci等常用校验方法的使用。通过具体示例讲解如何使用这些方法进行有效的数据校验,并提供了定制化校验规则和错误提示的方法,帮助开发者更好地完成应用程序的数据校验工作。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) +::: +## `New` + +- 说明: `New` 创建并返回一个 `Validator` 的新对象。 + +- 格式: + +```go +New() *Validator +``` + +- 示例: + +```go +func ExampleNew() { + validator := gvalid.New() + + if err := validator.Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Print(err) + } + + // Output: + // The value `16` must be equal or greater than 18 +} +``` + + +## `Run` + +- 说明: `Run` 对给定规则和信息的数据进行校验操作。 + +- 格式: + +```go +Run(ctx context.Context) Error +``` + +- 示例: + +```go +func ExampleValidator_Run() { + // check value mode + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println("check value err:", err) + } + // check map mode + data := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", + } + rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", + } + if err := g.Validator().Data(data).Rules(rules).Run(context.Background()); err != nil { + fmt.Println("check map err:", err) + } + // check struct mode + type Params struct { + Page int `v:"required|min:1"` + Size int `v:"required|between:1,100"` + ProjectId string `v:"between:1,10000"` + } + rules = map[string]string{ + "Page": "required|min:1", + "Size": "required|between:1,100", + "ProjectId": "between:1,10000", + } + obj := &Params{ + Page: 0, + Size: 101, + } + if err := g.Validator().Data(obj).Run(context.Background()); err != nil { + fmt.Println("check struct err:", err) + } + + // May Output: + // check value err: The value `16` must be equal or greater than 18 + // check map err: The passport field is required; The passport value `` length must be between 6 and 16; The password value `123456` must be the same as field password2 + // check struct err: The Page value `0` must be equal or greater than 1; The Size value `101` must be between 1 and 100 +} +``` + + +## `Clone` + +- 说明:Clone创建并返回一个当前Validator的值拷贝对象。 + +- 格式: + +``` +(v *Validator) Clone() *Validator +``` + +- 示例: + +```go +func ExampleValidator_Clone() { + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println(err) + } + + if err := g.Validator().Clone().Data(20).Run(context.Background()); err != nil { + fmt.Println(err) + } else { + fmt.Println("Check Success!") + } + + // Output: + // The value `16` must be equal or greater than 18 + // Check Success! +} +``` + + +## I18n + +- 说明: `I18n` 方法用于设置当前校验对象的 `I18N` 国际化组件。默认情况下,校验组件使用的是框架全局默认的 `i18n` 组件对象。 + +- 格式: + +```go +I18n(i18nManager *gi18n.Manager) *Validator +``` + +- 示例: + +```go +func ExampleValidator_I18n() { + var ( + i18nManager = gi18n.New() + ctxCn = gi18n.WithLanguage(context.Background(), "cn") + validator = gvalid.New() + ) + + validator = validator.Data(16).Rules("min:18") + + if err := validator.Run(context.Background()); err != nil { + fmt.Println(err) + } + + if err := validator.I18n(i18nManager).Run(ctxCn); err != nil { + fmt.Println(err) + } + + // Output: + // The value `16` must be equal or greater than 18 + // 字段值`16`字段最小值应当为18 +} +``` + + +## Bail + +- 说明: `Bail` 方法用于设定只要后续的多个校验中有一个规则校验失败则停止校验立即返回错误结果。 + +- 格式: + +```go +Bail() *Validator +``` + +- 示例: + +```go +func ExampleValidator_Bail() { + type BizReq struct { + Account string `v:"required|length:6,16|same:QQ"` + QQ string + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + QQ: "123456", + Password: "goframe.org", + Password2: "goframe.org", + } + ) + + if err := g.Validator().Bail().Data(req).Run(ctx); err != nil { + fmt.Println("Use Bail Error:", err) + } + + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println("Not Use Bail Error:", err) + } + + // output: + // Use Bail Error: The Account value `gf` length must be between 6 and 16 + // Not Use Bail Error: The Account value `gf` length must be between 6 and 16; The Account value `gf` must be the same as field QQ +} +``` + + +## `Ci` + +- 说明: `Ci` 方法用于设置需要比较数值的规则时,不区分字段的大小写。 + +- 格式: + +```go +Ci() *Validator +``` + +- 示例: + +```go +func ExampleValidator_Ci() { + + type BizReq struct { + Account string `v:"required"` + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + Password: "Goframe.org", // Diff from Password2, but because of "ci", rule check passed + Password2: "goframe.org", + } + ) + + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println("Not Use CI Error:", err) + } + + if err := g.Validator().Ci().Data(req).Run(ctx); err == nil { + fmt.Println("Use CI Passed!") + } + + // output: + // Not Use CI Error: The Password value `Goframe.org` must be the same as field Password2 + // Use CI Passed! +} +``` + + +## Data + +- 说明: `Data` 方法用于传递需要联合校验的数据。 + +- 格式: + +```go +Data(data interface{}) *Validator +``` + +- 示例: + +```go +func ExampleValidator_Data() { + type BizReq struct { + Password1 string `v:"password"` + Password2 string `v:"password"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "goframe", + Password2: "gofra", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Password2 value `gofra` is not a valid password format +} +``` + + +## Assoc + +- 说明:`Assoc` 是一个链式操作函数,为当前 `Validator` 设置验证的数据。参数 `assoc` 的类型通常是 `map`,指定了 `union validator` 中的 `map` 的值。 + +- 注意:使用非 `nil` 的 `assoc` 参数,会将 `useDataInsteadOfObjectAttributes` 属性设置为 `true`。 +- 格式: + +```go +Assoc(assoc interface{}) *Validator +``` + +- 示例: + +```go +func ExampleValidator_Assoc() { + + type User struct { + Name string `v:"required"` + Type int `v:"required"` + } + + data := g.Map{ + "name": "john", + } + + user := User{} + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + + if err := g.Validator().Data(user).Assoc(data).Run(context.Background()); err != nil { + fmt.Print(err) + } + + // Output: + // The Type field is required +} +``` + + +## Rules + +- 说明: `Rules` 方法用于传递当前链式操作校验的自定义校验规则。 + +- 格式: + +```go +Rules(rules interface{}) *Validator +``` + +- 示例: + +```go +func ExampleValidator_Rules() { + + if err := g.Validator().Data(16).Rules("min:18").Run(context.Background()); err != nil { + fmt.Println(err) + } + + // Output: + // The value `16` must be equal or greater than 18 +} +``` + + +## `Message` + +- 说明: `Messages` 方法用于传递当前链式操作校验的自定义错误提示信息。 + +- 格式: + +```go +Messages(messages interface{}) *Validator +``` + +- 示例: + +```go +func ExampleValidator_Messages() { + if err := g.Validator().Data(16).Rules("min:18").Messages("Can not regist, Age is less then 18!").Run(context.Background()); err != nil { + fmt.Println(err) + } + + // Output: + // Can not regist, Age is less then 18! +} +``` + + +## RuleFunc + +- 说明:`RuleFunc` 向当前的 `Validator` 注册一个自定义校验规则的函数。 + +- 格式: + +```go +RuleFunc(rule string, f RuleFunc) *Validator +``` + +- 示例: + +```go +func ExampleValidator_RuleFunc() { + var ( + ctx = context.Background() + lenErrRuleName = "LenErr" + passErrRuleName = "PassErr" + lenErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if len(pass) != 6 { + return errors.New(in.Message) + } + return nil + } + passErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if m := in.Data.Map(); m["data"] != pass { + return errors.New(in.Message) + } + return nil + } + ) + + type LenErrStruct struct { + Value string `v:"uid@LenErr#Value Length Error!"` + Data string `p:"data"` + } + + st := &LenErrStruct{ + Value: "123", + Data: "123456", + } + // single error sample + if err := g.Validator().RuleFunc(lenErrRuleName, lenErrRuleFunc).Data(st).Run(ctx); err != nil { + fmt.Println(err) + } + + type MultiErrorStruct struct { + Value string `v:"uid@LenErr|PassErr#Value Length Error!|Pass is not Same!"` + Data string `p:"data"` + } + + multi := &MultiErrorStruct{ + Value: "123", + Data: "123456", + } + // multi error sample + if err := g.Validator().RuleFunc(lenErrRuleName, lenErrRuleFunc).RuleFunc(passErrRuleName, passErrRuleFunc).Data(multi).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // Value Length Error! + // Value Length Error!; Pass is not Same! +} +``` + + +## RuleFuncMap + +- 说明:`RuleFuncMap` 向当前的 `Validator` 注册多个自定义校验规则的函数。 + +- 格式: + +```go +RuleFuncMap(m map[string]RuleFunc) *Validator +``` + +- 示例: + +```go +func ExampleValidator_RuleFuncMap() { + var ( + ctx = context.Background() + lenErrRuleName = "LenErr" + passErrRuleName = "PassErr" + lenErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if len(pass) != 6 { + return errors.New(in.Message) + } + return nil + } + passErrRuleFunc = func(ctx context.Context, in gvalid.RuleFuncInput) error { + pass := in.Value.String() + if m := in.Data.Map(); m["data"] != pass { + return errors.New(in.Message) + } + return nil + } + ruleMap = map[string]gvalid.RuleFunc{ + lenErrRuleName: lenErrRuleFunc, + passErrRuleName: passErrRuleFunc, + } + ) + + type MultiErrorStruct struct { + Value string `v:"uid@LenErr|PassErr#Value Length Error!|Pass is not Same!"` + Data string `p:"data"` + } + + multi := &MultiErrorStruct{ + Value: "123", + Data: "123456", + } + + if err := g.Validator().RuleFuncMap(ruleMap).Data(multi).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // Value Length Error!; Pass is not Same! +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..0c387f9a952 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\345\257\271\350\261\241.md" @@ -0,0 +1,148 @@ +--- +slug: '/docs/core/gvalid-validator' +title: '数据校验-校验对象' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据校验,校验对象,验证器,链式操作,国际化,自定义校验,错误提示,数据关联] +description: '使用GoFrame框架中的数据校验组件进行数据校验,包括校验对象的配置管理和链式操作等内容。通过详细的示例展示了单数据校验、结构体和映射的数据校验方法,帮助开发者快速掌握数据校验的使用技巧。' +--- + +## 校验对象 + +数据校验组件提供了数据校验对象,用于数据校验的统一的配置管理、便捷的链式操作。 + +**接口文档**: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +```go +type Validator + func New() *Validator + func (v *Validator) Assoc(assoc interface{}) *Validator + func (v *Validator) Bail() *Validator + func (v *Validator) Ci() *Validator + func (v *Validator) Clone() *Validator + func (v *Validator) Data(data interface{}) *Validator + func (v *Validator) I18n(i18nManager *gi18n.Manager) *Validator + func (v *Validator) Messages(messages interface{}) *Validator + func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator + func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator + func (v *Validator) Rules(rules interface{}) *Validator + func (v *Validator) Run(ctx context.Context) Error +``` + +**简要说明:** + +1. `New` 方法用于创建一个新的校验对象。 +2. `Assoc` 用于关联数据校验,具体请查看后续章节介绍。 +3. `Bail` 方法用于设定只要后续的多个校验中有一个规则校验失败则停止校验立即返回错误结果。 +4. `Ci` 方法用于设置需要比较数值的规则时,不区分字段的大小写。 +5. `Run` 方法对给定规则和信息的数据进行校验操作。 +6. `I18n` 方法用于设置当前校验对象的 `I18N` 国际化组件。默认情况下,校验组件使用的是框架全局默认的 `i18n` 组件对象。 +7. `Data` 方法用于传递需要联合校验的数据集合,往往传递的是 `map` 类型或者 `struct` 类型。 +8. `Rules` 方法用于传递当前链式操作校验的自定义校验规则,往往使用 `[]string` 类型或者 `map` 类型。 +9. `Messages` 方法用于传递当前链式操作校验的自定义错误提示信息,往往使用 `map` 类型传递,具体看后续代码示例。 +:::tip +由于数据校验对象也是一个非常常用的对象,因此 `g` 模块中定义了 `Validator` 方法来快捷创建校验对象。大部分场景下我们推荐使用 `g` 模块的 `g.Validator()` 方式来快捷创建一个校验对象,关于 `g` 模块的介绍请参考章节: [对象管理](../对象管理.md) +::: +## 使用示例 + +### 单数据校验 + +```go +var ( + err error + ctx = gctx.New() +) +err = g.Validator(). + Rules("min:18"). + Data(16). + Messages("未成年人不允许注册哟"). + Run(ctx) +fmt.Println(err.Error()) + +// Output: +// 未成年人不允许注册哟 +``` + +```go +var ( + err error + ctx = gctx.New() + data = g.Map{ + "password": "123", + } +) + +err = g.Validator().Data("").Assoc(data). + Rules("required-with:password"). + Messages("请输入确认密码"). + Run(ctx) + +fmt.Println(err.Error()) +``` + +### `Struct` 数据校验 + +```go +type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required#请选择用户类型"` +} +var ( + err error + ctx = gctx.New() + user = User{} + data = g.Map{ + "name": "john", + } +) +if err = gconv.Scan(data, &user); err != nil { + panic(err) +} +err = g.Validator().Assoc(data).Data(user).Run(ctx) +if err != nil { + fmt.Println(err.(gvalid.Error).Items()) +} + +// Output: +// [map[Type:map[required:请选择用户类型]]] +``` + +### `Map` 数据校验 + +```go +params := map[string]interface{}{ + "passport": "", + "password": "123456", + "password2": "1234567", +} +rules := map[string]string{ + "passport": "required|length:6,16", + "password": "required|length:6,16|same:password2", + "password2": "required|length:6,16", +} +messages := map[string]interface{}{ + "passport": "账号不能为空|账号长度应当在{min}到{max}之间", + "password": map[string]string{ + "required": "密码不能为空", + "same": "两次密码输入不相等", + }, +} +err := g.Validator().Messages(messages).Rules(rules).Data(params).Run(gctx.New()) +if err != nil { + g.Dump(err.Maps()) +} +``` + +执行后,终端输出: + +``` +{ + "passport": { + "length": "账号长度应当在6到16之间", + "required": "账号不能为空" + }, + "password": { + "same": "两次密码输入不相等" + } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" new file mode 100644 index 00000000000..6120977e678 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\347\273\223\346\236\234.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/gvalid-result-handling' +title: '数据校验-校验结果' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame框架,数据校验,校验结果,Error接口,gvalid.Error,GoFrame v2,校验方法,错误信息,gerror.Current,顺序性校验] +description: '利用GoFrame框架进行数据校验,描述了gvalid.Error接口的实现及使用方法。通过不同方法获取校验错误信息,包括顺序性和非顺序性校验的影响。同时,示例演示如何获取校验错误中的第一条错误信息,为开发者提供实用指引。' +--- + +## 基本介绍 + +校验结果为一个 `error` 错误对象,内部使用 `gvalid.Error` 对象实现。当数据规则校验成功时,校验方法返回的结果为 `nil`。当数据规则校验失败时,返回的该对象是包含结构化的层级 `map`,包含多个字段及其规则及对应错误信息,以便于接收端能够准确定位错误规则。相关数据结构及方法如下: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +```go +type Error interface { + Code() gcode.Code + Current() error + Error() string + FirstItem() (key string, messages map[string]error) + FirstRule() (rule string, err error) + FirstError() (err error) + Items() (items []map[string]map[string]error) + Map() map[string]error + Maps() map[string]map[string]error + String() string + Strings() (errs []string) +} +``` + +可以结合后续的示例理解这个数据结构。我们可以通过 `Maps()` 方法获得该原始错误信息数据结构 `map`。但在大多数时候我们可以通过 `Error` 接口的其他方法来方便地获取特定的错误信息。 +:::tip +大多数情况下,我们不关心具体出错的校验规则,可以使用 `Error/String` 方法直接返回所有的错误信息即可。大部分的方法获取错误信息时根据校验规则的顺序性与否,返回的结果顺序会不太一样。 +::: +**简要说明:** + +获取校验结果的值可以通过多个校验结果方法获取,为让各位开发者有充分的理解,详细说明以下: + +| 方法 | 说明 | +| --- | --- | +| `Code` | 常用方法。实现 `gerror` 的 `Code` 接口,在校验组件中,该方法固定返回错误码 `gcode.CodeValidationFailed`。 | +| `Error` | 常用方法。实现标准库的 `error.Error` 接口,获取返回所有校验错误组成的错误字符串。内部逻辑同 `String` 方法。 | +| `Current` | 常用方法。实现了 `gerror` 的 `Current` 接口,用于获取校验错误中的第一条错误对象。 | +| `Items` | 在顺序性校验中将会按照校验规则顺序返回校验错误数组。其顺序性只有在顺序校验时有效,否则返回的结果是随机的。 | +| `Map` | 返回 `FirstItem` 中得出错自规则及对应错误信息 `map`。 | +| `Maps` | 返回所有的出错键名及对应的出错规则及对应的错误信息( `map[string]map[string]error`)。 | +| `String` | 返回所有的错误信息,构成一条字符串返回,多个规则错误信息之间以 `;` 符号连接。其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的。 | +| `Strings` | 返回所有的错误信息,构成 `[]string` 类型返回。其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的。 | +| `FirstItem` | 在有多个键名/属性校验错误的时候,用以获取出错的第一个键名,以及其对应的出错规则和错误信息。其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的。 | +| `FirstRule` | 返回 `FirstItem` 中得第一条出错的规则及错误信息。其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的。 | +| `FirstString` | 返回 `FirstRule` 中得第一条规则错误信息。其顺序性只有使用顺序校验规则时有效,否则返回的结果是随机的。 | + +## `gerror.Current` 支持 + +我们可以看到, `gvalid.Error` 实现了 `Current() error` 接口,因此可以通过 `gerror.Current` 方法获取它的第一条错误信息,这在接口校验失败时返回错误信息非常方便。我们来看一个示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" +) + +func main() { + type User struct { + Name string `v:"required#请输入用户姓名"` + Type int `v:"required|min:1#|请选择用户类型"` + } + var ( + err error + ctx = gctx.New() + user = User{} + ) + if err = g.Validator().Data(user).Run(ctx); err != nil { + g.Dump(err.(gvalid.Error).Maps()) + g.Dump(gerror.Current(err)) + } +} +``` + +这里使用了 `gerror.Current(err)` 来获取校第一条验错误信息。执行后,终端输出: + +``` +{ + "Name": { + "required": "请输入用户姓名", + }, + "Type": { + "min": "请选择用户类型", + }, +} +"请输入用户姓名" +``` +:::warning +需要注意的是,数据校验时存在 **顺序性校验** 和 **非顺序性校验**,这会对获取第一条错误信息的结果产生影响。关于顺序与非顺序性校验,具体可以参考后续章节介绍。 +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" new file mode 100644 index 00000000000..59b44a63582 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\346\240\241\351\252\214\350\247\204\345\210\231.md" @@ -0,0 +1,1969 @@ +--- +slug: '/docs/core/gvalid-rules' +title: '数据校验-校验规则' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,数据校验,校验规则,GoFrame框架,校验逻辑,格,参数校验,内置规则,联合校验,智能匹配] +description: 'GoFrame框架中的数据校验规则及其应用。GoFrame框架内置了多种常用的校验规则,支持开发者在处理数据时进行参数校验和结构体校验。文章详细讲解了不同情况下如何使用各种校验规则,包括必需参数、日期格式、数值范围、枚举值验证等,为开发者提供了灵活的校验机制。' +--- + +`GoFrame` 框架校验组件内置了数十项常用的校验规则,校验组件是开发者最频繁使用的框架核心组件之一。 +:::info +校验规则涉及到联合校验的场景时,规则中关联的 **参数名称** 会自动按照不区分大小写且忽略特殊字符的形式进行智能匹配。以下示例代码中我们均采用常用的结构体校验的方式,结构体属性中的 `v` 标签标识 `validation` 的缩写,用于设置该属性的校验规格。 +::: +## 修饰规则 + +修饰规则本身没有任何的校验逻辑,而是修改后续功能规则的实现逻辑。 + +### `ci` + +- 格式:`ci` +- 说明:字段值比较在默认情况下为 **区分大小写** 严格匹配比较,通过 `ci(Case Insensitive)` 修饰规则,可以设置后续需要比较值的规则字段为 **不区分大小写**。如: `same`, `different`, `in`, `not-in` 等。 +- 示例: + + ```go + func Example_Rule_CaseInsensitive() { + type BizReq struct { + Account string `v:"required"` + Password string `v:"required|ci|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + Password: "Goframe.org", // Diff from Password2, but because of "ci", rule check passed + Password2: "goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // output: + } + ``` + + +### `bail` + +需要特别注意:如果参数校验存在多条校验规则,并且规则中存在 `required*` 规则和 `bail` 修饰规则一起使用时,那么建议将 `required*` 校验规则放置于所有规则之前,否则 `bail` 校验规则的特性(校验失败即停止后续校验)有可能会引起后续规则的 `required*` 规则不生效。 + +- 格式:`bail` +- 说明:只要后续的多个校验中有一个规则校验失败则停止校验并立即返回校验结果。 +- 注意:在框架的 `HTTP Server` 组件中,如果采用规范路由注册方式,在其自动校验特性中将会自动开启 `bail` 修饰规则,开发者无需在 `Req` 对象中显式设置 `bail`。 +- 示例: + + ```go + func Example_Rule_Bail() { + type BizReq struct { + Account string `v:"bail|required|length:6,16|same:QQ"` + QQ string + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Account: "gf", + QQ: "123456", + Password: "goframe.org", + Password2: "goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // output: + // The Account value `gf` length must be between 6 and 16 + } + ``` + + +### `foreach` + +- 格式:`foreach` +- 说明:**用于数组参数**,将待检验的参数作为数组遍历,并将后一个校验规则应用于数组中的每一项。 +- 版本:框架版本 `>=v2.2.0` +- 示例: + + ```go + func Example_Rule_Foreach() { + type BizReq struct { + Value1 []int `v:"foreach|in:1,2,3"` + Value2 []int `v:"foreach|in:1,2,3"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: []int{1, 2, 3}, + Value2: []int{3, 4, 5}, + } + ) + if err := g.Validator().Bail().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `4` is not in acceptable range: 1,2,3 + } + ``` + + +## 功能规则 + +功能规则实现特定的校验逻辑,框架内置了非常丰富强大的内置校验规则。 + +### `required` + +- 格式:`required` +- 说明:必需参数,除了支持常见的字符串,也支持 `Slice/Map` 类型。 +- 示例:姓名字段 `Name` 为必需参数必需不能为空。 + + ```go + func Example_Rule_Required() { + type BizReq struct { + ID uint `v:"required"` + Name string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Name field is required + } + ``` + + +### `required-if` + +- 格式:`required-if:field,value,...` +- 说明:必需参数(当任意所给定字段值与所给值相等时,即:当 `field` 字段的值为 `value` 时,当前验证字段为必须参数)。多个字段以 `,` 号分隔。 +- 示例:当 `Gender` 字段为 `1` 时 `WifeName` 字段必须不为空, 当 `Gender` 字段为 `2` 时 `HusbandName` 字段必须不为空。 + + ```go + func Example_Rule_RequiredIf() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string `v:"required-if:gender,1"` + HusbandName string `v:"required-if:gender,2"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The WifeName field is required + } + ``` + +### `required-if-all` + +- 格式:`required-if-all:field1,value1,field2,value2,...` +- 说明:必需参数(当所有所给定字段值与所给值相等时,即:当`field1`字段的值为`value1`,同时`field2`字段的值为`value2`,以此类推,当前验证字段为必须参数)。多个字段以 `,` 号分隔。 +- 示例:当 `ID`字段为`1`,并且`Age`字段为`18`时,`MoreInfo`字段不能为空。 + + ```go + func ExampleRule_RequiredIfAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Age int `v:"required" dc:"Your age"` + MoreInfo string `v:"required-if-all:id,1,age,18" dc:"Your more info"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Age: 18, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The MoreInfo field is required + } + ``` + + +### `required-unless` + +- 格式:`required-unless:field,value,...` +- 说明:必需参数(当所给定字段值与所给值都不相等时,即:当 `field` 字段的值不为 `value` 时,当前验证字段为必须参数)。多个字段以 `,` 号分隔。 +- 示例:当 `Gender` 不等于 `0` 且 `Gender` 不等于 `2` 时, `WifeName` 必须不为空;当 `Id` 不等于 `0` 且 `Gender` 不等于 `2` 时, `HusbandName` 必须不为空。 + + ```go + func Example_Rule_RequiredUnless() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string `v:"required-unless:gender,0,gender,2"` + HusbandName string `v:"required-unless:id,0,gender,2"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The WifeName field is required; The HusbandName field is required + } + ``` + + + + +### `required-with` + +- 格式:`required-with:field1,field2,...` +- 说明:必需参数(当所给定任意字段值其中之一不为空时)。 +- 示例:当 `WifeName` 不为空时, `HusbandName` 必须不为空。 + + ```go + func Example_Rule_RequiredWith() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-with:WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + WifeName: "Ann", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-with-all` + +- 格式:`required-with-all:field1,field2,...` +- 说明:必须参数(当所给定所有字段值全部都不为空时)。 +- 示例:当 `Id,Name,Gender,WifeName` 全部不为空时, `HusbandName` 必须不为空。 + + ```go + func Example_Rule_RequiredWithAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-with-all:Id,Name,Gender,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + WifeName: "Ann", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-without` + +- 格式:`required-without:field1,field2,...` +- 说明:必需参数(当所给定任意字段值其中之一为空时)。 +- 示例:当 `Id` 或 `WifeName` 为空时, `HusbandName` 必须不为空。 + + ```go + func Example_Rule_RequiredWithout() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-without:Id,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `required-without-all` + +- 格式:`required-without-all:field1,field2,...` +- 说明:必须参数(当所给定所有字段值全部都为空时)。 +- 示例:当 `Id` 和 `WifeName` 都为空时, `HusbandName` 必须不为空。 + + ```go + func Example_Rule_RequiredWithoutAll() { + type BizReq struct { + ID uint `v:"required" dc:"Your ID"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + WifeName string + HusbandName string `v:"required-without-all:Id,WifeName"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "test", + Gender: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The HusbandName field is required + } + ``` + + +### `date` + +- 格式:`date` +- 说明:参数为常用日期类型,日期之间支持的连接符号 `-` 或 `/` 或 `.`,也支持不带连接符号的 `8` 位长度日期,格式如: `2006-01-02`, `2006/01/02`, `2006.01.02`, `20060102` +- 示例: + + ```go + func Example_Rule_Date() { + type BizReq struct { + Date1 string `v:"date"` + Date2 string `v:"date"` + Date3 string `v:"date"` + Date4 string `v:"date"` + Date5 string `v:"date"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-10-31", + Date2: "2021.10.31", + Date3: "2021-Oct-31", + Date4: "2021 Octa 31", + Date5: "2021/Oct/31", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date3 value `2021-Oct-31` is not a valid date + // The Date4 value `2021 Octa 31` is not a valid date + // The Date5 value `2021/Oct/31` is not a valid date + } + ``` + + +### `datetime` + +- 格式:`datetime` +- 说明:参数为常用日期时间类型,其中日期之间支持的连接符号只支持 `-`,格式如: `2006-01-02 12:00:00` +- 示例: + + ```go + func Example_Rule_Datetime() { + type BizReq struct { + Date1 string `v:"datetime"` + Date2 string `v:"datetime"` + Date3 string `v:"datetime"` + Date4 string `v:"datetime"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-11-01 23:00:00", + Date2: "2021-11-01 23:00", // error + Date3: "2021/11/01 23:00:00", // error + Date4: "2021/Dec/01 23:00:00", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date2 value `2021-11-01 23:00` is not a valid datetime + // The Date3 value `2021/11/01 23:00:00` is not a valid datetime + // The Date4 value `2021/Dec/01 23:00:00` is not a valid datetime + } + ``` + + +### `date-format` + +- 格式:`date-format:format` +- 说明:判断日期是否为指定的日期/时间格式, `format` 参数格式为 `gtime` 日期格式(可以包含日期及时间),格式说明参考章节: [gtime模块](../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) +- 示例: `date-format:Y-m-d H:i:s` + + ```go + func Example_Rule_DateFormat() { + type BizReq struct { + Date1 string `v:"date-format:Y-m-d"` + Date2 string `v:"date-format:Y-m-d"` + Date3 string `v:"date-format:Y-m-d H:i:s"` + Date4 string `v:"date-format:Y-m-d H:i:s"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Date1: "2021-11-01", + Date2: "2021-11-01 23:00", // error + Date3: "2021-11-01 23:00:00", + Date4: "2021-11-01 23:00", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Date2 value `2021-11-01 23:00` does not match the format: Y-m-d + // The Date4 value `2021-11-01 23:00` does not match the format: Y-m-d H:i:s + } + ``` + + +### `before` + +- 格式:`before:field` +- 说明:判断给定的日期/时间是否在指定字段的日期/时间之前。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_Before() { + type BizReq struct { + Time1 string `v:"before:Time3"` + Time2 string `v:"before:Time3"` + Time3 string + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-03", + Time3: "2022-09-03", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Time2 value `2022-09-03` must be before field Time3 value `2022-09-03` + } + ``` + + +### `before-equal` + +- 格式:`before-equal:field` +- 说明:判断给定的日期/时间是否在指定字段的日期/时间之前,或者与指定字段的日期/时间相等。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_BeforeEqual() { + type BizReq struct { + Time1 string `v:"before-equal:Time3"` + Time2 string `v:"before-equal:Time3"` + Time3 string + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-01", + Time3: "2022-09-01", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Time1 value `2022-09-02` must be before or equal to field Time3 + } + ``` + + +### `after` + +- 格式:`after:field` +- 说明:判断给定的日期/时间是否在指定字段的日期/时间之后。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_After() { + type BizReq struct { + Time1 string + Time2 string `v:"after:Time1"` + Time3 string `v:"after:Time1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-01", + Time2: "2022-09-01", + Time3: "2022-09-02", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Time2 value `2022-09-01` must be after field Time1 value `2022-09-01` + } + ``` + + +### `after-equal` + +- 格式:`after-equal:field` +- 说明:判断给定的日期/时间是否在指定字段的日期/时间之后,或者与指定字段的日期/时间相等。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_AfterEqual() { + type BizReq struct { + Time1 string + Time2 string `v:"after-equal:Time1"` + Time3 string `v:"after-equal:Time1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Time1: "2022-09-02", + Time2: "2022-09-01", + Time3: "2022-09-02", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Time2 value `2022-09-01` must be after or equal to field Time1 value `2022-09-02` + } + ``` + + +### `array` + +- 格式:`array` +- 说明:判断给定的参数是否数组格式。如果给定的参数为 `JSON` 数组字符串,也将检验通过。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_Array() { + type BizReq struct { + Value1 string `v:"array"` + Value2 string `v:"array"` + Value3 string `v:"array"` + Value4 []string `v:"array"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: "1,2,3", + Value2: "[]", + Value3: "[1,2,3]", + Value4: []string{}, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Value1 value `1,2,3` is not of valid array type + } + ``` + + +### `enums` + +- 格式:`enums` +- 说明:校验提交的参数是否在字段类型的枚举值中。该规则需要结合 `gf gen enums` 命令一起使用,详情请参考: [枚举维护-gen enums](../../开发工具/代码生成-gen/枚举维护-gen%20enums.md) + + ```go + func ExampleRule_Enums() { + type Status string + const ( + StatusRunning Status = "Running" + StatusOffline Status = "Offline" + ) + type BizReq struct { + Id int `v:"required"` + Name string `v:"required"` + Status Status `v:"enums"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Id: 1, + Name: "john", + Status: Status("Pending"), + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // May Output: + // The Status value `Pending` should be in enums of: ["Running","Offline"] + } + ``` + + +### `email` + +- 格式:`email` +- 说明:`EMAIL` 邮箱地址格式。 + + ```go + func Example_Rule_Email() { + type BizReq struct { + MailAddr1 string `v:"email"` + MailAddr2 string `v:"email"` + MailAddr3 string `v:"email"` + MailAddr4 string `v:"email"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MailAddr1: "gf@goframe.org", + MailAddr2: "gf@goframe", // error + MailAddr3: "gf@goframe.org.cn", + MailAddr4: "gf#goframe.org", // error + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The MailAddr2 value `gf@goframe` is not a valid email address + // The MailAddr4 value `gf#goframe.org` is not a valid email address + } + ``` + + +### `phone` + +- 格式:`phone` +- 说明:大中国区手机号格式。 + + ```go + func Example_Rule_Phone() { + type BizReq struct { + PhoneNumber1 string `v:"phone"` + PhoneNumber2 string `v:"phone"` + PhoneNumber3 string `v:"phone"` + PhoneNumber4 string `v:"phone"` + } + + var ( + ctx = context.Background() + req = BizReq{ + PhoneNumber1: "13578912345", + PhoneNumber2: "11578912345", // error 11x not exist + PhoneNumber3: "11178912345", // error 171 not exit + PhoneNumber4: "1357891234", // error len must be 11 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The PhoneNumber2 value `11578912345` is not a valid phone number + // The PhoneNumber3 value `11178912345` is not a valid phone number + // The PhoneNumber4 value `1357891234` is not a valid phone number + } + ``` + + +### `phone-loose` + +- 格式:`phone` +- 说明:宽松的手机号验证,只要满足 `13、14、15、16、17、18、19` 开头的11位数字都可以通过验证。可用于非严格的业务场景。 + + ```go + func Example_Rule_PhoneLoose() { + type BizReq struct { + PhoneNumber1 string `v:"phone-loose"` + PhoneNumber2 string `v:"phone-loose"` + PhoneNumber3 string `v:"phone-loose"` + PhoneNumber4 string `v:"phone-loose"` + } + + var ( + ctx = context.Background() + req = BizReq{ + PhoneNumber1: "13578912345", + PhoneNumber2: "11578912345", // error 11x not exist + PhoneNumber3: "17178912345", + PhoneNumber4: "1357891234", // error len must be 11 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The PhoneNumber2 value `11578912345` is invalid + // The PhoneNumber4 value `1357891234` is invalid + } + ``` + + +### `telephone` + +- 格式:`telephone` +- 说明:大中国区座机电话号码,”XXXX-XXXXXXX”、”XXXX-XXXXXXXX”、”XXX-XXXXXXX”、”XXX-XXXXXXXX”、”XXXXXXX”、”XXXXXXXX”。 + + ```go + func Example_Rule_Telephone() { + type BizReq struct { + Telephone1 string `v:"telephone"` + Telephone2 string `v:"telephone"` + Telephone3 string `v:"telephone"` + Telephone4 string `v:"telephone"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Telephone1: "010-77542145", + Telephone2: "0571-77542145", + Telephone3: "20-77542145", // error + Telephone4: "775421451", // error len must be 7 or 8 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Telephone3 value `20-77542145` is not a valid telephone number + // The Telephone4 value `775421451` is not a valid telephone number + } + ``` + + +### `passport` + +- 格式:`passport` +- 说明:通用帐号规则( **字母开头,只能包含字母、数字和下划线,长度在6~18之间**)。 + + ```go + func Example_Rule_Passport() { + type BizReq struct { + Passport1 string `v:"passport"` + Passport2 string `v:"passport"` + Passport3 string `v:"passport"` + Passport4 string `v:"passport"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Passport1: "goframe", + Passport2: "1356666", // error starting with letter + Passport3: "goframe#", // error containing only numbers or underscores + Passport4: "gf", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Passport2 value `1356666` is not a valid passport format + // The Passport3 value `goframe#` is not a valid passport format + // The Passport4 value `gf` is not a valid passport format + } + ``` + + +### `password` + +- 格式:`password` +- 说明:通用密码规则( **任意可见字符,长度在6~18之间**)。 + + ```go + func Example_Rule_Password() { + type BizReq struct { + Password1 string `v:"password"` + Password2 string `v:"password"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "goframe", + Password2: "gofra", // error length between 6 and 18 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + } + ``` + + +### `password2` + +- 格式:`password2` +- 说明:中等强度密码( **在通用密码规则的基础上,要求密码必须包含大小写字母和数字**)。 + + ```go + func Example_Rule_Password2() { + type BizReq struct { + Password1 string `v:"password2"` + Password2 string `v:"password2"` + Password3 string `v:"password2"` + Password4 string `v:"password2"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "Goframe123", + Password2: "gofra", // error length between 6 and 18 + Password3: "Goframe", // error must contain lower and upper letters and numbers. + Password4: "goframe123", // error must contain lower and upper letters and numbers. + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + // The Password3 value `Goframe` is not a valid password format + // The Password4 value `goframe123` is not a valid password format + } + ``` + + +### `password3` + +- 格式:`password3` +- 说明:强等强度密码( **在通用密码规则的基础上,必须包含大小写字母、数字和特殊字符**)。 + + ```go + func Example_Rule_Password3() { + type BizReq struct { + Password1 string `v:"password3"` + Password2 string `v:"password3"` + Password3 string `v:"password3"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Password1: "Goframe123#", + Password2: "gofra", // error length between 6 and 18 + Password3: "Goframe123", // error must contain lower and upper letters, numbers and special chars. + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Password2 value `gofra` is not a valid password format + // The Password3 value `Goframe123` is not a valid password format + } + ``` + + +### `postcode` + +- 格式:`postcode` +- 说明:大中国区邮政编码规则。 + + ```go + func Example_Rule_Postcode() { + type BizReq struct { + Postcode1 string `v:"postcode"` + Postcode2 string `v:"postcode"` + Postcode3 string `v:"postcode"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Postcode1: "100000", + Postcode2: "10000", // error length must be 6 + Postcode3: "1000000", // error length must be 6 + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Postcode2 value `10000` is not a valid postcode format + // The Postcode3 value `1000000` is not a valid postcode format + } + ``` + + +### `resident-id` + +- 格式:  resident-id +- 说明:公民身份证号码。 + + ```go + func Example_Rule_ResidentId() { + type BizReq struct { + ResidentID1 string `v:"resident-id"` + } + + var ( + ctx = context.Background() + req = BizReq{ + ResidentID1: "320107199506285482", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The ResidentID1 value `320107199506285482` is not a valid resident id number + } + ``` + + +### `bank-card` + +- 格式:`bank-card` +- 说明:大中国区银行卡号校验。 + + ```go + func Example_Rule_BankCard() { + type BizReq struct { + BankCard1 string `v:"bank-card"` + } + + var ( + ctx = context.Background() + req = BizReq{ + BankCard1: "6225760079930218", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The BankCard1 value `6225760079930218` is not a valid bank card number + } + ``` + + +### `qq` + +- 格式:`qq` +- 说明:腾讯QQ号码规则。 + + ```go + func Example_Rule_QQ() { + type BizReq struct { + QQ1 string `v:"qq"` + QQ2 string `v:"qq"` + QQ3 string `v:"qq"` + } + + var ( + ctx = context.Background() + req = BizReq{ + QQ1: "389961817", + QQ2: "9999", // error >= 10000 + QQ3: "514258412a", // error all number + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The QQ2 value `9999` is not a valid QQ number + // The QQ3 value `514258412a` is not a valid QQ number + } + ``` + + +### `ip` + +- 格式:`ip` +- 说明:`IPv4/IPv6` 地址。 + + ```go + func Example_Rule_IP() { + type BizReq struct { + IP1 string `v:"ip"` + IP2 string `v:"ip"` + IP3 string `v:"ip"` + IP4 string `v:"ip"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "127.0.0.1", + IP2: "fe80::812b:1158:1f43:f0d1", + IP3: "520.255.255.255", // error >= 10000 + IP4: "ze80::812b:1158:1f43:f0d1", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The IP3 value `520.255.255.255` is not a valid IP address + // The IP4 value `ze80::812b:1158:1f43:f0d1` is not a valid IP address + } + ``` + + +### `ipv4` + +- 格式:`ipv4` +- 说明:`IPv4` 地址。 + + ```go + func Example_Rule_IPV4() { + type BizReq struct { + IP1 string `v:"ipv4"` + IP2 string `v:"ipv4"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "127.0.0.1", + IP2: "520.255.255.255", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The IP2 value `520.255.255.255` is not a valid IPv4 address + } + ``` + + +### `ipv6` + +- 格式:`ipv6` +- 说明:`IPv6` 地址。 + + ```go + func Example_Rule_IPV6() { + type BizReq struct { + IP1 string `v:"ipv6"` + IP2 string `v:"ipv6"` + } + + var ( + ctx = context.Background() + req = BizReq{ + IP1: "fe80::812b:1158:1f43:f0d1", + IP2: "ze80::812b:1158:1f43:f0d1", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The IP2 value `ze80::812b:1158:1f43:f0d1` is not a valid IPv6 address + } + ``` + + +### `mac` + +- 格式:`mac` +- 说明:`MAC` 地址。 + + ```go + func Example_Rule_Mac() { + type BizReq struct { + Mac1 string `v:"mac"` + Mac2 string `v:"mac"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Mac1: "4C-CC-6A-D6-B1-1A", + Mac2: "Z0-CC-6A-D6-B1-1A", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Mac2 value `Z0-CC-6A-D6-B1-1A` is not a valid MAC address + } + ``` + + +### `url` + +- 格式:`url` +- 说明:URL +- 示例:支持以 `http,https,ftp,file` 开头的地址。 + + ```go + func Example_Rule_Url() { + type BizReq struct { + URL1 string `v:"url"` + URL2 string `v:"url"` + URL3 string `v:"url"` + } + + var ( + ctx = context.Background() + req = BizReq{ + URL1: "http://goframe.org", + URL2: "ftp://goframe.org", + URL3: "ws://goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The URL3 value `ws://goframe.org` is not a valid URL address + } + ``` + + +### `domain` + +- 格式:`domain` +- 说明:域名 +- 示例:域名规则。 `xxx.yyy`(首位必须为字母)。 + + ```go + func Example_Rule_Domain() { + type BizReq struct { + Domain1 string `v:"domain"` + Domain2 string `v:"domain"` + Domain3 string `v:"domain"` + Domain4 string `v:"domain"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Domain1: "goframe.org", + Domain2: "a.b", + Domain3: "goframe#org", + Domain4: "1a.2b", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Domain3 value `goframe#org` is not a valid domain format + // The Domain4 value `1a.2b` is not a valid domain format + } + ``` + + +### `size` + +- 格式:`size:size` +- 说明:参数 **长度** 为 `size`(长度参数为整形),注意底层使用 `Unicode` 计算长度,因此中文一个汉字占 `1` 个长度单位。 + + ```go + func Example_Rule_Size() { + type BizReq struct { + Size1 string `v:"size:10"` + Size2 string `v:"size:5"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Size1: "goframe欢迎你", + Size2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Size2 value `goframe` length must be 5 + } + ``` + + +### `length` + +- 格式:`length:min,max` +- 说明:参数 **长度** 为 `min` 到 `max`(长度参数为整形),注意底层使用 `Unicode` 计算长度,因此中文一个汉字占 `1` 个长度单位。 + + ```go + func Example_Rule_Length() { + type BizReq struct { + Length1 string `v:"length:5,10"` + Length2 string `v:"length:10,15"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Length1: "goframe欢迎你", + Length2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Length2 value `goframe` length must be between 10 and 15 + } + ``` + + +### `min-length` + +- 格式:`min-length:min` +- 说明:参数 **长度** 最小为 `min`(长度参数为整形),注意底层使用 `Unicode` 计算长度,因此中文一个汉字占 `1` 个长度单位。 + + ```go + func Example_Rule_MinLength() { + type BizReq struct { + MinLength1 string `v:"min-length:10"` + MinLength2 string `v:"min-length:8"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MinLength1: "goframe欢迎你", + MinLength2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The MinLength2 value `goframe` length must be equal or greater than 8 + } + ``` + + +### `max-length` + +- 格式:`max-length:max` +- 说明:参数 **长度** 最大为 `max`(长度参数为整形),注意底层使用 `Unicode` 计算长度,因此中文一个汉字占 `1` 个长度单位。 + + ```go + func Example_Rule_MaxLength() { + type BizReq struct { + MaxLength1 string `v:"max-length:10"` + MaxLength2 string `v:"max-length:5"` + } + + var ( + ctx = context.Background() + req = BizReq{ + MaxLength1: "goframe欢迎你", + MaxLength2: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The MaxLength2 value `goframe` length must be equal or lesser than 5 + } + ``` + + +### `between` + +- 格式:`between:min,max` +- 说明:参数 **大小** 为 `min` 到 `max`(支持整形和浮点类型参数)。 + + ```go + func Example_Rule_Between() { + type BizReq struct { + Age1 int `v:"between:1,100"` + Age2 int `v:"between:1,100"` + Score1 float32 `v:"between:0,10"` + Score2 float32 `v:"between:0,10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 50, + Age2: 101, + Score1: 9.8, + Score2: -0.5, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age2 value `101` must be between 1 and 100 + // The Score2 value `-0.5` must be between 0 and 10 + } + ``` + + +### `min` + +- 格式:`min:min` +- 说明:参数 **大小** 最小为 `min`(支持整形和浮点类型参数)。 + + ```go + func Example_Rule_Min() { + type BizReq struct { + Age1 int `v:"min:100"` + Age2 int `v:"min:100"` + Score1 float32 `v:"min:10"` + Score2 float32 `v:"min:10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 50, + Age2: 101, + Score1: 9.8, + Score2: 10.1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age1 value `50` must be equal or greater than 100 + // The Score1 value `9.8` must be equal or greater than 10 + } + ``` + + +### `max` + +- 格式:`max:max` +- 说明:参数 **大小** 最大为 `max`(支持整形和浮点类型参数)。 + + ```go + func Example_Rule_Max() { + type BizReq struct { + Age1 int `v:"max:100"` + Age2 int `v:"max:100"` + Score1 float32 `v:"max:10"` + Score2 float32 `v:"max:10"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Age1: 99, + Age2: 101, + Score1: 9.9, + Score2: 10.1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Age2 value `101` must be equal or lesser than 100 + // The Score2 value `10.1` must be equal or lesser than 10 + } + ``` + + +### `json` + +- 格式:`json` +- 说明:判断数据格式为 `JSON`。 + + ```go + func Example_Rule_Json() { + type BizReq struct { + JSON1 string `v:"json"` + JSON2 string `v:"json"` + } + + var ( + ctx = context.Background() + req = BizReq{ + JSON1: "{\"name\":\"goframe\",\"author\":\"郭强\"}", + JSON2: "{\"name\":\"goframe\",\"author\":\"郭强\",\"test\"}", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The JSON2 value `{"name":"goframe","author":"郭强","test"}` is not a valid JSON string + } + ``` + + +### `integer` + +- 格式:`integer` +- 说明:整数(正整数或者负整数)。 + + ```go + func Example_Rule_Integer() { + type BizReq struct { + Integer string `v:"integer"` + Float string `v:"integer"` + Str string `v:"integer"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Integer: "100", + Float: "10.0", + Str: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Float value `10.0` is not an integer + // The Str value `goframe` is not an integer + } + ``` + + +### `float` + +- 格式:`float` +- 说明:浮点数。 + + ```go + func Example_Rule_Float() { + type BizReq struct { + Integer string `v:"float"` + Float string `v:"float"` + Str string `v:"float"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Integer: "100", + Float: "10.0", + Str: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(err) + } + + // Output: + // The Str value `goframe` is invalid + } + ``` + + +### `boolean` + +- 格式:`boolean` +- 说明:布尔值( `1`, `true`, `on`, `yes` 为 `true` \| `0`, `false`, `off`, `no`, `""` 为 `false`)。 + + ```go + func Example_Rule_Boolean() { + type BizReq struct { + Boolean bool `v:"boolean"` + Integer int `v:"boolean"` + Float float32 `v:"boolean"` + Str1 string `v:"boolean"` + Str2 string `v:"boolean"` + Str3 string `v:"boolean"` + } + + var ( + ctx = context.Background() + req = BizReq{ + Boolean: true, + Integer: 1, + Float: 10.0, + Str1: "on", + Str2: "", + Str3: "goframe", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Float value `10` field must be true or false + // The Str3 value `goframe` field must be true or false + } + ``` + + +### `same` + +- 格式:`same:field` +- 说明:参数值必需与 `field` 字段参数的值相同。 +- 示例:在用户注册时,提交密码 `Password` 和确认密码 `Password2` 必须相等(服务端校验)。 + + ```go + func Example_Rule_Same() { + type BizReq struct { + Name string `v:"required"` + Password string `v:"required|same:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + Password: "goframe.org", + Password2: "goframe.net", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Password value `goframe.org` must be the same as field Password2 + } + ``` + + +### `different` + +- 格式:`different:field` +- 说明:参数值不能与 `field` 字段参数的值相同。 +- 示例:备用邮箱 `OtherMailAddr` 和邮箱地址 `MailAddr` 必须不相同。 + + ```go + func Example_Rule_Different() { + type BizReq struct { + Name string `v:"required"` + MailAddr string `v:"required"` + ConfirmMailAddr string `v:"required|different:MailAddr"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + MailAddr: "gf@goframe.org", + ConfirmMailAddr: "gf@goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The ConfirmMailAddr value `gf@goframe.org` must be different from field MailAddr + } + ``` + + +### `eq` + +- 格式:`eq:field` +- 说明:参数值必需与 `field` 字段参数的值相同。 `same` 规则的别名,功能同 `same` 规则。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_EQ() { + type BizReq struct { + Name string `v:"required"` + Password string `v:"required|eq:Password2"` + Password2 string `v:"required"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + Password: "goframe.org", + Password2: "goframe.net", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Password value `goframe.org` must be equal to field Password2 value `goframe.net` + } + ``` + + +### `not-eq` + +- 格式:`not-eq:field` +- 说明:参数值必需与 `field` 字段参数的值不相同。 `different` 规则的别名,功能同 `different` 规则。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_NotEQ() { + type BizReq struct { + Name string `v:"required"` + MailAddr string `v:"required"` + OtherMailAddr string `v:"required|not-eq:MailAddr"` + } + var ( + ctx = context.Background() + req = BizReq{ + Name: "gf", + MailAddr: "gf@goframe.org", + OtherMailAddr: "gf@goframe.org", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The OtherMailAddr value `gf@goframe.org` must not be equal to field MailAddr value `gf@goframe.org` + } + ``` + + +### `gt` + +- 格式:`gt:field` +- 说明:参数值必需大于给定字段对应的值。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_GT() { + type BizReq struct { + Value1 int + Value2 int `v:"gt:Value1"` + Value3 int `v:"gt:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 1, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `1` must be greater than field Value1 value `1` + } + ``` + + +### `gte` + +- 格式:`gte:field` +- 说明:参数值必需大于或等于给定字段对应的值。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_GTE() { + type BizReq struct { + Value1 int + Value2 int `v:"gte:Value1"` + Value3 int `v:"gte:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 2, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value2 value `1` must be greater than or equal to field Value1 value `2` + } + ``` + + +### `lt` + +- 格式:`lt:field` +- 说明:参数值必需小于给定字段对应的值。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_LT() { + type BizReq struct { + Value1 int + Value2 int `v:"lt:Value1"` + Value3 int `v:"lt:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 2, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value3 value `2` must be lesser than field Value1 value `2` + } + ``` + + +### `lte` + +- 格式:`lte:field` +- 说明:参数值必需小于或等于给定字段对应的值。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_LTE() { + type BizReq struct { + Value1 int + Value2 int `v:"lte:Value1"` + Value3 int `v:"lte:Value1"` + } + var ( + ctx = context.Background() + req = BizReq{ + Value1: 1, + Value2: 1, + Value3: 2, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err.String()) + } + + // Output: + // The Value3 value `2` must be lesser than or equal to field Value1 value `1` + } + ``` + + +### `in` + +- 格式:`in:value1,value2,...` +- 说明:参数值应该在 `value1,value2,...` 中(字符串匹配)。 +- 示例:性别字段 `Gender` 的值必须在 `0/1/2` 中。 + + ```go + func Example_Rule_In() { + type BizReq struct { + ID uint `v:"required" dc:"Your Id"` + Name string `v:"required" dc:"Your name"` + Gender uint `v:"in:0,1,2" dc:"0:Secret;1:Male;2:Female"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + Gender: 3, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The Gender value `3` is not in acceptable range: 0,1,2 + } + ``` + + +### `not-in` + +- 格式:`not-in:value1,value2,...` +- 说明:参数值不应该在 `value1,value2,...` 中(字符串匹配)。 +- 示例:无效索引 `InvalidIndex` 的值必须不在 `-1/0/1` 中。 + + ```go + func Example_Rule_NotIn() { + type BizReq struct { + ID uint `v:"required" dc:"Your Id"` + Name string `v:"required" dc:"Your name"` + InvalidIndex uint `v:"not-in:-1,0,1"` + } + var ( + ctx = context.Background() + req = BizReq{ + ID: 1, + Name: "test", + InvalidIndex: 1, + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Println(err) + } + + // Output: + // The InvalidIndex value `1` must not be in range: -1,0,1 + } + ``` + + +### `regex` + +- 格式:`regex:pattern` +- 说明:参数值应当满足正则匹配规则 `pattern` + + ```go + func Example_Rule_Regex() { + type BizReq struct { + Regex1 string `v:"regex:[1-9][0-9]{4,14}"` + Regex2 string `v:"regex:[1-9][0-9]{4,14}"` + Regex3 string `v:"regex:[1-9][0-9]{4,14}"` + } + var ( + ctx = context.Background() + req = BizReq{ + Regex1: "1234", + Regex2: "01234", + Regex3: "10000", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Regex1 value `1234` must be in regex of: [1-9][0-9]{4,14} + // The Regex2 value `01234` must be in regex of: [1-9][0-9]{4,14} + } + ``` + + +### `not-regex` + +- 格式:`not-regex:pattern` +- 说明:参数值不应当满足正则匹配规则 `pattern`。 +- 版本:框架版本 `>=v2.2.0` + + ```go + func Example_Rule_NotRegex() { + type BizReq struct { + Regex1 string `v:"regex:\\d{4}"` + Regex2 string `v:"not-regex:\\d{4}"` + } + var ( + ctx = context.Background() + req = BizReq{ + Regex1: "1234", + Regex2: "1234", + } + ) + if err := g.Validator().Data(req).Run(ctx); err != nil { + fmt.Print(gstr.Join(err.Strings(), "\n")) + } + + // Output: + // The Regex2 value `1234` should not be in regex of: \d{4} + } + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" new file mode 100644 index 00000000000..5127347d1f7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/core/gvalid-custom-rules' +title: '数据校验-自定义规则' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,gvalid,自定义校验,数据校验,业务场景,校验规则,灵活性,可复用性,校验特性] +description: '在GoFrame框架中使用gvalid进行数据校验的自定义规则。通过灵活配置,开发者可以定义业务需要的校验标准,提升代码的复用性和适应性,为各种业务场景提供更强的校验能力,有效满足复杂的数据校验需求。' +--- + +虽然 `gvalid` 已经内置了常见的数十种校验规则,但是在部分业务场景下我们需要自定义校验规则,特别是一些可以重复使用的业务相关的校验规则。当然, `gvalid` 如此的强大和贴心,她已经为您考虑得如此周全。自定义校验规则可以实现灵活性强,可复用性高的校验特性。 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..41790d38162 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\345\256\214\346\225\264\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/core/gvalid-custom-rules-handle-input-parameters' +title: '自定义规则-完整数据校验' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,自定义规则,完整数据校验,UserCreateReq,校验组件,结构体校验,GoFrame框架,gvalid,数据校验,校验规则] +description: '在GoFrame框架中使用自定义规则对结构体进行完整数据校验。通过给结构体添加元数据g.Meta,能够注册和使用自定义校验规则,例如UserCreateReq,实现对创建用户请求的校验。示例代码演示了如何实现和应用自定义校验方法,以确保数据的唯一性和有效性。' +--- + +## 基本介绍 + +大家也许已经注意到,当我们给定一个 `struct` 时,我们的规则只能对其中的键值或者属性进行校验,如果我们想要通过规则完整校验 `struct` 这个对象时,居然无法注册校验组件的自定义校验规则。当然,我们的校验组件也支持直接校验当前的 `struct` 对象。我们来看一个例子,在这个例子中,我们需要对创建的用户请求进行完整的自定义校验,并注册一个 `UserCreateReq` 的校验规则来实现。 + +## 使用示例 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type UserCreateReq struct { + g.Meta `v:"UserCreateReq"` + Name string + Pass string +} + +func RuleUserCreateReq(ctx context.Context, in gvalid.RuleFuncInput) error { + var req *UserCreateReq + if err := in.Data.Scan(&req); err != nil { + return gerror.Wrap(err, `Scan data to UserCreateReq failed`) + } + // SELECT COUNT(*) FROM `user` WHERE `name` = xxx + count, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }).Where("name", req.Name).Count() + if err != nil { + return err + } + if count > 0 { + return gerror.Newf(`The name "%s" is already token by others`, req.Name) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + user = &UserCreateReq{ + Name: "john", + Pass: "123456", + } + ) + err := g.Validator().RuleFunc("UserCreateReq", RuleUserCreateReq).Data(user).Run(ctx) + fmt.Println(err) +} +``` + +可以看到,我们通过给结构体一个 `g.Meta` 嵌入的元数据,并绑定 `UserCreateReq` 的自定义规则, `g.Meta` 作为结构体的一部分,当我们通过 `CheckStruct` 校验该结构体对象时,便可以通过 `UserCreateReq` 来实现校验。 + +上面的例子,我们执行后,终端输出: + +``` +The name "john" is already token +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" new file mode 100644 index 00000000000..b158d6f10f3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231/\350\207\252\345\256\232\344\271\211\350\247\204\345\210\231-\350\247\204\345\210\231\346\263\250\345\206\214.md" @@ -0,0 +1,283 @@ +--- +slug: '/docs/core/gvalid-custom-rules-register' +title: '自定义规则-规则注册' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,自定义规则,数据校验,规则注册,全局校验规则,局部校验规则,i18n特性,validation,gvalid] +description: '在GoFrame框架下如何进行自定义规则注册及数据校验。详细讲解了自定义规则的定义方法、参数说明以及全局和局部规则的注册方式。通过示例代码展示了订单ID存在校验和用户唯一性校验两种常见场景的实现,使开发者能够灵活应用自定义校验规则功能。' +--- + +## 相关数据结构 + +自定义规则方法定义,以及对应的输入参数数据结构。 + +```go +// RuleFuncInput holds the input parameters that passed to custom rule function RuleFunc. +type RuleFuncInput struct { + // Rule specifies the validation rule string, like "required", "between:1,100", etc. + Rule string + + // Message specifies the custom error message or configured i18n message for this rule. + Message string + + // Value specifies the value for this rule to validate. + Value *gvar.Var + + // Data specifies the `data` which is passed to the Validator. It might be a type of map/struct or a nil value. + // You can ignore the parameter `Data` if you do not really need it in your custom validation rule. + Data *gvar.Var +} + +// RuleFunc is the custom function for data validation. +type RuleFunc func(ctx context.Context, in RuleFuncInput) error +``` + +方法参数简要说明: + +1. 上下文参数 `ctx` 是必须的。 +2. `RuleFuncInput` 数据结构说明: + - `Rule` 表示当前的校验规则,包含规则的参数,例如: `required`, `between:1,100`, `length:6` 等等。 + - `Message` 参数表示在校验失败后返回的校验错误提示信息。 + - `Value` 参数表示被校验的数据值,注意类型是一个 `*gvar.Var` 泛型,因此您可以传递任意类型的参数。 + - `Data` 参数表示校验时传递的参数,例如校验的是一个 `map` 或者 `struct` 时,往往在联合校验时有用。需要注意的是,这个值是运行时输入的,值可能是 `nil`。 +:::tip +自定义错误默认情况下已支持 `i18n` 特性,因此您只需要按照 `gf.gvalid.rule.自定义规则名称 ` 配置 `i18n` 转译信息即可,该信息在校验失败时会自动从 `i18n` 管理器获取后,通过 `Message` 参数传入给您注册的自定义校验方法中。 +::: +## 全局校验规则注册 + +自定义规则分为两种:全局规则注册和局部规则注册。 + +全局规则是全局生效的规则,注册之后无论是使用方法还是对象来执行数据校验都可以使用自定义的规则。 + +注册校验方法: + +```go +// RegisterRule registers custom validation rule and function for package. +func RegisterRule(rule string, f RuleFunc) { + customRuleFuncMap[rule] = f +} + +// RegisterRuleByMap registers custom validation rules using map for package. +func RegisterRuleByMap(m map[string]RuleFunc) { + for k, v := range m { + customRuleFuncMap[k] = v + } +} +``` + +您需要按照 `RuleFunc` 类型的方法定义,实现一个您需要的校验方法,随后使用 `RegisterRule` 注册到 `gvalid` 模块中全局管理。该注册逻辑往往是在程序初始化时执行。该方法在对数据进行校验时将会被自动调用,方法返回 `nil` 表示校验通过,否则应当返回一个非空的 `error` 类型值。 +:::warning +注意事项:自定义规则的注册方法不支持并发调用,您需要在程序启动时进行注册(例如在 `boot` 包中处理),无法在运行时动态注册,否则会产生并发安全问题。 +::: +### 示例1,订单ID存在校验 + +在电商业务中,当我们对订单进行操作时,可以通过自定义规则校验给定的订单ID是否存在,因此我们可以注册一个 `order-exist` 的全局规则来实现。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type Request struct { + OrderId int64 `v:"required|order-exist"` + ProductName string + Amount int64 + // ... +} + +func init() { + rule := "order-exist" + gvalid.RegisterRule(rule, RuleOrderExist) +} + +func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error { + // SELECT COUNT(*) FROM `order` WHERE `id` = xxx + count, err := g.Model("order"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", in.Value.Int64()). + Count() + if err != nil { + return err + } + if count == 0 { + return gerror.Newf(`invalid order id "%d"`, in.Value.Int64()) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + req = &Request{ + OrderId: 65535, + ProductName: "HikingShoe", + Amount: 10000, + } + ) + err := g.Validator().CheckStruct(ctx, req) + fmt.Println(err) +} +``` + +### 示例2,用户唯一性规则 + +在用户注册时,我们往往需要校验当前用户提交的名称/账号是否唯一,因此我们可以注册一个 `unique-name` 的全局规则来实现。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type User struct { + Id int + Name string `v:"required|unique-name#请输入用户名称|用户名称已被占用"` + Pass string `v:"required|length:6,18"` +} + +func init() { + rule := "unique-name" + gvalid.RegisterRule(rule, RuleUniqueName) +} + +func RuleUniqueName(ctx context.Context, in gvalid.RuleFuncInput) error { + var user *User + if err := in.Data.Scan(&user); err != nil { + return gerror.Wrap(err, `Scan data to user failed`) + } + // SELECT COUNT(*) FROM `user` WHERE `id` != xxx AND `name` != xxx + count, err := g.Model("user"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", user.Id). + WhereNot("name", user.Name). + Count() + if err != nil { + return err + } + if count > 0 { + if in.Message != "" { + return gerror.New(in.Message) + } + return gerror.Newf(`user name "%s" is already token by others`, user.Name) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + user = &User{ + Id: 1, + Name: "john", + Pass: "123456", + } + ) + err := g.Validator().CheckStruct(ctx, user) + fmt.Println(err) +} +``` + +## 局部校验规则注册 + +局部规则是仅在当前校验对象下生效规则,校验规则是注册到当前使用的链式操作流程中而不是全局中。 + +注册方法: + +```go +// RuleFunc registers one custom rule function to current Validator. +func (v *Validator) RuleFunc(rule string, f RuleFunc) *Validator + +// RuleFuncMap registers multiple custom rule functions to current Validator. +func (v *Validator) RuleFuncMap(m map[string]RuleFunc) *Validator +``` + +简要介绍: + +- `RuleFunc` 方法用于注册单个自定义校验规则到当前对象。 +- `RuleFuncMap` 方法用于注册多个自定义校验规则到当前对象。 + +使用示例: + +我们将上面其中一个例子改为局部校验规则注册。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gvalid" + "time" +) + +type Request struct { + OrderId int64 + ProductName string + Amount int64 + // ... +} + +func RuleOrderExist(ctx context.Context, in gvalid.RuleFuncInput) error { + // SELECT COUNT(*) FROM `order` WHERE `id` = xxx + count, err := g.Model("order"). + Ctx(ctx). + Cache(gdb.CacheOption{ + Duration: time.Hour, + Name: "", + Force: false, + }). + WhereNot("id", in.Value.Int64()). + Count() + if err != nil { + return err + } + if count == 0 { + return gerror.Newf(`invalid order id "%d"`, in.Value.Int64()) + } + return nil +} + +func main() { + var ( + ctx = gctx.New() + req = &Request{ + OrderId: 65535, + ProductName: "HikingShoe", + Amount: 10000, + } + ) + err := g.Validator().RuleFunc("order-exist", RuleOrderExist).Data(req).Run(ctx) + fmt.Println(err) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" new file mode 100644 index 00000000000..afdae980c7d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\350\207\252\345\256\232\344\271\211\351\224\231\350\257\257.md" @@ -0,0 +1,105 @@ +--- +slug: '/docs/core/gvalid-custom-validating-message' +title: '数据校验-自定义错误' +sidebar_position: 7 +hide_title: true +keywords: [数据校验,自定义错误,GoFrame,GoFrame框架,i18n,国际化,验证消息,中文错误提示,英文错误提示,错误提示配置] +description: '在使用GoFrame框架的数据校验组件时,自定义错误提示并支持i18n国际化功能。通过结合i18n组件,可以方便地设置不同语言的错误信息。文中详细说明了如何配置英文和中文的i18n文件,以及如何通过中间件进行错误提示语言的设置,帮助开发者更高效地进行国际化处理。' +--- + +数据校验组件支持 `i18n` 特性,内部使用了 `goframe` 框架统一的 `i18n` 组件实现。默认使用默认的 `i18n` 单例对象,即 `g.I18n()` 对象。 + +在进一步使用之前,关于 `i18n` 国际化功能配置及使用请参考章节: [I18N国际化](../I18N国际化/I18N国际化.md) + +## 配置示例 + +### 默认 `i18n` 错误提示 + +默认的英文国际化语言配置文件参考: [https://github.com/gogf/gf/tree/master/util/gvalid/i18n/en](https://github.com/gogf/gf/tree/master/util/gvalid/i18n/en) + +### 中文错误提示 + +我们提供了建议的中文 `i18n` 国际化语言配置文件: [https://github.com/gogf/gf/tree/master/util/gvalid/i18n/cn](https://github.com/gogf/gf/tree/master/util/gvalid/i18n/cn) + +### 默认错误提示 + +当在 `i18n` 中找不到对应规则的错误提示时,将会使用 `__default__` 配置的错误提示信息。往往使用在自定义规则中。 + +## 开发示例 + +我们通过中间件统一设置请求的错误提示 `i18n` 语言。 + +### 目录结构 +:::warning +注意工程目录结构,以便于默认的 `g.i18n()` 对象能自动读取配置。有相当一部分同学倒在了这里。 +::: +``` +├── main.go +└── i18n + ├── en.toml + └── zh-CN.toml +``` + +### `i18n` 文件 + +`en.toml` + +``` +"ReuiredUserName" = "Please input user name" +"ReuiredUserType" = "Please select user type" +``` + +`zh-CN.toml` + +``` +"ReuiredUserName" = "请输入用户名称" +"ReuiredUserType" = "请选择用户类型" +``` + +### 示例代码 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Name string `v:"required#ReuiredUserName"` + Type int `v:"required#ReuiredUserType"` + } + var ( + ctx = gctx.New() + data = g.Map{ + "name": "john", + } + user = User{} + ctxEn = gi18n.WithLanguage(ctx, "en") + ctxCh = gi18n.WithLanguage(ctx, "zh-CN") + ) + + if err := gconv.Scan(data, &user); err != nil { + panic(err) + } + // 英文 + if err := g.Validator().Assoc(data).Data(user).Run(ctxEn); err != nil { + g.Dump(err.String()) + } + // 中文 + if err := g.Validator().Assoc(data).Data(user).Run(ctxCh); err != nil { + g.Dump(err.String()) + } +} +``` + +执行后,终端输出: + +``` +Please select user type +请选择用户类型 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..eaacb502d1d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214-\351\200\222\345\275\222\346\240\241\351\252\214.md" @@ -0,0 +1,205 @@ +--- +slug: '/docs/core/gvalid-recursive-validating' +title: '数据校验-递归校验' +sidebar_position: 5 +hide_title: true +keywords: [数据校验,递归校验,GoFrame,GoFrame框架,嵌套校验,struct校验,map校验,slice校验,校验组件,GoFrame Validator] +description: 'GoFrame框架中数据校验组件的递归校验特性。通过示例代码展示了如何对结构体、切片及映射类型的数据进行嵌套校验,实现对复杂数据结构的有效验证。特别说明了在递归校验中空对象的处理方法,以及带有默认值的空对象将被视为已传递并被校验。' +--- + +校验组件支持强大的递归校验(嵌套校验)特性。如果给定的校验数据中的属性或者键值为 `struct/map/slice` 类型时,将会被自动执行递归校验。我们来看几个示例: + +## 示例1,递归校验: `struct` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +type SearchReq struct { + Key string `v:"required"` + Option SearchOption +} + +type SearchOption struct { + Page int `v:"min:1"` + Size int `v:"max:100"` +} + +func main() { + var ( + ctx = gctx.New() + req = SearchReq{ + Key: "GoFrame", + Option: SearchOption{ + Page: 1, + Size: 10000, + }, + } + ) + err := g.Validator().Data(req).Run(ctx) + fmt.Println(err) +} +``` + +执行后,终端输出: + +``` +The Size value `10000` must be equal or lesser than 100 +``` + +## 示例2,递归校验: `slice` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required#Student Name is required"` + Age int + } + type Teacher struct { + Name string + Students []Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "name": "john", + "students": `[{"age":2},{"name":"jack", "age":4}]`, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +执行后,终端输出: + +``` +Student Name is required +``` + +## 示例3,递归校验: `map` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required#Student Name is required"` + Age int + } + type Teacher struct { + Name string + Students map[string]Student + } + var ( + ctx = gctx.New() + teacher = Teacher{ + Name: "Smith", + Students: map[string]Student{ + "john": {Name: "", Age: 18}, + }, + } + ) + err := g.Validator().Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +执行后,终端输出: + +``` +Student Name is required +``` + +## 注意事项:空对象对递归校验的影响 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required"` + } + type Teacher struct { + Students Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "students": nil, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +执行后,终端输出: + +``` +Student Name is required +``` + +有同学可能会觉得奇怪,明明我都没有传递 `Student` 字段值,为什么还会递归校验 `Student` 结构体里面的 `Name` 字段?这是因为这里的 `Student` 属性是个空结构体,是带有默认值的( `Name` 默认值为空字符串)。递归校验里面,虽然 `Student` 不是必须参数,这个意思是你可以不传递,但是只要传递了就会按照里面属性的校验规则执行校验(带有默认值的空对象也算是有值)。可以对比和以下代码的差别: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type Student struct { + Name string `v:"required"` + } + type Teacher struct { + Students *Student + } + var ( + ctx = gctx.New() + teacher = Teacher{} + data = g.Map{ + "students": nil, + } + ) + err := g.Validator().Assoc(data).Data(teacher).Run(ctx) + fmt.Println(err) +} +``` + +和前一示例的唯一差异在于 `Student` 属性从结构体改为了结构体指针 `*Student`,这样该属性不是空对象便没有默认值了。执行后,终端输出位空,表示校验通过。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" new file mode 100644 index 00000000000..03c037a6fc2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\225\260\346\215\256\346\240\241\351\252\214/\346\225\260\346\215\256\346\240\241\351\252\214.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gvalid' +title: '数据校验' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据校验,表单校验,gvalid,校验规则,自定义校验,国际化,结构体属性校验,服务器自动化校验] +description: 'GoFrame框架中的gvalid组件,它是一个功能强大且灵活易扩展的数据和表单校验工具。gvalid组件提供了多种常用校验规则、支持多数据多规则校验、自定义错误信息、国际化处理等功能,使其成为Go语言中最强大的数据校验模块。' +--- + +## 基本介绍 + +`goframe` 框架提供了功能强大、使用便捷、灵活易扩展的数据/表单校验组件,由 `gvalid` 组件实现。 `gvalid` 组件实现了非常强大的数据校验功能,内置了数十种常用的校验规则,支持单数据多规则校验、多数据多规则批量校验、自定义错误信息、自定义正则校验、自定义校验规则注册、支持 `i18n` 国际化处理、支持 `struct tag` 规则及提示信息绑定等等特性,是目前功能最强大的 `Go` 数据校验模块。 +:::tip +数据校验设计的灵感来源于经典的 `PHP Laravel` 框架 [https://laravel.com/docs/8.x/validation](https://laravel.com/docs/8.x/validation) 感谢 `Laravel` ❤️ +::: +**使用方式**: + +```go +import "github.com/gogf/gf/v2/util/gvalid" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid](https://pkg.go.dev/github.com/gogf/gf/v2/util/gvalid) + +## 组件特性 + +`gvalid` 组件具有以下显著特性: + +- 内置数十种常见数据校验规则,支持大部分业务场景 +- 支持 `Server` 层及命令行组件自动化校验 +- 支持基础类型及复杂对象类型参数校验 +- 支持顺序校验、灵活的校验结果处理 +- 支持自定义校验错误提示 +- 支持结构体属性递归校验 +- 支持自定义校验规则 +- 支持 `I18n` 国际化特性 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" new file mode 100644 index 00000000000..edd075f7b0e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Context.md" @@ -0,0 +1,82 @@ +--- +slug: '/docs/core/glog-context' +title: '日志组件-Context' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,glog,日志组件,Context,CtxKeys,日志输出,OpenTelemetry,链路跟踪,Handler] +description: 'GoFrame框架中glog日志组件的使用,特别是如何通过Context上下文变量实现日志打印。文章详细讲解了自定义CtxKeys的配置和使用示例,并提供了链路跟踪支持的功能。此外,还涉及了日志Handler的实现,以帮助开发者更好地集成日志功能。' +--- + +从 `v2` 版本开始, `glog` 组件将 `ctx` 上下文变量作为日志打印的必需参数。 + +## 自定义 `CtxKeys` + +日志组件支持自定义的键值打印,通过 `ctx` 上下文变量中读取。 + +### 使用配置 + +```yaml +# 日志组件配置 +logger: + Level: "all" + Stdout: true + CtxKeys: ["RequestId", "UserId"] +``` + +其中 `CtxKeys` 用于配置需要从 `context.Context` 接口对象中读取并输出的键名。 + +### 日志输出 + +使用上述配置,然后在输出日志的时候,通过 `Ctx` 链式操作方法指定输出的 `context.Context` 接口对象,请注意 **不要使用自定义类型作为Key**,否则无法输出到日志文件中,例如: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var ctx = context.Background() + + // 可以直接使用String作为Key + ctx = context.WithValue(ctx, "RequestId", "123456789") + + // 如需将Key提取为公共变量,可以使用gctx.StrKey类型,或直接使用string类型 + const userIdKey gctx.StrKey = "UserId" // or const userIdKey = "UserId" + ctx = context.WithValue(ctx, userIdKey, "10000") + + // 不能自定义类型 + type notPrintKeyType string + const notPrintKey notPrintKeyType = "NotPrintKey" + ctx = context.WithValue(ctx, notPrintKey, "notPrintValue") // 不会打印 notPrintValue + + g.Log().Error(ctx, "runtime error") +} +``` + +执行后,终端输出: + +```html +2024-09-26 11:45:33.790 [ERRO] {123456789, 10000} runtime error +Stack: +1. main.main + /Users/teemo/GolandProjects/gogf/demo/main.go:24 + +``` + +### 日志示例 + +![](/markdown/d9b17863576dca859b0b13b98041130e.png) + +## 传递给 `Handler` + +如果开发者自定义了日志对象的 `Handler`,那么每个日志打印传递的 `ctx` 上下文变量将会传递给 `Handler` 中。关于日志 `Handler` 的介绍请参考章节: [日志组件-Handler](日志组件-Handler.md) + +## 链路跟踪支持 + +`glog` 组件支持 `OpenTelemetry` 标准的链路跟踪特性,该支持是内置的,无需开发者做任何设置,具体请参考章节: [服务链路跟踪](../../服务可观测性/服务链路跟踪/服务链路跟踪.md) + +![](/markdown/a6ade54c58ba067b6be203a6e17b15e5.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..2306b32dbb6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Flags\347\211\271\346\200\247.md" @@ -0,0 +1,54 @@ +--- +slug: '/docs/core/glog-flags' +title: '日志组件-Flags特性' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame,GoFrame框架,glog,日志组件,Flags特性,日志异步输出,调用行号信息,时间格式,日期时间,毫秒] +description: 'GoFrame框架中日志组件的Flags特性,说明如何通过不同的常量组合来控制日志输出的额外特性,包括异步输出、调用行号信息打印以及多种时间格式的选择。这些特性能够帮助开发者实现更灵活的日志记录和调试。' +--- + +`flags` 用于控制日志组件的额外特性开关,这些属性使用常量进行组合控制,包括: + +```go +F_ASYNC = 1 << iota // 开启日志异步输出 +F_FILE_LONG // 打印调用行号信息,完整绝对路径,例如:/a/b/c/d.go:23 +F_FILE_SHORT // 打印调用行号信息,仅打印文件名,例如:d.go:23,覆盖 F_FILE_LONG. +F_TIME_DATE // 打印当前日期,如:2009-01-23 +F_TIME_TIME // 打印当前时间,如:01:23:23 +F_TIME_MILLI // 打印当前时间+毫秒,如:01:23:23.675 +F_TIME_STD = F_TIME_DATE | F_TIME_MILLI // (默认)打印当前日期+时间+毫秒,如:2009-01-23 01:23:23.675 +``` + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.SetFlags(glog.F_TIME_TIME | glog.F_FILE_SHORT) + l.Print(ctx, "time and short line number") + l.SetFlags(glog.F_TIME_MILLI | glog.F_FILE_LONG) + l.Print(ctx, "time with millisecond and long line number") + l.SetFlags(glog.F_TIME_STD | glog.F_FILE_LONG) + l.Print(ctx, "standard time format and long line number") +} + +``` + +执行后,终端输出结果为: + +```html +PS C:\hailaz\test> go run .\main.go +16:05:35 main.go:13: time and short line number +16:05:35.108 C:/hailaz/test/main.go:15: time with millisecond and long line number +2022-01-05 16:05:35.109 C:/hailaz/test/main.go:17: standard time format and long line number + +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" new file mode 100644 index 00000000000..2a71493dcdd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Handler.md" @@ -0,0 +1,301 @@ +--- +slug: '/docs/core/glog-handler' +title: '日志组件-Handler' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,日志组件,Handler特性,日志处理,自定义输出,Json格式,Graylog服务,日志收集,glog,中间件设计] +description: '文章介绍了从v2.0版本开始,GoFrame框架的glog组件新增的可自定义日志处理的Handler特性。开发者可以通过Handler实现日志输出的自定义内容,例如将日志转为Json格式输出或者输出至第三方服务如Graylog。详细示例展示了如何在GoFrame框架中使用Handler进行日志处理。' +--- + +从 `v2.0` 版本开始, `glog` 组件提供了超级强大的、可自定义日志处理的 `Handler` 特性。 `Handler` 采用了中间件设计方式,开发者可以为日志对象注册多个处理 `Handler`,也可以在 `Handler` 中覆盖默认的日志组件处理逻辑。 + +## 相关定义 + +### `Handler` 方法定义 + +```go +// Handler is function handler for custom logging content outputs. +type Handler func(ctx context.Context, in *HandlerInput) +``` + +可以看到第二个参数为日志处理的日志信息,并且为指针类型,意味着在 `Handler` 中可以修改该参数的任意属性信息,并且修改后的内容将会传递给下一个 `Handler`。 + +### `Handler` 参数定义 + +```go +// HandlerInput is the input parameter struct for logging Handler. +type HandlerInput struct { + Logger *Logger // Current Logger object. + Buffer *bytes.Buffer // Buffer for logging content outputs. + Time time.Time // Logging time, which is the time that logging triggers. + TimeFormat string // Formatted time string, like "2016-01-09 12:00:00". + Color int // Using color, like COLOR_RED, COLOR_BLUE, etc. Eg: 34 + Level int // Using level, like LEVEL_INFO, LEVEL_ERRO, etc. Eg: 256 + LevelFormat string // Formatted level string, like "DEBU", "ERRO", etc. Eg: ERRO + CallerFunc string // The source function name that calls logging, only available if F_CALLER_FN set. + CallerPath string // The source file path and its line number that calls logging, only available if F_FILE_SHORT or F_FILE_LONG set. + CtxStr string // The retrieved context value string from context, only available if Config.CtxKeys configured. + TraceId string // Trace id, only available if OpenTelemetry is enabled. + Prefix string // Custom prefix string for logging content. + Content string // Content is the main logging content without error stack string produced by logger. + Values []any // The passed un-formatted values array to logger. + Stack string // Stack string produced by logger, only available if Config.StStatus configured. + IsAsync bool // IsAsync marks it is in asynchronous logging. +} +``` + +开发者有 **两种方式** 通过 `Handler` 自定义日志输出内容: + +- 一种是直接修改 `HandlerInput` 中的属性信息,然后继续执行 `in.Next(ctx)`,默认的日志输出逻辑会将 `HandlerInput` 中的属性打印为字符串输出。 +- 另一种是将日志内容写入到 `Buffer` 缓冲对象中即可,默认的日志输出逻辑如果发现 `Buffer` 已经存在日志内容将会忽略默认日志输出逻辑。 + +### `Handler` 注册到 `Logger` 方法 + +```go +// SetHandlers sets the logging handlers for current logger. +func (l *Logger) SetHandlers(handlers ...Handler) +``` + +## 使用示例 + +我们来看两个示例便于更快速了解 `Handler` 的使用。 + +### 示例1\. 将日志输出转换为 `Json` 格式输出 + +在本示例中,我们采用了前置中间件的设计,通过自定义 `Handler` 将日志内容输出格式修改为了 `JSON` 格式。 + +```go +package main + +import ( + "context" + "encoding/json" + "os" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gstr" +) + +// JsonOutputsForLogger is for JSON marshaling in sequence. +type JsonOutputsForLogger struct { + Time string `json:"time"` + Level string `json:"level"` + Content string `json:"content"` +} + +// LoggingJsonHandler is a example handler for logging JSON format content. +var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + jsonForLogger := JsonOutputsForLogger{ + Time: in.TimeFormat, + Level: gstr.Trim(in.LevelFormat, "[]"), + Content: gstr.Trim(in.Content), // 2.7以上版本用in.ValuesContent() + } + jsonBytes, err := json.Marshal(jsonForLogger) + if err != nil { + _, _ = os.Stderr.WriteString(err.Error()) + return + } + in.Buffer.Write(jsonBytes) + in.Buffer.WriteString("\n") + in.Next(ctx) +} + +func main() { + g.Log().SetHandlers(LoggingJsonHandler) + ctx := context.TODO() + g.Log().Debug(ctx, "Debugging...") + g.Log().Warning(ctx, "It is warning info") + g.Log().Error(ctx, "Error occurs, please have a check") +} +``` + +可以看到,我们可以在 `Handler` 中通过 `Buffer` 属性操作来控制输出的日志内容。如果在所有的前置中间件 `Handler` 处理后 `Buffer` 内容为空,那么继续 `Next` 执行后将会执行日志中间件默认的 `Handler` 逻辑。执行本示例的代码后,终端输出: + +```html +{"time":"2021-12-31 11:03:25.438","level":"DEBU","content":"Debugging..."} +{"time":"2021-12-31 11:03:25.438","level":"WARN","content":"It is warning info"} +{"time":"2021-12-31 11:03:25.438","level":"ERRO","content":"Error occurs, please have a check \nStack:\n1. main.main\n C:/hailaz/test/main.go:42"} +``` + +### 示例2\. 将内容输出到第三方日志搜集服务中 + +在本示例中,我们采用了后置中间件的设计,通过自定义 `Handler` 将日志内容输出一份到第三方 `graylog` 日志搜集服务中,并且不影响原有的日志输出处理。 + +> `Graylog` 是与 `ELK` 可以相提并论的一款集中式日志管理方案,支持数据收集、检索、可视化 `Dashboard`。在本示例中使用到了一个简单的第三方 `graylog` 客户端组件。 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + gelf "github.com/robertkowalski/graylog-golang" +) + +var grayLogClient = gelf.New(gelf.Config{ + GraylogPort: 80, + GraylogHostname: "graylog-host.com", + Connection: "wan", + MaxChunkSizeWan: 42, + MaxChunkSizeLan: 1337, +}) + +// LoggingGrayLogHandler is an example handler for logging content to remote GrayLog service. +var LoggingGrayLogHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + in.Next(ctx) + grayLogClient.Log(in.Buffer.String()) +} + +func main() { + g.Log().SetHandlers(LoggingGrayLogHandler) + ctx := context.TODO() + g.Log().Debug(ctx, "Debugging...") + g.Log().Warning(ctx, "It is warning info") + g.Log().Error(ctx, "Error occurs, please have a check") + glog.Print(ctx, "test log") +} +``` + +## 全局默认 `Handler` + +日志对象默认是没有设置任何的 `Handler`,从 `v2.1` 版本开始,框架提供了可以设置全局默认 `Handler` 的功能特性。全局默认 `Handler` 将对所有的使用该日志组件,并且没有自定义 `Handler` 的日志打印功能生效。同时,全局默认 `Handler` 将会影响日志包方法的日志打印行为。 + +开发者可以通过以下两个方法来设置和获取全局默认的 `Handler`。 + +```go +// SetDefaultHandler sets default handler for package. +func SetDefaultHandler(handler Handler) + +// GetDefaultHandler returns the default handler of package. +func GetDefaultHandler() Handler +``` + +需要注意,这种全局包配置的方法不是并发安全的,并且往往需要在项目启动逻辑最顶部执行。 + +使用示例,我们将项目所有的日志输出均采用 `JSON` 格式输出,以保证日志内容结构化并且每次日志输出都是单行,方便日志采集期采集日志: + +```go +package main + +import ( + "context" + "encoding/json" + "os" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gstr" +) + +// JsonOutputsForLogger is for JSON marshaling in sequence. +type JsonOutputsForLogger struct { + Time string `json:"time"` + Level string `json:"level"` + Content string `json:"content"` +} + +// LoggingJsonHandler is a example handler for logging JSON format content. +var LoggingJsonHandler glog.Handler = func(ctx context.Context, in *glog.HandlerInput) { + jsonForLogger := JsonOutputsForLogger{ + Time: in.TimeFormat, + Level: gstr.Trim(in.LevelFormat, "[]"), + Content: gstr.Trim(in.Content), // 2.7以上版本用in.ValuesContent() + } + jsonBytes, err := json.Marshal(jsonForLogger) + if err != nil { + _, _ = os.Stderr.WriteString(err.Error()) + return + } + in.Buffer.Write(jsonBytes) + in.Buffer.WriteString("\n") + in.Next(ctx) +} + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(LoggingJsonHandler) + + g.Log().Debug(ctx, "Debugging...") + glog.Warning(ctx, "It is warning info") + glog.Error(ctx, "Error occurs, please have a check") +} +``` + +执行后,终端输出: + +```html +{"time":"2022-06-20 10:51:50.235","level":"DEBU","content":"Debugging..."} +{"time":"2022-06-20 10:51:50.235","level":"WARN","content":"It is warning info"} +{"time":"2022-06-20 10:51:50.235","level":"ERRO","content":"Error occurs, please have a check"} +``` + +## 组件通用 `Handler` + +组件提供了一些通用性的日志 `Handler`,方便开发者使用,提高开发效率。 + +### `HandlerJson` + +该 `Handler` 可以将日志内容转换为 `Json` 格式打印。使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(glog.HandlerJson) + + g.Log().Debug(ctx, "Debugging...") + glog.Warning(ctx, "It is warning info") + glog.Error(ctx, "Error occurs, please have a check") +} +``` + +执行后,终端输出: + +```html +{"Time":"2022-06-20 20:04:04.725","Level":"DEBU","Content":"Debugging..."} +{"Time":"2022-06-20 20:04:04.725","Level":"WARN","Content":"It is warning info"} +{"Time":"2022-06-20 20:04:04.725","Level":"ERRO","Content":"Error occurs, please have a check","Stack":"1. main.main\n /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/main.go:16\n"} +``` + +### `HandlerStructure` + +该 `Handler` 可以将日志内容转换为结构化格式打印,主要是为了和 `Golang` 新版本的 `slog` 日志输出内容保持一致。需要注意,日志结构化打印的特性需要保证所有日志记录均采用结构化输出才更具意义。使用示例: + +```go +package main + +import ( + "context" + "net" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.SetDefaultHandler(glog.HandlerStructure) + + g.Log().Info(ctx, "caution", "name", "admin") + glog.Error(ctx, "oops", net.ErrClosed, "status", 500) +} + +``` + +执行后,终端输出: + +```html +Time="2023-11-23 21:00:08.671" Level=INFO Content=caution name=admin +Time="2023-11-23 21:00:08.671" Level=ERRO oops="use of closed network connection" status=500 Stack="1. main.main\n /Users/txqiangguo/Workspace/gogf/gf/example/.test/main.go:16\n" +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..d16a51d826f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-JSON\346\240\274\345\274\217.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/core/glog-json' +title: '日志组件-JSON格式' +sidebar_position: 7 +hide_title: true +keywords: [日志组件,JSON格式,GoFrame,GoFrame框架,glog,日志分析,结构体日志,日志输出,map参数,gjson] +description: '使用GoFrame框架中的glog组件以JSON格式输出日志,适合日志分析工具解析。您将学习如何通过map或struct参数实现JSON日志格式输出,以及结合gjson.MustEncode方法实现更复杂的JSON内容输出。' +--- + +`glog` 对日志分析工具非常友好,支持输出 `JSON` 格式的日志内容,以便于后期对日志内容进行解析分析。 + +## 使用 `map/struct` 参数 + +想要支持 `JSON` 数据格式的日志输出非常简单,给打印方法提供 `map`/ `struct` 类型参数即可。 + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Debug(ctx, g.Map{"uid": 100, "name": "john"}) + type User struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + g.Log().Debug(ctx, User{100, "john"}) +} +``` + +执行后,终端输出结果: + +```html +2019-06-02 15:28:52.653 [DEBU] {"name":"john","uid":100} +2019-06-02 15:28:52.653 [DEBU] {"uid":100,"name":"john"} +``` + +## 结合 `gjson.MustEncode` + +此外,也可以结合 `gjson.MustEncode来` 实现 `Json` 内容输出,例如: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + type User struct { + Uid int `json:"uid"` + Name string `json:"name"` + } + g.Log().Debugf(ctx, `user json: %s`, gjson.MustEncode(User{100, "john"})) +} +``` + +执行后,终端输出结果: + +```html +2022-04-25 18:09:45.029 [DEBU] user json: {"uid":100,"name":"john"} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..d58512ec555 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Rotate\347\211\271\346\200\247.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/glog-rotate' +title: '日志组件-Rotate特性' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame,GoFrame框架,glog,日志组件,日志切分,日志滚动,日志压缩,日志配置,文件格式,日志备份] +description: 'GoFrame框架中glog组件的日志滚动切分特性,包括通过设置日志文件名称按照日期输出、通过设置RotateSize和RotateExpire进行日志滚动切分、支持日志文件压缩与备份、配置示例,以及注意事项等内容。提供详细的配置项说明和示例代码,帮助开发者更好地管理日志文件。' +--- +:::warning +滚动切分目前属于实验性特性,如有问题请随时反馈。 +::: +之前的章节中我们知道, `glog` 组件支持通过设置日志文件名称的方式,使得日志文件按照日期进行输出。从 `GoFrame v1.12` 版本开始, `glog` 组件也支持对日志文件进行滚动切分的特性,该特性涉及到日志对象配置属性中的以下几个配置项: + +```go +RotateSize int64 // Rotate the logging file if its size > 0 in bytes. +RotateExpire time.Duration // Rotate the logging file if its mtime exceeds this duration. +RotateBackupLimit int // Max backup for rotated files, default is 0, means no backups. +RotateBackupExpire time.Duration // Max expire for rotated files, which is 0 in default, means no expiration. +RotateBackupCompress int // Compress level for rotated files using gzip algorithm. It's 0 in default, means no compression. +RotateCheckInterval time.Duration // Asynchronizely checks the backups and expiration at intervals. It's 1 hour in default. +``` + +简要说明: + +1. `RotateSize` 用于设置滚动切分时按照文件大小进行切分,该属性的单位为字节。只有当该属性值大于0时才会开启滚动切分的特性。 +2. `RotateExpire` 用于设置滚动切分时按照文件超过一定时间没有修改时进行切分。只有当该属性值大于0时才会开启滚动切分的特性。 +3. `RotateBackupLimit` 用于设置滚动切分的保留文件数,默认为0表示不保留,往往该值需要设置大于0。超过该保留文件数的切分文件将会按照从旧到新进行删除。 +4. `RotateBackupExpire` 用于设置按照过期时间进行清理。当切分文件超过指定的时间时将会被删除。 +5. `RotateBackupCompress` 用于设置切分文件的压缩级别,默认为0表示不压缩。该压缩级别的取值范围为 `1-9`,其中 `9` 为最大压缩级别。 +6. `RotateCheckInterval` 用于设置定时器的定时检测间隔,默认为1小时,往往不需要设置。 + +### 功能开启 + +从以上滚动切分的配置项可以看到,仅有当 `RotateSize` 或者 `RotateExpire` 配置项被设置时才会生效,默认情况下是关闭的。 + +### 文件格式 + +`glog` 组件的日志输出文件固定格式为 `*.log`,即使用 `.log` 作为日志文件名后缀。为方便统一规范管理,切分文件的格式也是固定的,开发者无法自定义。当日志文件被滚动切分时,当前被切分的日志文件将会按照” `*.切分时间.log`“的格式进行重命名。其中 `切分时间` 的格式为: `年月日时分秒毫秒微秒`,例如: + +```html +access.log -> access.20200326101301899002.log +access.20200326.log -> access.20200326.20200326101301899002.log +``` + +### 配置示例 + +1. 示例1,按照日志文件大小进行滚动切分: + +```yaml +logger: + Path: "/var/log" + Level: "all" + Stdout: false + RotateSize: "100M" + RotateBackupLimit: 2 + RotateBackupExpire: "7d" + RotateBackupCompress: 9 +``` + +简要说明: + + - 可以看到 `RotateSize` 配置项在配置文件中支持使用字符串的形式进行设置,例如: `100M`, `200MB`, `500KB`, `1Gib` 等等,在该示例中,我们设置日志文件大小超过 `100M` 时进行切分。 + - 同时, `RotateBackupExpire` 的配置项也支持字符串配置,例如: `1h`, `30m`, `1d6h`, `7d` 等等,在该示例中,我们设置切分文件的过期时间为 `7d`,即七天后会自动删除该切分文件。 + - 这里通过设置 `RotateBackupCompress` 为 `9` 表示使用最大的压缩级别,使得切分后的文件最小化存储。但是需要注意,切分和压缩是两个不同的操作,文件压缩是一个异步操作,因此当文件被自动切分后,定时器通过一定的时间间隔定期检查后再自动将其压缩为 `*.gz` 文件。压缩级别设置得越高,会更多使用 `CPU` 资源。 +2. 示例2,按照日志文件修改过期进行滚动切分: + +```yaml +logger: + Path: "/var/log" + Level: "all" + Stdout: false + RotateExpire: "1d" + RotateBackupLimit: 1 + RotateBackupExpire: "7d" + RotateBackupCompress: 9 +``` + +在这里,我们设置 `RotateExpire` 为 `1d` 表示当某个日志文件超过一天都没有任何修改/写入时, `glog` 模块将会自动将其进行滚动切分。同时进行压缩存储。 + + +### 注意事项 + +由于不同的日志对象可能有不同的滚动切分配置,假如多个日志对象的日志目录为同一个,并且都开启了滚动切分特性,那么多个日志对象的滚动切分配置项会冲突,会有意想不到的结果。因此,我们建议您两个选择: + +1. 全局使用同一个默认的日志单例对象( `g.Log()`),通过 `Cat` 或 `File` 方法设置输出日志文件到不同的目录或文件名。 +2. 将不同日志对象( `g.Log(名称)`)的输出目录( `Path` 配置项)设置为不同的文件目录,并且多个日志对象的日志目录不存在相互的层级关系。 + +例如: 我们有两类业务日志文件 `order` 和 `promo`,分别对应订单业务和促销业务,我们先假定他们属于同一个服务程序中。 + +假如日志路径为 `/var/log`,我们可以: + +1. 通过 `g.Log().Cat("order").Print(xxx)` 输出订单日志。生成的日志文件例如: `/var/log/order/2020-03-26.log`。 +2. 通过 `g.Log().Cat("promo").Print(xxx)` 输出促销日志。生成的日志文件例如: `/var/log/promo/2020-03-26.log`。 + +也可以通过: + +1. 通过 `g.Log("order").Print(xxx)` 输出订单日志。生成的日志文件例如: `/var/log/order.log`。 +2. 通过 `g.Log("order").Print(xxx)` 输出促销日志。生成的日志文件例如: `/var/log/promo.log`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..3ea44b215b3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-Writer\346\216\245\345\217\243.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/core/glog-writer' +title: '日志组件-Writer接口' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,日志组件,Writer接口,日志自定义输出,自定义Writer,glog模块,日志HOOK,graylog,集中式日志管理] +description: '在GoFrame框架中使用glog模块的Writer接口来自定义日志输出。通过实现自定义Writer对象,可以灵活地将日志输出到不同的目标如文件、标准输出和Graylog等。此外,还提供了示例代码说明如何实现日志HOOK功能,以便及时将严重错误通知到监控服务。' +--- +:::tip +`Writer` 接口是最底层的 `IO` 写入接口,如果业务需要自定义日志内容打印,建议使用 `Handler` 特性,参考章节: [日志组件-Handler](日志组件-Handler.md) +::: +## 自定义 `Writer` 接口 + +`glog` 模块实现了标准输出以及文件输出的日志内容打印。当然,开发者也可以通过自定义 `io.Writer` 接口实现自定义的日志内容输出。 `io.Writer` 是标准库提供的内容输出接口,其定义如下: + +```go +type Writer interface { + Write(p []byte) (n int, err error) +} +``` + +我们可以通过 `SetWriter` 方法或者链式方法 `To` 来实现自定义 `Writer` 输出,开发者可以在该 `Writer` 中实现定义的操作,也可以在其中整合其他的模块功能。 + +此外, `glog.Logger` 对象已经实现了 `io.Writer` 接口,因此开发者可以非常方便地将 `glog` 整合使用到其他的模块中。 + +## 示例1,实现日志 `HOOK` + +在该示例中,我们实现了一个自定义的 `Writer` 对象 `MyWriter`,在该对象实现的 `Writer` 接口中我们对日志内容进行判断,如果出现了 `PANI` 或者 `FATA` 错误,那么表示是非常严重的错误,该接口将会第一时间通过 `HTTP` 接口告知 `Monitor` 监控服务。随后再将日志内容通过 `glog` 模块按照配置写入到文件和标准输出。 + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/text/gregex" +) + +type MyWriter struct { + logger *glog.Logger +} + +func (w *MyWriter) Write(p []byte) (n int, err error) { + var ( + s = string(p) + ctx = context.Background() + ) + if gregex.IsMatchString(`PANI|FATA`, s) { + fmt.Println("SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time!") + g.Client().PostContent(ctx, "http://monitor.mydomain.com", s) + } + return w.logger.Write(p) +} + +func main() { + var ctx = context.Background() + glog.SetWriter(&MyWriter{ + logger: glog.New(), + }) + glog.Fatal(ctx, "FATAL ERROR") +} +``` + +执行后,输出结果为: + +```html +SERIOUS ISSUE OCCURRED!! I'd better tell monitor in first time! +2019-05-23 20:14:49.374 [FATA] FATAL ERROR +Stack: +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_writer_hook.go:27 +``` + +## 示例2,整合 `graylog` + +假如我们需要输出日志到文件及标准输出,并且同时也需要输出日志到 `Graylog`,很明显这个也是需要自定义 `Writer` 才能实现。当然同理,我们也可以自定义输出到其他的日志收集组件或者数据库中。 + +> `Graylog` 是与 `ELK` 可以相提并论的一款集中式日志管理方案,支持数据收集、检索、可视化 `Dashboard`。 + +示例代码: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" + "github.com/robertkowalski/graylog-golang" +) + +type MyGrayLogWriter struct { + gelf *gelf.Gelf + logger *glog.Logger +} + +func (w *MyGrayLogWriter) Write(p []byte) (n int, err error) { + w.gelf.Send(p) + return w.logger.Write(p) +} + +func main() { + var ctx = context.Background() + glog.SetWriter(&MyGrayLogWriter{ + logger : glog.New(), + gelf : gelf.New(gelf.Config{ + GraylogPort : 80, + GraylogHostname : "graylog-host.com", + Connection : "wan", + MaxChunkSizeWan : 42, + MaxChunkSizeLan : 1337, + }), + }) + glog.Println(ctx, "test log") +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..ed625018024 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\240\206\346\240\210\346\211\223\345\215\260.md" @@ -0,0 +1,146 @@ +--- +slug: '/docs/core/glog-stack' +title: '日志组件-堆栈打印' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,日志组件,堆栈打印,glog,GetStack,PrintStack,gerror,错误日志,调试] +description: 'GoFrame框架日志组件中堆栈打印的功能,该功能允许开发者自动打印日志调用方法的堆栈信息,并可以通过多种方法获取或打印堆栈信息。这些功能对于调试错误日志信息非常有用,特别是在处理复杂应用程序时。本文通过代码示例展示了如何使用这些功能,帮助开发者更好地理解和应用日志组件的堆栈打印特性。' +--- + +错误日志信息支持 `Stack` 特性,该特性可以自动打印出当前调用日志组件方法的堆栈信息,该堆栈信息可以通过 `Notice*/Warning*/Error*/Critical*/Panic*/Fatal*` 等错误日志输出方法触发,也可以通过 `GetStack/PrintStack` 获取/打印。错误信息的 `stack` 信息对于调试来说相当有用。 + +### 示例1,通过 `Error` 方法触发 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func Test(ctx context.Context) { + glog.Error(ctx, "This is error!") +} + +func main() { + ctx := context.TODO() + Test(ctx) +} + +``` + +打印出的结果如下: + +```html +2022-01-05 15:08:54.998 [ERRO] This is error! +Stack: +1. main.Test + C:/hailaz/test/main.go:10 +2. main.main + C:/hailaz/test/main.go:15 +``` + +### 示例2,通过 `Stack` 方法打印 + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + glog.PrintStack(ctx) + glog.New().PrintStack(ctx) + + fmt.Println(glog.GetStack()) + fmt.Println(glog.New().GetStack()) +} + +``` + +执行后,输出结果为: + +```html +2019-07-12 22:20:28.070 Stack: +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:11 + +2019-07-12 22:20:28.070 Stack: +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:12 + +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:14 + +1. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_stack.go:15 +``` + +### 示例3,打印 `gerror.Error` + +`glog` 日志模块支持对标准错误以及 `gerror` 错误的堆栈打印支持。 + +```go +package main + +import ( + "context" + "errors" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/os/glog" +) + +func MakeError() error { + return errors.New("connection closed with normal error") +} + +func MakeGError() error { + return gerror.New("connection closed with gerror") +} + +func TestGError(ctx context.Context) { + err1 := MakeError() + err2 := MakeGError() + glog.Error(ctx, err1) + glog.Errorf(ctx, "%+v", err2) +} + +func main() { + ctx := context.TODO() + TestGError(ctx) +} + +``` + +执行后,终端输出: + +```html +2019-07-12 22:23:11.467 [ERRO] connection closed with normal error +Stack: +1. main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:20 +2. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 + +2019-07-12 22:23:11.467 [ERRO] connection closed with gerror +1. connection closed with gerror + 1). main.MakeGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:14 + 2). main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:19 + 3). main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 +Stack: +1. main.TestGError + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:21 +2. main.main + /home/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/os/glog/glog_gerror.go:25 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" new file mode 100644 index 00000000000..4f2d25bbd07 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\270\270\350\247\201\351\227\256\351\242\230.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/glog-faq' +title: '日志组件-常见问题' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame,GoFrame框架,日志组件,常见问题,error变量,错误堆栈,日志方法,打印错误,g.Log,GoFrame日志] +description: '在使用GoFrame框架进行日志记录时,打印error变量的堆栈信息,而不是打印日志方法调用时的堆栈。提供了具体的Go代码示例,帮助开发者更准确地进行错误日志的记录和调试。' +--- + +## 如果打印 `error` 变量,打印出 `error` 对应的堆栈而不是日志方法调用时的堆栈 + +通过以下方法只会打印 `error` 的字符串描述信息,并且堆栈只是日志方法 `Error` 调用时的堆栈: + +```go +g.Log().Error(ctx, err) +``` + +如果想要打印 `error` 变量的堆栈,并不想打印 `Error` 方法调用时的堆栈,可以通过以下方式: + +```go +g.Log().Printf(ctx, "%+v", err) +``` + +参考连接: + +- [https://github.com/gogf/gf/issues/1640](https://github.com/gogf/gf/issues/1640) +- [错误处理-其他特性](../错误处理/错误处理-其他特性.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" new file mode 100644 index 00000000000..29ca9d96d0a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\345\274\202\346\255\245\350\276\223\345\207\272.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/core/glog-async' +title: '日志组件-异步输出' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,日志组件,异步输出,glog,goroutine,SetAsync,SetFlags,链式操作,日志优化,资源占用] +description: '使用GoFrame框架进行日志的异步输出,以提高日志打印的效率。您可以通过SetAsync或链式方法设置异步输出,异步输出能够减少资源占用,但需注意可能导致日志乱序的问题。' +--- + +对于日志输出即时性要求不高的内容,可以通过异步的方式输出日志,异步输出使得日志打印调用可立即返回,因此效率较高。 `glog` 当然支持异步输出特性,并且内部使用了 `goroutine` 池来管理异步日志打印任务,可以充分的降低对资源的占用率。 + +异步输出可以通过日志对象的 `SetAsync`/ `SetFlags` 方法,或者通过链式操作 `Async` 方法实现。但是需要注意的是,如果通过对象设置方法设置异步输出,那么后续所有的日志输出都将是异步的;如果是通过链式操作输出,那么仅对当前日志输出为异步。 +:::warning +如果对于同一个文件日志输出既采用了同步打印,也采用了异步打印,注意日志文件的内容可能会出现乱序问题,这种情况应当尽量避免。 +::: +### `SetAsync` + +我们来看一个使用 `SetAsync` 方法实现异步打印的示例。 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().SetAsync(true) + for i := 0; i < 10; i++ { + g.Log().Print(ctx, "async log", i) + } +} +``` + +执行后,可以发现终端什么内容也没有输出,因为日志输出的异步的,该示例在日志内容还没有输出之前就退出了。因此,我们可以稍做改进如下: + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().SetAsync(true) + for i := 0; i < 10; i++ { + g.Log().Print(ctx, "async log", i) + } + time.Sleep(time.Second) +} +``` + +执行后,终端输出结果为: + +```html +2019-06-02 15:44:21.399 async log 0 +2019-06-02 15:44:21.399 async log 1 +2019-06-02 15:44:21.399 async log 2 +2019-06-02 15:44:21.399 async log 3 +2019-06-02 15:44:21.399 async log 4 +2019-06-02 15:44:21.399 async log 5 +2019-06-02 15:44:21.399 async log 6 +2019-06-02 15:44:21.399 async log 7 +2019-06-02 15:44:21.399 async log 8 +2019-06-02 15:44:21.399 async log 9 +``` + +### `Async` 链式操作 + +使用链式操作比较简单。 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + for i := 0; i < 10; i++ { + g.Log().Async().Print(ctx, "async log", i) + } + g.Log().Print(ctx, "normal log") + g.Log().Print(ctx, "normal log") + g.Log().Print(ctx, "normal log") + time.Sleep(time.Second) +} +``` + +执行后,终端输出结果为: + +```html +2022-01-05 15:00:44.101 normal log +2022-01-05 15:00:44.101 async log 0 +2022-01-05 15:00:44.101 async log 1 +2022-01-05 15:00:44.101 async log 2 +2022-01-05 15:00:44.101 async log 3 +2022-01-05 15:00:44.101 async log 4 +2022-01-05 15:00:44.101 async log 5 +2022-01-05 15:00:44.101 async log 6 +2022-01-05 15:00:44.101 async log 7 +2022-01-05 15:00:44.101 async log 8 +2022-01-05 15:00:44.101 async log 9 +2022-01-05 15:00:44.101 normal log +2022-01-05 15:00:44.103 normal log +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" new file mode 100644 index 00000000000..fd4855a6e0e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\226\207\344\273\266\347\233\256\345\275\225.md" @@ -0,0 +1,101 @@ +--- +slug: '/docs/core/glog-file-folder' +title: '日志组件-文件目录' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,日志组件,文件目录,日志文件,SetFile方法,gtime时间格式,链式操作,SetPath方法,gfpool文件指针池,日志目录] +description: '使用GoFrame框架中的日志组件设置日志文件的名称和目录路径。通过SetFile方法,用户可以自定义日志文件的格式并支持gtime时间格式。通过SetPath方法,用户可以将日志内容写入指定目录,并利用gfpool优化文件写入效率。' +--- + +## 日志文件 + +默认情况下,日志文件名称以当前时间日期命名,格式为 `YYYY-MM-DD.log`,我们可以使用 `SetFile` 方法来设置文件名称的格式,并且文件名称格式支持 [时间管理-gtime](../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) 时间格式 。简单示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +// 设置日志等级 +func main() { + ctx := context.TODO() + path := "./glog" + glog.SetPath(path) + glog.SetStdoutPrint(false) + // 使用默认文件名称格式 + glog.Print(ctx, "标准文件名称格式,使用当前时间时期") + // 通过SetFile设置文件名称格式 + glog.SetFile("stdout.log") + glog.Print(ctx, "设置日志输出文件名称格式为同一个文件") + // 链式操作设置文件名称格式 + glog.File("stderr.log").Print(ctx, "支持链式操作") + glog.File("error-{Ymd}.log").Print(ctx, "文件名称支持带gtime日期格式") + glog.File("access-{Ymd}.log").Print(ctx, "文件名称支持带gtime日期格式") + + list, err := gfile.ScanDir(path, "*") + g.Dump(err) + g.Dump(list) +} + +``` + +执行后,输出结果为: + +```html + +[ + "C:\hailaz\test\glog\2021-12-31.log", + "C:\hailaz\test\glog\access-20211231.log", + "C:\hailaz\test\glog\error-20211231.log", + "C:\hailaz\test\glog\stderr.log", + "C:\hailaz\test\glog\stdout.log", +] +``` + +可以看到,文件名称格式中如果需要使用 `gtime` 时间格式,格式内容需要使用 `{xxx}` 包含起来。该示例中也使用到了 `链式操作` 的特性,具体请看后续说明。 + +## 日志目录 + +默认情况下, `glog` 将会输出日志内容到标准输出,我们可以通过 `SetPath` 方法设置日志输出的目录路径,这样日志内容将会写入到日志文件中,并且由于其内部使用了 `gfpool` 文件指针池,文件写入的效率相当优秀。简单示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/glog" +) + +// 设置日志等级 +func main() { + ctx := context.TODO() + path := "./glog" + glog.SetPath(path) + glog.Print(ctx, "日志内容") + list, err := gfile.ScanDir(path, "*") + g.Dump(err) + g.Dump(list) +} + +``` + +执行后,输出内容为: + +```html +2021-12-31 11:32:16.742 日志内容 + +[ + "C:\hailaz\test\glog\2021-12-31.log", +] +``` + +当通过 `SetPath` 设置日志的输出目录,如果目录不存在时,将会递归创建该目录路径。可以看到,执行 `Println` 之后,在 `/tmp` 下创建了日志目录 `glog`,并且在其下生成了日志文件。同时,我们也可以看见日志内容不仅输出到了文件,默认情况下也输出到了终端,我们可以通过 `SetStdoutPrint(false)` 方法来关闭终端的日志输出,这样日志内容仅会输出到日志文件中。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" new file mode 100644 index 00000000000..a36832a306e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\346\227\245\345\277\227\347\272\247\345\210\253.md" @@ -0,0 +1,196 @@ +--- +slug: '/docs/core/glog-level' +title: '日志组件-日志级别' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,日志组件,日志级别,GoFrame框架,SetLevel,SetLevelStr,SetLevelPrint,级别名称,glog,日志输出] +description: '在GoFrame框架中使用日志组件来管理和设定日志级别,包括SetLevel、SetLevelStr和SetLevelPrint方法的具体用法示例。我们还讨论了如何通过不同的日志级别名称来过滤和显示日志内容,实现灵活的日志管理。' +--- + +## 日志级别 + +日志级别用于管理日志的输出,我们可以通过设定特定的日志级别来关闭/开启特定的日志内容。 日志级别的设置可以通过两个方法实现: + +```go +func (l *Logger) SetLevel(level int) +func (l *Logger) SetLevelStr(levelStr string) error +func (l *Logger) SetLevelPrint(enabled bool) +``` + +### `SetLevel` 方法 + +通过 `SetLevel` 方法可以设置日志级别, `glog` 模块支持以下几种日志级别常量设定: + +```html +LEVEL_ALL +LEVEL_DEV +LEVEL_PROD +LEVEL_DEBU +LEVEL_INFO +LEVEL_NOTI +LEVEL_WARN +LEVEL_ERRO +``` + +我们可以通过 `位操作` 组合使用这几种级别,例如其中 `LEVEL_ALL` 等价于 `LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT`。我们还可以通过 `LEVEL_ALL & ^LEVEL_DEBU & ^LEVEL_INFO & ^LEVEL_NOTI` 来过滤掉 `LEVEL_DEBU/LEVEL_INFO/LEVEL_NOTI` 日志内容。 +:::warning +当然日志模块还有其他的一些级别,如 `CRIT/PANI/FATA`,但是这几个级别是非常严重的错误,无法在日志级别中由开发者自定义屏蔽。例如产生严重错误的时候, `PANI/FATA` 错误界别将会产生一些额外的系统动作: `panic`/ `exit`。 +::: +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevel(glog.LEVEL_ALL ^ glog.LEVEL_INFO) + l.Info(ctx, "info2") +} + +``` + +执行后,输出结果为: + +```html +2021-12-31 11:16:57.272 [INFO] info1 +``` + +### `SetLevelStr` 方法 + +大部分场景下我们可以通过 `SetLevelStr` 方法来通过字符串设置日志级别,配置文件中的 `level` 配置项也是通过字符串来配置日志级别。支持的日志级别字符串如下,不区分大小写: + +```html +"ALL": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEV": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEVELOP": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"PROD": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"PRODUCT": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEBU": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"DEBUG": LEVEL_DEBU | LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"INFO": LEVEL_INFO | LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"NOTI": LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"NOTICE": LEVEL_NOTI | LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"WARN": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"WARNING": LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT, +"ERRO": LEVEL_ERRO | LEVEL_CRIT, +"ERROR": LEVEL_ERRO | LEVEL_CRIT, +"CRIT": LEVEL_CRIT, +"CRITICAL": LEVEL_CRIT, +``` + +可以看到,通过级别名称设置的日志级别是按照日志级别的高低来进行过滤的: `DEBU < INFO < NOTI < WARN < ERRO < CRIT`,也支持 `ALL`, `DEV`, `PROD` 常见部署模式配置名称。 + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevelStr("notice") + l.Info(ctx, "info2") +} + +``` + +执行后,输出结果为: + +```html +2021-12-31 11:20:15.019 [INFO] info1 +``` + +### `SetLevelPrint` 方法 + +控制默认日志输出是否打印日志级别标识,默认会打印日志级别标识。 + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.Info(ctx, "info1") + l.SetLevelPrint(false) + l.Info(ctx, "info2") +} + +``` + +执行后,输出结果为: + +```html +2023-03-14 10:28:18.598 [INFO] info1 +2023-03-14 10:28:18.631 info1 +``` + +## 级别名称 + +在日志中我们会看到不同级别的打印内容,会在内容前面带有不同的日志级别名称。默认的日志级别名称如下: + +```html +LEVEL_DEBU: "DEBU", +LEVEL_INFO: "INFO", +LEVEL_NOTI: "NOTI", +LEVEL_WARN: "WARN", +LEVEL_ERRO: "ERRO", +LEVEL_CRIT: "CRIT", +LEVEL_PANI: "PANI", +LEVEL_FATA: "FATA", +``` + +为方便统一日志格式,保证比较优雅的排版风格,因此日志级别的名称都使用了级别英文单词的前四个字符。若有特殊需求需要修改日志级别名称的,可以通过以下方法进行设置: + +```go +func (l *Logger) SetLevelPrefix(level int, prefix string) +func (l *Logger) SetLevelPrefixes(prefixes map[int]string) +``` + +使用示例: + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + ctx := context.TODO() + l := glog.New() + l.SetLevelPrefix(glog.LEVEL_DEBU, "debug") + l.Debug(ctx, "test") +} + +``` + +执行后,终端输出: + +```html +2021-12-31 11:21:45.754 [debug] test +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" new file mode 100644 index 00000000000..37f45ea9e7b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\350\260\203\350\257\225\344\277\241\346\201\257.md" @@ -0,0 +1,55 @@ +--- +slug: '/docs/core/glog-debug' +title: '日志组件-调试信息' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,日志组件,调试信息,Debug方法,测试环境,SetDebug,示例代码,日志输出,命令行参数,环境变量] +description: '在GoFrame框架中使用Debug/Debugf方法进行调试信息记录的方式,适用于开发和测试环境。通过代码示例演示如何使用SetDebug方法控制调试信息的输出,以及通过命令行参数和环境变量关闭调试日志的操作。' +--- + +`Debug/Debugf` 是非常有用的几个方法,用于调试信息的记录,常用于开发/测试环境中,当应用上线之后可以方便地使用 `SetDebug` 或者配置文件进行开启/关闭。 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + ctx := context.TODO() + gtimer.SetTimeout(ctx, 3*time.Second, func(ctx context.Context) { + g.Log().SetDebug(false) + }) + for { + g.Log().Debug(ctx, gtime.Datetime()) + g.Log().Info(ctx, gtime.Datetime()) + time.Sleep(time.Second) + } +} +``` + +该示例中使用 `glog.Debug` 方法输出调试信息,3秒后关闭调试信息的输出。执行后,输出结果如下,可以看到只输出了3条日志信息,后续的调试日志信息由于通过 `SetDebug` 方法关闭后,便不再输出。 + +```html +2022-01-05 15:59:05.674 [DEBU] 2022-01-05 15:59:05 +2022-01-05 15:59:05.675 [INFO] 2022-01-05 15:59:05 +2022-01-05 15:59:06.684 [DEBU] 2022-01-05 15:59:06 +2022-01-05 15:59:06.684 [INFO] 2022-01-05 15:59:06 +2022-01-05 15:59:07.692 [DEBU] 2022-01-05 15:59:07 +2022-01-05 15:59:07.692 [INFO] 2022-01-05 15:59:07 +2022-01-05 15:59:08.708 [INFO] 2022-01-05 15:59:08 +2022-01-05 15:59:09.717 [INFO] 2022-01-05 15:59:09 +2022-01-05 15:59:10.728 [INFO] 2022-01-05 15:59:10 +2022-01-05 15:59:11.733 [INFO] 2022-01-05 15:59:11 +``` + +> 我们还可以通过命令行参数或者系统环境变量参数的方式关闭掉调试信息。 +> +> 1. 修改命令行启动参数 \- `gf.glog.debug=false`; +> 2. 修改指定的环境变量 \- `GF_GLOG_DEBUG=false`; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..b177d98f312 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/core/glog-config' +title: '日志组件-配置管理' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,日志组件,配置管理,日志输出,日志级别,配置文件,Logger,模块化,日志切分,日志格式] +description: 'GoFrame框架中日志组件的配置管理功能,包括如何通过配置文件和配置方法管理Logger对象。日志组件支持多种配置格式,模块化设计使其能独立地进行日志输出配置。配置项涵盖日志路径、输出级别和终端显示等,日志级别支持多种模式,确保灵活记录各级别信息。' +--- + +日志组件是 `GoFrame` 框架核心的组件之一,支持非常方便的配置管理能力。 + +## 配置文件(推荐) +:::tip +日志的配置使用的是框架统一的配置组件,支持多种文件格式,也支持配置中心、接口化扩展等特性,更多细节请参考章节: [配置管理](../配置管理/配置管理.md) +::: +日志组件支持配置文件,当使用 `g.Log(单例名称)` 获取 `Logger` 单例对象时,将会自动通过默认的配置管理对象获取对应的 `Logger` 配置。默认情况下会读取 `logger.单例名称` 配置项,当该配置项不存在时,将会读取默认的 `logger` 配置项。配置项请参考配置对象结构定义: [https://pkg.go.dev/github.com/gogf/gf/v2/os/glog#Config](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog#Config) + +完整配置文件配置项及说明如下,其中配置项名称不区分大小写: + +```yaml +logger: + path: "/var/log/" # 日志文件路径。默认为空,表示关闭,仅输出到终端 + file: "{Y-m-d}.log" # 日志文件格式。默认为"{Y-m-d}.log" + prefix: "" # 日志内容输出前缀。默认为空 + level: "all" # 日志输出级别 + timeFormat: "2006-01-02T15:04:05" # 自定义日志输出的时间格式,使用Golang标准的时间格式配置 + ctxKeys: [] # 自定义Context上下文变量名称,自动打印Context的变量到日志中。默认为空 + header: true # 是否打印日志的头信息。默认true + stdout: true # 日志是否同时输出到终端。默认true + rotateSize: 0 # 按照日志文件大小对文件进行滚动切分。默认为0,表示关闭滚动切分特性 + rotateExpire: 0 # 按照日志文件时间间隔对文件滚动切分。默认为0,表示关闭滚动切分特性 + rotateBackupLimit: 0 # 按照切分的文件数量清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除 + rotateBackupExpire: 0 # 按照切分的文件有效期清理切分文件,当滚动切分特性开启时有效。默认为0,表示不备份,切分则删除 + rotateBackupCompress: 0 # 滚动切分文件的压缩比(0-9)。默认为0,表示不压缩 + rotateCheckInterval: "1h" # 滚动切分的时间检测间隔,一般不需要设置。默认为1小时 + stdoutColorDisabled: false # 关闭终端的颜色打印。默认开启 + writerColorEnable: false # 日志文件是否带上颜色。默认false,表示不带颜色 +``` + +其中, `level` 配置项使用字符串配置,按照日志级别支持以下配置: `DEBU` < `INFO` < `NOTI` < `WARN` < `ERRO` < `CRIT`,也支持 `ALL`, `DEV`, `PROD` 常见部署模式配置名称。 `level` 配置项字符串不区分大小写。关于日志级别的详细介绍请查看 [日志组件-日志级别](日志组件-日志级别.md) 章节。 + +### 示例1,默认配置项 + +```yaml +logger: + path: "/var/log" + level: "all" + stdout: false +``` + +随后可以使用 `g.Log()` 获取默认的单例对象时自动获取并设置该配置。 + +### 示例2,多个配置项 + +多个 `Logger` 的配置示例: + +```yaml +logger: + path: "/var/log" + level: "all" + stdout: false + logger1: + path: "/var/log/logger1" + level: "dev" + stdout: false + logger2: + path: "/var/log/logger2" + level: "prod" + stdout: true +``` + +我们可以通过单例对象名称获取对应配置的 `Logger` 单例对象: + +```go +// 对应 logger.logger1 配置项 +l1 := g.Log("logger1") +// 对应 logger.logger2 配置项 +l2 := g.Log("logger2") +// 对应默认配置项 logger +l3 := g.Log("none") +// 对应默认配置项 logger +l4 := g.Log() +``` + +## 配置方法(高级) + +配置方法用于模块化使用 `glog` 时由开发者自己进行配置管理。 + +方法列表: + +简要说明: + +1. 可以通过 `SetConfig` 及 `SetConfigWithMap` 来设置。 +2. 也可以使用 `Logger` 对象的 `Set*` 方法进行特定配置的设置。 +3. 主要注意的是,配置项在 `Logger` 对象执行日志输出之前设置,避免并发安全问题。 + +我们可以使用 `SetConfigWithMap` 方法通过 `Key-Value` 键值对来设置/修改 `Logger` 的特定配置,其余的配置使用默认配置即可。其中 `Key` 的名称即是 `Config` 这个 `struct` 中的属性名称,并且不区分大小写,单词间也支持使用 `-`/ `_`/ `空格` 符号连接,具体可参考 [类型转换-Struct转换](../类型转换/类型转换-Struct转换.md) 章节的转换规则。 + +简单示例: + +```go +logger := glog.New() +logger.SetConfigWithMap(g.Map{ + "path": "/var/log", + "level": "all", + "stdout": false, + "StStatus": 0, +}) +logger.Print("test") +``` + +其中 `StStatus` 表示是否开启堆栈打印,设置为 `0` 表示关闭。键名也可以使用 `stStatus`, `st-status`, `st_status`, `St Status`,其他配置属性以此类推。 + +## 注意事项 + +常见问题:如日志组件的配置为何没有对 `HTTP Server`、 `GRPC Server`、 `ORM` 组件打印的日志生效。 + +`GoFrame` 框架采用了模块化设计,日志组件是框架独立的组件,本章节介绍的配置均只对独立使用日志组件的方式生效,例如使用 `g.Log()` 或者 `glog.New()` 方法创建的日志组件。其他组件的日志配置有专门的配置项,或者日志对象设置方法来实现日志的配置,请具体查看对应的组件文档和 `API`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" new file mode 100644 index 00000000000..9d70c463adb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\223\276\345\274\217\346\223\215\344\275\234.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/core/glog-chaining' +title: '日志组件-链式操作' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,日志组件,链式操作,glog,日志输出,日志级别,文件回溯,终端输出,异步日志] +description: 'GoFrame框架中的glog模块,支持链式操作的日志功能。包括设置日志输出路径、日志文件分类、日志级别、开启trace打印等功能。此外,提供如何设置文件回溯值,实现异步输出日志的示例代码和应用场景。全面优化日志使用体验。' +--- + +完整的方法列表可参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/glog](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog) + +`glog` 模块支持非常简便的 `链式操作` 方式,主要的链式操作方法如下: + +```go +// 重定向日志输出接口 +func To(writer io.Writer) *Logger +// 日志内容输出到目录 +func Path(path string) *Logger +// 设置日志文件分类 +func Cat(category string) *Logger +// 设置日志文件格式 +func File(file string) *Logger +// 设置日志打印级别 +func Level(level int) *Logger +// 设置日志打印级别(字符串) +func LevelStr(levelStr string) *Logger +// 设置文件回溯值 +func Skip(skip int) *Logger +// 是否开启trace打印 +func Stack(enabled bool) *Logger +// 开启trace打印并设定过滤trace的字符串 +func StackWithFilter(filter string) *Logger +// 是否开启终端输出 +func Stdout(enabled...bool) *Logger +// 是否输出日志头信息 +func Header(enabled...bool) *Logger +// 输出日志调用行号信息 +func Line(long...bool) *Logger +// 异步输出日志 +func Async(enabled...bool) *Logger +``` + +## 示例1, 基本使用 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfile" +) + +func main() { + ctx := context.TODO() + path := "/tmp/glog-cat" + g.Log().SetPath(path) + g.Log().Stdout(false).Cat("cat1").Cat("cat2").Print(ctx, "test") + list, err := gfile.ScanDir(path, "*", true) + g.Dump(err) + g.Dump(list) +} +``` + +执行后,输出结果为: + +```javascriprt +[ + "/tmp/glog-cat/cat1", + "/tmp/glog-cat/cat1/cat2", + "/tmp/glog-cat/cat1/cat2/2018-10-10.log", +] +``` + +## 示例2, 打印调用行号 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Line().Print(ctx, "this is the short file name with its line number") + g.Log().Line(true).Print(ctx, "lone file name with line number") +} +``` + +执行后,终端输出结果为: + +```html +2019-05-23 09:22:58.141 glog_line.go:8: this is the short file name with its line number +2019-05-23 09:22:58.142 /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/os/glog/glog_line.go:9: lone file name with line number +``` + +## 示例3, 文件回溯 `Skip` + +有时我们通过一些模块封装了 `glog` 模块来打印日志,例如封装了一个 `logger` 包通过 `logger.Print` 来打印日志,这个时候打印出来的调用文件行号总是同一个位置,因为对于 `glog` 来讲,它的调用方即总是 `logger.Print` 方法。这个时候,我们可以通过设置回溯值来跳过回溯的文件数,使用 `SetStackSkip` 或者链式方法 `Skip` 实现。 + +> 文件回溯值的设置同样也会影响 `Stack` 调用回溯打印结果。 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func PrintLog(ctx context.Context, content string) { + g.Log().Skip(1).Line().Print(ctx, "line number with skip:", content) + g.Log().Line().Print(ctx, "line number without skip:", content) +} + +func main() { + ctx := context.TODO() + PrintLog(ctx, "just test") +} +``` + +执行后,终端输出结果为: + +```html +2019-05-23 19:30:10.984 glog_line2.go:13: line number with skip: just test +2019-05-23 19:30:10.984 glog_line2.go:9: line number without skip: just test +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" new file mode 100644 index 00000000000..ef2fede362e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266-\351\242\234\350\211\262\346\211\223\345\215\260.md" @@ -0,0 +1,75 @@ +--- +slug: '/docs/core/glog-color' +title: '日志组件-颜色打印' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,日志组件,颜色打印,日志等级,日志配置,日志可查看性,终端颜色输出,文件日志,日志调试] +description: '在GoFrame框架中使用日志组件的颜色打印功能,以提高日志的可查看性。通过添加字体颜色来突出显示不同的日志等级,包括Debug、Info、Notice、Warning、Error等。此外,本文还提供了在配置文件以及代码中启用日志颜色打印的示例,并说明了默认情况下不同日志等级对应的颜色设置。' +--- + +## 颜色打印 + +可以增加日志的可查看性,打印日志时,会将错误等级文字通过添加字体颜色的方式突出显示。 + +## 效果示例 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + ctx := context.TODO() + g.Log().Debug(ctx, "Debug") + g.Log().Info(ctx, "Info") + g.Log().Notice(ctx, "Notice") + g.Log().Warning(ctx, "Warning") + g.Log().Error(ctx, "Error") +} +``` + +![](/markdown/14e84e84c66a71247cfb1d19dd4bc07d.png) + +## 使用配置 + +控制台是必然会自带颜色输出的,文件日志默认不带颜色 + +若需要在文件中的日志也带上颜色可以在配置文件中添加配置 + +```yaml +logger: + stdoutColorDisabled: false # 是否关闭终端的颜色打印。默认否,表示终端的颜色输出。 + writerColorEnable: false # 是否开启Writer的颜色打印。默认否,表示不输出颜色到自定义的Writer或者文件。 +``` + +也可以在代码中添加 + +```go +g.Log().SetWriterColorEnable(true) +``` + +得到效果如下(红框部分是添加了文件日志颜色后的效果,另外部分为未开启时的效果) + +![](/markdown/034442032ae97084b092395d8c9daa93.png) + +## 默认颜色对应表 + +默认情况下,不同日志等级对应的颜色如下: + +```go +// \v2\os\glog\glog_logger_color.go +var defaultLevelColor = map[int]int{ + LEVEL_DEBU: COLOR_YELLOW, + LEVEL_INFO: COLOR_GREEN, + LEVEL_NOTI: COLOR_CYAN, + LEVEL_WARN: COLOR_MAGENTA, + LEVEL_ERRO: COLOR_RED, + LEVEL_CRIT: COLOR_HI_RED, + LEVEL_PANI: COLOR_HI_RED, + LEVEL_FATA: COLOR_HI_RED, +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..2ce009031bd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266/\346\227\245\345\277\227\347\273\204\344\273\266.md" @@ -0,0 +1,77 @@ +--- +slug: '/docs/core/glog' +sidebar_position: 4 +hide_title: true +keywords: [glog,日志管理,高性能,GoFrame,日志组件,调试管理,日志级别,链式操作,滚动切分,单例模式] +description: 'glog 是 GoFrame 框架的核心日志管理模块,提供高性能的日志管理功能。它支持文件输出、日志级别、日志分类以及丰富的调试、调用跟踪特性。通过 glog.New 创建自定义 Logger 对象或使用默认方法打印日志。glog 组件的显著特性包括简便功能、支持颜色打印、异步输出、链式操作和自定义日志处理。' +--- + +`glog` 是通用的高性能日志管理模块,实现了强大易用的日志管理功能,是 `GoFrame` 开发框架的核心组件之一。 + +## 基本介绍 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/glog" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/glog](https://pkg.go.dev/github.com/gogf/gf/v2/os/glog) + +**简要说明:** + +1. `glog` 模块固定日志文件名称格式为 `*.log`,即固定使用 `.log` 作为日志文件名后缀。 +2. `glog` 支持文件输出、日志级别、日志分类、调试管理、调用跟踪、链式操作、滚动切分等等丰富特性。 +3. 可以使用 `glog.New` 方法创建 `glog.Logger` 对象用于自定义日志打印,也可以并推荐使用 `glog` 默认提供的包方法来打印日志。 +4. 当使用包方法修改模块配置时,注意任何的 `glog.Set*` 设置方法都将会 **全局生效**。 +5. 日志内容默认时间格式为 `时间 [级别] 内容 换行`,其中 `时间` 精确到毫秒级别, `级别` 为可选输出, `内容` 为调用端的参数输入, `换行` 为可选输出(部分方法自动为日志内容添加换行符号),日志内容示例: `2018-10-10 12:00:01.568 [ERRO] 产生错误`。 +6. `Print*/Debug*/Info*` 方法输出日志内容到标准输出( `stdout`),为防止日志的错乱, `Notice*/Warning*/Error*/Critical*/Panic*/Fatal*` 方法也是将日志内容输出到标准输出( `stdout`)。 +7. `Panic*` 方法在输出日志信息后会引发 `panic` 错误方法 +8. `Fatal*` 方法在输出日志信息之后会停止进程运行,并返回进程状态码值为 `1`(正常程序退出状态码为 `0`)。 + +## 组件特性 + +`glog` 组件具有以下显著特性: + +- 使用简便,功能强大 +- 支持配置管理,使用统一的配置组件 +- 支持日志级别 +- 支持颜色打印 +- 支持链式操作 +- 支持指定输出日志文件/目录 +- 支持链路跟踪 +- 支持异步输出 +- 支持堆栈打印 +- 支持调试信息开关 +- 支持自定义 `Writer` 输出接口 +- 支持自定义日志 `Handler` 处理 +- 支持自定义日志 `CtxKeys` 键值 +- 支持 `JSON` 格式打印 +- 支持 `Flags` 特性 +- 支持 `Rotate` 滚动切分特性 + + +## 单例对象 + +日志组件支持单例模式,使用 `g.Log(单例名称)` 获取不同的单例日志管理对象。提供单例对象的目的在于针对不同业务场景可以使用不同配置的日志管理对象。我们推荐使用 `g.Log()` 方法获取单例对象来进行日志操作,该方法内部会自动读取配置文件并初始化单例对象,该初始化操作仅会执行一次。 + +## `glog.Print` 和 `g.Log().Print` 区别 + +- `glog` 是日志组件的包名, `g.Log()` 是一个日志组件单例对象。 +- `g.Log()` 单例对象通过对象管理组件 `g` 包来维护,对象初始化时会自动读取日志配置,使用简便,大多数场景下推荐使用这种方式使用日志组件。 +- `glog` 通过独立组件的形式使用,默认情况下会直接输出日志到终端。也可以通过配置管理方法设置全局配置,或者通过 `New` 创建独立的日志对象使用。 +:::tip +**网友**:为什么会有两种日志打印方式?我应该用哪种方式呢? + +**回答**: + +由于框架的每个组件都是 **解耦化设计** 的,框架是 **可以作为独立的工具库使用** 的。比如项目只需要使用日志组件,那么可以直接使用 `glog` 包的方法即可,不会引入其他的组件。但在项目工程化使用中,工具库的使用方式会较为繁琐,往往会涉及到配置和组件初始化(大部分场景会引起二次封装),因此框架也提供了一个 **耦合包** 叫做 `g` 包,这个包下面默认加载了一些常用的组件。 `g.Log()` 就是其中的日志组件,它会自动按照工程规范读取并初始化配置对象,快速初始化日志对象并实现单例管理,极大简化工程化下的日志使用。更多的介绍请参考: [对象管理](../对象管理.md) +::: + + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..f7714bf683e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\240\270\345\277\203\347\273\204\344\273\266.md" @@ -0,0 +1,14 @@ +--- +slug: '/docs/core' +title: '核心组件(🔥重点🔥)' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,核心组件,GoFrame框架,框架组成,软件开发,技术框架,开发工具,编程基础,应用开发,系统架构] +description: '核心组件是GoFrame框架的重要组成部分,提供了基础的开发工具和系统架构支持,为应用开发提供了坚实可靠的基础。本文档介绍核心组件及其在GoFrame框架中的应用,帮助开发者更好地理解和使用这些组件。' +--- + +核心组件是 `GoFrame` 框架的核心组成部分。 + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..0b474e4ddf4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-XSS\345\244\204\347\220\206.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gview-xss' +title: '模板引擎-XSS处理' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,XSS处理,HTML转码,AutoEncode,SetAutoEncode,变量输出,安全性,框架配置] +description: '在使用GoFrame框架的模板引擎时进行XSS处理。默认情况下,输出变量未进行HTML转码,可能导致XSS漏洞,但GoFrame框架提供了灵活的配置参数,通过AutoEncode或SetAutoEncode方法控制转义效果,以增强输出安全性。' +--- + +默认情况下,模板引擎对所有的变量输出并没有使用 `HTML` 转码处理,也就是说,如果开发者处理不好,可能会存在XSS漏洞。 + +不用担心, `GoFrame` 框架当然已经充分考虑到这点,并且为开发者提供了比较灵活的配置参数来控制是否默认转义变量输出的 `HTML` 内容。该特性可以通过 `AutoEncode` 配置项,或者 `SetAutoEncode` 方法来开启/关闭。 +:::tip +需要注意的是,该特性并不会影响 `include` 模板的内置函数。 +::: +使用示例: + +1、配置文件 + +```toml +[viewer] + delimiters = ["${", "}"] + autoencode = true +``` + +2、示例代码 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + result, _ := g.View().ParseContent(context.TODO(), "姓名: ${.name}", g.Map{ + "name": "", + }) + fmt.Println(result) +} +``` + +3、执行输出 + +```html +姓名: <script>alert('john');</script> +``` diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..e7ab6f0273f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\345\205\266\344\273\226\344\275\277\347\224\250.md" @@ -0,0 +1,214 @@ +--- +slug: '/docs/core/gview-more' +title: '模板引擎-其他使用' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,I18N支持,HTTP对象视图,控制器视图管理,GoFrame框架,gview,模板解析,WebServer] +description: '在GoFrame框架中使用模板引擎的不同功能,包括支持I18N特性及在Http对象和控制器中的实现。通过示例代码,我们展示了模板解析语法和在多线程环境下的数据隔离管理。也包含了不推荐的控制器注册方式的使用注意事项。' +--- + +## `I18N` 支持 + +模板引擎支持 `i18n` 特性,可以通过给上下文注入特定的 `i18n` 语言来实现不同的请求/页面使用不同的 `i18n` 语言渲染。例如: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/i18n/gi18n" +) + +func main() { + var ( + ctxCN = gi18n.WithLanguage(context.TODO(), "zh-CN") + ctxJa = gi18n.WithLanguage(context.TODO(), "ja") + content = `{{.name}} says "{#hello}{#world}!"` + ) + + result1, _ := g.View().ParseContent(ctxCN, content, g.Map{ + "name": "john", + }) + fmt.Println(result1) + + result2, _ := g.View().ParseContent(ctxJa, content, g.Map{ + "name": "john", + }) + fmt.Println(result2) +} +``` + +执行后,输出结果为:(保证当前运行目录带有 `i18n` 转译配置文件) + +```html +john says "你好世界!" +john says "こんにちは世界!" +``` + +## HTTP对象视图 + +`goframe` 框架的 `WebServer` 的返回对象中提供了基础的模板解析方法,如下: + +```go +func (r *Response) WriteTpl(tpl string, params map[string]interface{}, funcMap ...map[string]interface{}) error +func (r *Response) WriteTplContent(content string, params map[string]interface{}, funcMap ...map[string]interface{}) error +``` + +其中 `WriteTpl` 用于输出模板文件, `WriteTplContent` 用于直接解析输出模板内容。 + +使用示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Cookie.Set("theme", "default") + r.Session.Set("name", "john") + r.Response.WriteTplContent(`Cookie:{{.Cookie.theme}}, Session:{{.Session.name}}`, nil) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,输出结果为: + +``` +Cookie:default, Session:john +``` + +## 控制器视图管理 + +`goframe` 为路由控制器注册方式提供了良好的模板引擎支持,由 `gmvc.View` 视图对象进行管理,提供了良好的数据 `隔离性`。控制器视图是并发安全设计的,允许在多线程中异步操作。 +:::warning +控制器注册方式类似于PHP执行流程,相对来说性能比较低效,因此未来不再推荐使用控制器注册方式。 +::: +```go +func (view *View) Assign(key string, value interface{}) +func (view *View) Assigns(data gview.Params) + +func (view *View) Parse(file string) ([]byte, error) +func (view *View) ParseContent(content string) ([]byte, error) + +func (view *View) Display(files ...string) error +func (view *View) DisplayContent(content string) error + +func (view *View) LockFunc(f func(vars map[string]interface{})) +func (view *View) RLockFunc(f func(vars map[string]interface{})) +``` + +使用示例1: + +```go +package main + +import ( + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/frame/gmvc" +) + +type ControllerTemplate struct { + gmvc.Controller +} + +func (c *ControllerTemplate) Info() { + c.View.Assign("name", "john") + c.View.Assigns(map[string]interface{}{ + "age" : 18, + "score" : 100, + }) + c.View.Display("index.tpl") +} + +func main() { + s := ghttp.GetServer() + s.BindController("/template", new(ControllerTemplate)) + s.SetPort(8199) + s.Run() +} +``` + +其中 `index.tpl` 的模板内容如下: + +``` + + + gf template engine + + +

    Name: {{.name}}

    +

    Age: {{.age}}

    +

    Score:{{.score}}

    + + +``` + +执行后,访问 [http://127.0.0.1:8199/template/info](http://127.0.0.1:8199/template/info) 可以看到模板被解析并展示到页面上。如果页面报错找不到模板文件,没有关系,因为这里并没有对模板目录做设置,默认是当前可行文件的执行目录( `Linux&Mac` 下是 `/tmp` 目录, `Windows` 下是 `C:\Documents and Settings\用户名\Local Settings\Temp`)。 + +其中,给定的模板文件file参数是需要带完整的文件名后缀,例如: `index.tpl`, `index.html` 等等,模板引擎对模板文件后缀名没有要求,用户可完全自定义。此外,模板文件参数也支持文件的绝对路径(完整的文件路径)。 + +当然,我们也可以直接解析模板内容,例如: + +```go +package main + +import ( + "github.com/gogf/gf/v2/net/ghttp" + "github.com/gogf/gf/v2/frame/gmvc" +) + +type ControllerTemplate struct { + gmvc.Controller +} + +func (c *ControllerTemplate) Info() { + c.View.Assign("name", "john") + c.View.Assigns(map[string]interface{}{ + "age" : 18, + "score" : 100, + }) + c.View.DisplayContent(` + + + gf template engine + + +

    Name: {{.name}}

    +

    Age: {{.age}}

    +

    Score:{{.score}}

    + + + `) +} + +func main() { + s := ghttp.GetServer() + s.BindController("/template", new(ControllerTemplate{})) + s.SetPort(8199) + s.Run() +} +``` + +执行后,访问 [http://127.0.0.1:8199/template/info](http://127.0.0.1:8199/template/info) 可以看到解析后的内容如下: + +``` + + + gf template engine + + +

    Name: john

    +

    Age: 18

    +

    Score:100

    + + +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..55487531a34 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\206\205\347\275\256\345\207\275\346\225\260.md" @@ -0,0 +1,304 @@ +--- +slug: '/docs/core/gview-funcs-builtin' +title: '模板函数-内置函数' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,模板函数,内置函数,plus函数,minus函数,times函数,divide函数,字符串操作,日期格式化,url转义] +description: '介绍GoFrame框架中模板使用的内置函数,这些函数可以用于数学计算、HTML和URL转义、字符串处理、日期格式化以及数据格式转换,包括plus、minus、times、divide等函数,提升开发效率。' +--- + +## `plus/minus/times/divide` + +| 函数 | 说明 | 格式 | 示例 | 备注 | +| --- | --- | --- | --- | --- | +| `plus` | **加** | `{{.value1 \| plus .value2}}` | `{{2 \| plus 3}} => 5` | `3+2` | +| `minus` | **减** | `{{.value1 \| minus .value2}}` | `{{2 \| minus 3}} => 1` | `3-2` | +| `times` | **乘** | `{{.value1 \| times .value2}}` | `{{2 \| times 3}} => 6` | `3*2` | +| `divide` | **除** | `{{.value1 \| divide .value2}}` | `{{2 \| divide 3}} => 1.5` | `3/2` | + +## `text` + +``` +{{.value | text}} +``` + +将 `value` 变量值去掉HTML标签,仅显示文字内容(并且去掉 `script` 标签)。 示例: + +``` +{{"
    测试
    "|text}} +// 输出: 测试 +``` + +## `htmlencode/encode/html` + +``` +{{.value | htmlencode}} +{{.value | encode}} +{{.value | html}} +``` + +将 `value` 变量值进行html转义。 示例: + +``` +{{"
    测试
    "|html}} +// 输出: <div>测试</div> +``` + +## `htmldecode/decode` + +``` +{{.value | htmldecode}} +{{.value | decode}} +``` + +将 `value` 变量值进行html反转义。 示例: + +``` +{{"<div>测试</div>" | htmldecode}} +// 输出:
    测试
    +``` + +## `urlencode/url` + +``` +{{.url | url}} +``` + +将 `url` 变量值进行 `url` 转义。 示例: + +``` +{{"https://goframe.org" | url}} +// 输出: https%3A%2F%2Fgoframe.org +``` + +## `urldecode` + +``` +{{.url | urldecode}} +``` + +将 `url` 变量值进行 `url` 反转义。 示例: + +``` +{{"https%3A%2F%2Fgoframe.org"|urldecode}} +// 输出: https://goframe.org +``` + +## `date` + +``` +{{.timestamp | date .format}} +{{date .format .timestamp}} +{{date .format}} +``` + +将 `timestamp` 时间戳变量进行时间日期格式化,类似PHP的 `date` 方法, `format` 参数支持 [PHP date](http://php.net/manual/en/function.date.php) 方法格式,亦可参考 [时间管理-gtime](../../../组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) 。 + +当 `timestamp` 变量为 `空`(或者 `0`)时,表示以当前时间作为时间戳参数执行打印。 + +示例: + +``` +{{1540822968 | date "Y-m-d"}} +{{"1540822968" | date "Y-m-d H:i:s"}} +{{date "Y-m-d H:i:s"}} +// 输出: +// 2018-10-29 +// 2018-10-29 22:22:48 +// 2018-12-05 10:22:00 +``` + +## `compare` + +``` +{{compare .str1 .str2}} +{{.str2 | compare .str1}} +``` + +将 `str1` 和 `str2` 进行字符串比较,返回值: \- 0 : `str1` == `str2` \- 1 : `str1` \> `str2` \- -1 : `str1` < `str2` + +示例: + +``` +{{compare "A" "B"}} +{{compare "1" "2"}} +{{compare 2 1}} +{{compare 1 1}} +// 输出: +// -1 +// -1 +// 1 +// 0 +``` + +## `replace` + +``` +{{.str | replace .search .replace}} +{{replace .search .replace .str}} +``` + +将 `str` 中的 `search` 替换为 `replace`。 示例: + +``` +{{"I'm中国人" | replace "I'm" "我是"}} +// 输出: +// 我是中国人 +``` + +## `substr` + +``` +{{.str | substr .start .length}} +{{substr .start .length .str}} +``` + +将 `str` 从 `start` 索引位置(索引从0开始)进行字符串截取 `length`,支持中文,类似PHP的 `substr` 函数。 示例: + +``` +{{"我是中国人" | substr 2 -1}} +{{"我是中国人" | substr 2 2}} +// 输出: +// 中国人 +// 中国 +``` + +## `strlimit` + +``` +{{.str | strlimit .length .suffix}} +``` + +将 `str` 字符串截取 `length` 长度,支持中文,超过长度则追加 `suffix` 字符串到末尾。 示例: + +``` +{{"我是中国人" | strlimit 2 "..."}} +// 输出: +// 我是... +``` + +## `concat` + +``` +{{concat .str1 .str2 .str3...}} +``` + +拼接字符串。 示例: + +``` +{{concat "我" "是" "中" "国" "人"}} +// 输出: +// 我是中国人 +``` + +## `hidestr` + +``` +{{.str | hidestr .percent .hide}} +``` + +将 `str` 字符串按照 `percent` 百分比从字符串中间向两边隐藏字符(主要用于姓名、手机号、邮箱地址、身份证号等的隐藏),隐藏字符由 `hide` 变量定义。 支持中文,支持email格式。 示例: + +``` +{{"热爱GF热爱生活" | hidestr 20 "*"}} +{{"热爱GF热爱生活" | hidestr 50 "*"}} +// 输出: +// 热爱GF*爱生活 +// 热爱****生活 +``` + +## `highlight` + +``` +{{.str | highlight .key .color}} +``` + +将 `str` 字符串中的关键字 `key` 按照定义的颜色 `color` 进行前置颜色高亮。 示例: + +``` +{{"热爱GF热爱生活" | highlight "GF" "red"}} +// 输出: +// 热爱GF热爱生活 +``` + +## `toupper/tolower` + +``` +{{.str | toupper}} +{{.str | tolower}} +``` + +将 `str` 字符串进行大小写转换。 示例: + +``` +{{"gf" | toupper}} +{{"GF" | tolower}} +// 输出: +// GF +// gf +``` + +## `nl2br` + +``` +{{.str | nl2br}} +``` + +将 `str` 字符串中的 `\n/\r` 替换为html中的 `
    ` 标签。 示例: + +``` +{{"Go\nFrame" | nl2br}} +// 输出: +// Go
    Frame +``` + +## `dump` + +``` +{{dump .var}} +``` + +格式化打印变量,功能类似于 `g.Dump` 方法,常用于开发调试。 示例: + +``` +gview.Assign("var", g.Map{ + "name" : "john", +}) +``` + +``` +{{dump .var}} +// 输出: +// +``` + +## `map` + +``` +{{map .var}} +``` + +将模板变量转换为 `map[string]interface{}` 类型,常用于 `range...end` 遍历。 + +## `maps` + +``` +{{maps .var}} +``` + +将模板变量转换为 `[]map[string]interface{}` 类型,常用于 `range...end` 遍历。 + +## `json/xml/ini/yaml/yamli/toml` + +| 函数 | 说明 | 格式 | +| --- | --- | --- | +| `json` | 将模板变量转换为 `JSON` 格式字符串。 | `{{json .var}}` | +| `xml` | 将模板变量转换为 `XML` 格式字符串。 | `{{xml .var}}` | +| `ini` | 将模板变量转换为 `INI` 格式字符串。 | `{{ini .var}}` | +| `yaml` | 将模板变量转换为 `YAML` 格式字符串。 | `{{yaml .var}}` | +| `yamli` | 将模板变量转换为带有自定义缩进的 `YAML` 格式字符串。 | `{{yamli .var .indent}}` | +| `toml` | 将模板变量转换为 `TOML` 格式字符串。 | `{{toml .var}}` | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..9494e288d09 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\345\237\272\347\241\200\345\207\275\346\225\260.md" @@ -0,0 +1,252 @@ +--- +slug: '/docs/core/gview-funcs-index' +title: '模板函数-基础函数' +sidebar_position: 0 +hide_title: true +keywords: [Golang,GoFrame,模板引擎,基础函数,逻辑运算,数据比较,参数传递,函数调用,标准库,模板渲染] +description: 'Golang标准库中的基础函数及其在GoFrame框架中的改进使用。通过实用示例展示了如何在不同模板函数中进行参数传递,以及如何灵活调用函数。详细解读了and、or、not、call、index等函数的用法,以及GoFrame框架中对eq/ne/lt/le/gt/ge比较函数的自动类型转换优化。' +--- +:::tip +以下为 `Golang` 标准库的一些基础语法和基础函数, `GoFrame` 框架对部分基础函数做了必要的改进。 +::: +变量可以使用符号 `|` 在函数间传递 + +``` +{{.value | Func1 | Func2}} +``` + +使用括号 + +``` +{{printf "nums is %s %d" (printf "%d %d" 1 2) 3}} +``` + +## `and` + +``` +{{and .X .Y .Z}} +``` + +`and` 会逐一判断每个参数,将返回第一个为空的参数,否则就返回最后一个非空参数 + +## `call` + +``` +{{call .Field.Func .Arg1 .Arg2}} +``` + +`call` 可以调用函数,并传入参数 + +调用的函数需要返回 1 个值 或者 2 个值,返回两个值时,第二个值用于返回 `error` 类型的错误。返回的错误不等于 `nil` 时,执行将终止。 + +## `index` + +`index` 支持 `map`, `slice`, `array`, `string`,读取指定类型对应下标的值。 + +``` +{{index .Maps "name"}} +``` + +## `len` + +``` +{{printf "The content length is %d" (.Content|len)}} +``` + +返回对应类型的长度,支持类型: `map`, `slice`, `array`, `string`, `chan`。 + +## `not` + +`not` 返回输入参数的否定值。 + +例如,判断是否变量是否为空: + +``` +{{if not .Var}} +// 执行为空操作(.Var为空, 如: nil, 0, "", 长度为0的slice/map) +{{else}} +// 执行非空操作(.Var不为空) +{{end}} +``` + +## `or` + +``` +{{or .X .Y .Z}} +``` + +`or` 会逐一判断每个参数,将返回第一个非空的参数,否则就返回最后一个参数。 + +## `print` + +同 `fmt.Sprint`。 + +## `printf` + +同 `fmt.Sprintf`。 + +## `println` + +同 `fmt.Sprintln`。 + +## `urlquery` + +``` +{{urlquery "http://johng.cn"}} +``` + +将返回 + +``` +http%3A%2F%2Fjohng.cn +``` + +## `eq / ne / lt / le / gt / ge` + +这类函数一般配合在 `if` 中使用 + +``` +`eq`: arg1 == arg2 +`ne`: arg1 != arg2 +`lt`: arg1 < arg2 +`le`: arg1 <= arg2 +`gt`: arg1 > arg2 +`ge`: arg1 >= arg2 +``` + +`eq` 和其他函数不一样的地方是,支持多个参数。 + +``` +{{eq arg1 arg2 arg3 arg4}} +``` + +和下面的逻辑判断相同: + +``` +arg1==arg2 || arg1==arg3 || arg1==arg4 ... +``` + +与 `if` 一起使用 + +``` +{{if eq true .Var1 .Var2 .Var3}}...{{end}} +``` + +``` +{{if lt 100 200}}...{{end}} +``` + +例如,判断变量不为空时执行: + +``` +{{if .Var}} +// 执行非空操作(.Var不为空) +{{else}} +// 执行为空操作(.Var为空, 如: nil, 0, "", 长度为0的slice/map) +{{end}} +``` + +### 对比函数改进 + +`GoFrame` 框架模板引擎对标准库自带的对比模板函数 `eq/ne/lt/le/gt/ge` 做了必要的改进,以便支持任意数据类型的比较。例如,在标准库模板中的以下比较: + +``` +{{eq 1 "1"}} +``` + +将会引发错误: + +``` +panic: template: at : error calling eq: incompatible types for comparison +``` + +由于比较的两个参数不是同一数据类型,因此引发了 `panic` 错误。 + +在 `GoFrame` 框架的模板引擎中,将会自动将两个参数进行数据转换,转换为同一类型后再进行比较,这样的开发体验更好、灵活性更高。如果两个参数均为整型变量(或者整型字符串),那么将会转换为整型进行比较,否则转换为字符串变量进行比较(区分大小写)。 + +### 改进运行示例 + +我们来看一个 `GoFrame` 框架的模板引擎中的对比模板函数运行示例。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + tplContent := ` +eq: +eq "a" "a": {{eq "a" "a"}} +eq "1" "1": {{eq "1" "1"}} +eq 1 "1": {{eq 1 "1"}} + +ne: +ne 1 "1": {{ne 1 "1"}} +ne "a" "a": {{ne "a" "a"}} +ne "a" "b": {{ne "a" "b"}} + +lt: +lt 1 "2": {{lt 1 "2"}} +lt 2 2 : {{lt 2 2 }} +lt "a" "b": {{lt "a" "b"}} + +le: +le 1 "2": {{le 1 "2"}} +le 2 1 : {{le 2 1 }} +le "a" "a": {{le "a" "a"}} + +gt: +gt 1 "2": {{gt 1 "2"}} +gt 2 1 : {{gt 2 1 }} +gt "a" "a": {{gt "a" "a"}} + +ge: +ge 1 "2": {{ge 1 "2"}} +ge 2 1 : {{ge 2 1 }} +ge "a" "a": {{ge "a" "a"}} +` + content, err := g.View().ParseContent(context.TODO(), tplContent, nil) + if err != nil { + panic(err) + } + fmt.Println(content) +} +``` + +运行后,输出结果为: + +``` +eq: +eq "a" "a": true +eq "1" "1": true +eq 1 "1": true + +ne: +ne 1 "1": false +ne "a" "a": false +ne "a" "b": true + +lt: +lt 1 "2": true +lt 2 2 : false +lt "a" "b": true + +le: +le 1 "2": true +le 2 1 : false +le "a" "a": true + +gt: +gt 1 "2": false +gt 2 1 : true +gt "a" "a": false + +ge: +ge 1 "2": false +ge 2 1 : true +ge "a" "a": true +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..7c860388a8e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\207\275\346\225\260-\350\207\252\345\256\232\344\271\211\345\207\275\346\225\260.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gview-funcs-custom' +title: '模板函数-自定义函数' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板函数,自定义函数,视图对象,全局绑定,对象赋值,方法调用,管道传参,模板解析] +description: '在GoFrame框架中自定义模板函数,开发者可以将这些函数全局绑定到指定的视图对象中。此外,还可以将自定义对象赋值给模板并通过该对象调用其封装的方法。示例代码展示了如何定义和绑定模板函数,以及使用普通方式和管道方式传递参数进行模板解析。' +--- + +## 基本介绍 + +开发者可以自定义模板函数,全局绑定模板函数到指定的视图对象中。 +:::tip +也可以将自定义的对象赋值给模板,随后通过对象来调用其封装的方法。 +::: +## 使用示例 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +// 用于测试的带参数的内置函数 +func funcHello(name string) string { + return fmt.Sprintf(`Hello %s`, name) +} + +func main() { + // 绑定全局的模板函数 + g.View().BindFunc("hello", funcHello) + + // 普通方式传参 + parsed1, err := g.View().ParseContent(context.TODO(), `{{hello "GoFrame"}}`, nil) + if err != nil { + panic(err) + } + fmt.Println(string(parsed1)) + + // 通过管道传参 + parsed2, err := g.View().ParseContent(context.TODO(), `{{"GoFrame" | hello}}`, nil) + if err != nil { + panic(err) + } + fmt.Println(string(parsed2)) +} +``` + +执行后,输出结果为: + +``` +Hello GoFrame +Hello GoFrame +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" new file mode 100644 index 00000000000..af56c6d1197 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\207\275\346\225\260.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/core/gview-funcs' +title: '模板引擎-模板函数' +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,模板函数,文档页面,前端开发,动态模板,视图渲染,函数扩展,编程指南] +description: '在GoFrame框架中使用模板引擎与模板函数,以便在Web应用程序中实现动态模板和视图渲染。通过自定义和扩展模板函数,开发者可以更高效地进行前端开发,提升应用的灵活性。' +--- +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..30cc73468af --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\217\230\351\207\217.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gview-variable' +title: '模板引擎-模板变量' +sidebar_position: 3 +hide_title: true +keywords: [模板引擎,模板变量,自定义对象,对象属性,对象方法,GoFrame,GoFrame框架,模板解析,WebServer内置变量,GoFrame教程] +description: '模板引擎中如何使用自定义对象作为模板变量,并在模板中访问对象的属性和调用方法。通过示例,详细说明了如何在GoFrame框架中实现模板内容解析,以及对象指针和对象变量的使用区别与方法调用规则,帮助开发者更好地掌握GoFrame框架中的模板技术。' +--- + +## 内置变量 + +`WebServer` 内置变量请参考 [数据返回-模板解析](../../WEB服务开发/数据返回/数据返回-模板解析.md) 章节。 + +## 变量对象 + +我们可以在模板中使用自定义的对象,并可在模板中访问对象的属性及调用其方法。 + +示例: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" +) + +type T struct { + Name string +} + +func (t *T) Hello(name string) string { + return "Hello " + name +} + +func (t *T) Test() string { + return "This is test" +} + +func main() { + t := &T{"John"} + v := g.View() + content := `{{.t.Hello "there"}}, my name's {{.t.Name}}. {{.t.Test}}.` + if r, err := v.ParseContent(context.TODO(), content, g.Map{"t": t}); err != nil { + g.Dump(err) + } else { + g.Dump(r) + } +} +``` + +其中,赋值给模板的变量既可以是 `对象指针` 也可以是 `对象变量`。但是注意定义的对象方法,如果为对象指针那么只能调用方法接收器为对象指针的方法;如果为对象变量,那么只能调用方法接收器为对象的方法。 + +执行后,输出结果为: + +``` +Hello there, my name's John. This is test. +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" new file mode 100644 index 00000000000..c454002743c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\345\270\203\345\261\200.md" @@ -0,0 +1,168 @@ +--- +slug: '/docs/core/gview-layout' +title: '模板引擎-模板布局' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,gview,模板引擎,layout,define,template,include,模板布局,模板变量] +description: 'GoFrame框架中的gview模板引擎的layout模板布局方式。gview支持两种布局方式:通过define和template标签进行内容块管理,以及通过include标签进行模板嵌入。这两种方式均支持模板变量的传递。示例代码演示了如何在GoFrame框架中使用这些模板引擎技术。' +--- + +`gview` 模板引擎支持两种 `layout` 模板布局方式: + +1. `define` + `template` 方式 +2. `include` 模板嵌入方式 + +这两种方式均支持对模板变量的传入。 + +### `define` + `template` + +由于 `gview` 底层采用了 `ParseFiles` 方式批量解析模板文件,因此可以使用 `define` 标签定义模板内容块,通过 `template` 标签在其他任意的模板文件中引入指定的模板内容块。 `template` 标签支持跨模板引用,也就是说 `define` 标签定义的模板内容块可能是在其他模板文件中, `template` 也可以随意引入。 +:::warning +注意: +- 为嵌套的子模板传递模板变量时,应当使用: `{{template "xxx" .}}` 的语法。 +- 模板文件的后缀名,要跟 `define template` 文件的后缀名保持一致。 +::: +使用示例: + +![](/markdown/5c50dcf4b78634b414c3857035097292.png) + +1. `layout.html` +```html + + + + GoFrame Layout + {{template "header" .}} + + +
    + {{template "container" .}} +
    + + + +``` + +2. `header.html` +```html +{{define "header"}} +

    {{.header}}

    +{{end}} +``` + +1. `container.html` +```html +{{define "container"}} +

    {{.container}}

    +{{end}} +``` + +1. `footer.html` +```html +{{define "footer"}} +

    {{.footer}}

    +{{end}} +``` + +1. `main.go` +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "header": "This is header", + "container": "This is container", + "footer": "This is footer", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,访问 [http://127.0.0.1:8199](http://127.0.0.1:8199/) 结果如下: + +![](/markdown/8826a2512ed91b4c9e7b77eabad4ae2c.png) + +### `include` 模板嵌入 + +当然我们也可以使用 `include` 标签来实现页面布局。 +:::warning +注意,为嵌套的子模板传递模板变量时,应当使用: `{{include "xxx" .}}` 的语法。 +::: +使用示例: + +![](/markdown/21b9ad277927db37879d5513766557c2.png) + +1. `layout.html` +```html +{{include "header.html" .}} +{{include .mainTpl .}} +{{include "footer.html" .}} +``` + +2. `header.html` +```html +

    HEADER

    +``` + +3. `footer.html` +```html +

    FOOTER

    +``` + +4. `main1.html` +```html +

    MAIN1

    +``` + +1. `main2.html` +```html +

    MAIN2

    +``` + +1. `main.go` +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/main1", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "mainTpl": "main/main1.html", + }) + }) + s.BindHandler("/main2", func(r *ghttp.Request) { + r.Response.WriteTpl("layout.html", g.Map{ + "mainTpl": "main/main2.html", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + + +执行后,访问不同的路由地址,将会看到不同的结果: + +1. [http://127.0.0.1:8199/main1](http://127.0.0.1:8199/main1) + +![](/markdown/88d87dc79a4aa226d1c20312c3aeaff0.png) + +2. [http://127.0.0.1:8199/main2](http://127.0.0.1:8199/main2) + +![](/markdown/8db789d456e0422ca3796242b89b8b44.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" new file mode 100644 index 00000000000..380b3d4b9ed --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\346\240\207\347\255\276.md" @@ -0,0 +1,202 @@ +--- +slug: '/docs/core/gview-tags' +title: '模板引擎-模板标签' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,模板标签,gview.SetDelimiters,pipeline,if...else,range,define,template,include] +description: 'GoFrame框架中的模板引擎与模板标签基本用法,包括如何自定义模板闭合标签,使用pipeline在模板中传递数据,条件语句if...else的使用,range用于迭代slice、map等类型,使用define定义模板块,template标签结合define实现模板嵌套和复用,以及include标签在GoFrame框架中的使用差异。' +--- + +模板引擎默认使用了 `{{` 和 `}}` 作为左右闭合标签,开发者可通过 `gview.SetDelimiters` 方法设置自定义的模板闭合标签。 + +使用 `.` 来访问当前对象的值(模板局部变量)。 + +使用 `$` 来引用当前模板根级的上下文(模板全局变量)。 + +使用 `$var` 来访问特定的模板变量。 + +**模板中支持的 `go` 语言符号** + +``` +{{"string"}} // 一般 string +{{`raw string`}} // 原始 string +{{'c'}} // byte +{{print nil}} // nil 也被支持 +``` + +**模板中的 `pipeline`** + +可以是上下文的变量输出,也可以是函数通过管道传递的返回值 + +``` +{{. | FuncA | FuncB | FuncC}} +``` + +当 `pipeline` 的值等于: + +- `false` 或 `0` +- `nil的指针` 或 `interface` +- 长度为 `0` 的 `array`, `slice`, `map`, `string` + +那么这个 `pipeline` 被认为是空。 +:::warning +需要注意:在 `gf` 模板引擎中,当模板中展示的指定变量不存在时,将会显示为空(标准库模板引擎会展示 ``)。 +::: +## `if … else … end` + +``` +{{if pipeline}}...{{end}} +``` + +`if` 判断时, `pipeline` 为空时,相当于判断为 `false`。 + +支持嵌套的循环 + +``` +{{if .condition}} + ... +{{else}} + {{if .condition2}} + ... + {{end}} +{{end}} +``` + +也可以使用 `else if` 进行 + +``` +{{if .condition}} + ... +{{else if .condition2}} + ... +{{else}} + ... +{{end}} +``` + +## `range … end` + +``` +{{range pipeline}} {{.}} {{end}} +``` + +`pipeline` 支持的类型为 `slice`, `map`, `channel`。 + +注意:在 `range` 循环内部, `.` 符号会被覆盖为以上类型的子元素(局部变量)。如果想在循环中访问外部变量(全局变量),请加上 `$` 符号,如: `{{$.Session.Name}}` + +此外,对应的值长度为 `0` 时, `range` 不会执行, `.` 不会改变。 + +例如,遍历 `map`: + +``` +{{range $key, $value := .MapContent}} + {{$key}}:{{$value}} +{{end}} +``` + +例如,遍历 `slice`: + +``` +{{range $index, $elem := .SliceContent}} + {{range $key, $value := $elem}} + {{$key}}:{{$value}} + {{end}} +{{end}} +``` + +## `with … end` + +``` +{{with pipeline}}...{{end}} +``` + +`with` 用于重定向 `pipeline` + +``` +{{with .Field.NestField.SubField}} + {{.Var}} +{{end}} +``` + +## `define` + +`define` 可以用来 **自定义模板内容块**(给一段模板内容定义一个模板名称),可用于模块定义和模板嵌套(使用在 `template` 标签中)。 + +``` +{{define "loop"}} +
  • {{.Name}}
  • +{{end}} +``` + +其中 `loop` 为该模板内容块的名称,随后可使用 `template` 标签调用模板: + +``` +
      + {{range .Items}} + {{template "loop" .}} + {{end}} +
    +``` +:::warning +`define` 标签需要结合 `template` 标签一起使用,并且支持跨模板使用(在同一模板目录/子目录下有效,原理是使用的 `ParseFiles` 方法解析模板文件)。 +::: +## `template` + +``` +{{template "模板名称" pipeline}} +``` + +将对应的上下文 `pipeline` 传给模板,才可以在模板中调用。 + +注意: `template` 标签参数为 `模板名称`,而不是模板文件路径, `template` 标签不支持模板文件路径。 +:::warning +`template` 标签需要结合 `define` 标签一起使用,并且支持跨模板使用(在同一模板目录/子目录下有效,原理是使用的 `ParseFiles` 方法解析模板文件)。 +::: +## `include` + +**该标签为 `gf` 框架模板引擎新增标签** + +``` +{{include "模板文件名(需要带完整文件名后缀)" pipeline}} +``` + +在模板中可以使用 `include` 标签载入其他模板(任意路径),模板文件名支持 **相对路径** 以及文件的系统 **绝对路径**。如果想要把当前模板的模板变量传递给子模板(嵌套模板),可以这样: + +``` +{{include "模板文件名(需要带完整文件名后缀)" .}} +``` + +与 `template` 标签的区别是: `include` 仅支持 **文件路径**,不支持 **模板名称**;而 `tempalte` 标签仅支持 **模板名称**,不支持 **文件路径**。 + +## 注释 + +允许多行文本注释,不允许嵌套。 + +``` +{{/* +comment content +support new line +*/}} +``` + +## 删除空白符号 + +删除移除空格换行符 + +``` +# 使用{{-语法去除模板内容左侧的所有空白符号, 使用-}}去除模板内容右侧的所有空白符号。 + +# 注意:-要紧挨{{和}},同时与模板值之间需要使用空格分隔。 + +{{- .Name -}} + +{{- range $key, $value := .list}} + "{{$value}}" +{{- end}} +``` + +## 更多使用说明 + +[template package - html/template - Go Packages](https://pkg.go.dev/html/template) + +[template package - text/template - Go Packages](https://pkg.go.dev/text/template) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..7721dbfb51f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216-\346\250\241\346\235\277\351\205\215\347\275\256.md" @@ -0,0 +1,131 @@ +--- +slug: '/docs/core/gview-config' +title: '模板引擎-模板配置' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,配置管理,视图组件,模板配置,gview,模板解析,XSS编码,单例对象] +description: 'GoFrame框架的视图组件是核心之一,支持便捷的配置管理。本文详细介绍了通过配置文件管理视图组件的方式,包括配置项定义和示例。同时介绍了如何使用SetConfigWithMap方法进行特定配置的设置,确保模板解析的正常进行。' +--- + +视图组件是 `GoFrame` 框架核心的组件之一,当然也支持非常方便的配置管理功能。 + +## 配置对象 + +配置对象定义: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gview#Config](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview#Config) + +## 配置文件 + +视图组件支持配置文件,当使用 `g.View(单例名称)` 获取 `View` 单例对象时,将会自动通过默认的配置管理对象获取对应的 `View` 配置。默认情况下会读取 `viewer.单例名称` 配置项,当该配置项不存在时,将会读取 `viewer` 配置项。 + +完整配置文件配置项及说明如下,其中配置项名称不区分大小写: + +``` +[viewer] + Paths = ["/var/www/template"] # 模板文件搜索目录路径,建议使用绝对路径。默认为当前程序工作路径 + DefaultFile = "index.html" # 默认解析的模板引擎文件。默认为"index.html" + Delimiters = ["${", "}"] # 模板引擎变量分隔符号。默认为 ["{{", "}}"] + AutoEncode = false # 是否默认对变量内容进行XSS编码。默认为false + [viewer.Data] # 自定义的全局Key-Value键值对,将在模板解析中可被直接使用到 + Key1 = "Value1" + Key2 = "Value2" +``` + +### 示例1,默认配置项 + +``` +[viewer] + paths = ["template", "/var/www/template"] + defaultFile = "index.html" + delimiters = ["${", "}"] + [viewer.data] + name = "gf" + version = "1.10.0" +``` + +随后可以使用 `g.View()` 获取默认的单例对象时自动获取并设置该配置。 + +### 示例2,多个配置项 + +多个 `View` 对象的配置示例: + +``` +[viewer] + paths = ["template", "/var/www/template"] + defaultFile = "index.html" + delimiters = ["${", "}"] + [viewer.data] + name = "gf" + version = "1.10.0" + [viewer.view1] + defaultFile = "layout.html" + delimiters = ["${", "}"] + [viewer.view2] + defaultFile = "main.html" + delimiters = ["#{", "}"] +``` + +我们可以通过单例对象名称获取对应配置的 `View` 单例对象: + +```go +// 对应 viewer.view1 配置项 +v1 := g.View("view1") +// 对应 viewer.view2 配置项 +v2 := g.View("view2") +// 对应默认配置项 viewer +v3 := g.View("none") +// 对应默认配置项 viewer +v4 := g.View() +``` + +### 配置方法 + +方法列表: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gview](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview) + +简要说明: + +1. 可以通过 `SetConfig` 及 `SetConfigWithMap` 来设置。 +2. 也可以使用 `View` 对象的 `Set*` 方法进行特定配置的设置。 +3. 主要注意的是,配置项在 `View` 对象执行视图解析之前设置,避免并发安全问题。 + +### `SetConfigWithMap` 方法 + +我们可以使用 `SetConfigWithMap` 方法通过 `Key-Value` 键值对来设置/修改 `View` 的特定配置,其余的配置使用默认配置即可。其中 `Key` 的名称即是 `Config` 这个 `struct` 中的属性名称,并且不区分大小写,单词间也支持使用 `-`/ `_`/ `空格` 符号连接,具体可参考 [类型转换-Struct转换](../类型转换/类型转换-Struct转换.md) 章节转换规则。 + +简单示例: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gview" +) + +func main() { + view := gview.New() + view.SetConfigWithMap(g.Map{ + "Paths": []string{"template"}, + "DefaultFile": "index.html", + "Delimiters": []string{"${", "}"}, + "Data": g.Map{ + "name": "gf", + "version": "1.10.0", + }, + }) + result, err := view.ParseContent(context.TODO(), "hello ${.name}, version: ${.version}") + if err != nil { + panic(err) + } + fmt.Println(result) +} +``` + +其中 `DefaultFile` 表示默认解析的模板文件,键名也可以使用 `defaultFile`, `default-File`, `default_file`, `default file`,其他配置属性以此类推。 + +## 注意事项 + +经常有小伙伴问,为神马我的模板解析没有生效?为神马页面上直接将我写的标签原木原样显示出来了? + +这个时候请检查你的配置文件中是否有设置模板标签,常见的是 `delimiters` 设置为了 `["${", "}"]`,但是模板中使用的是 `["{{", "}}"]`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" new file mode 100644 index 00000000000..b1c4a9ad948 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\346\250\241\346\235\277\345\274\225\346\223\216/\346\250\241\346\235\277\345\274\225\346\223\216.md" @@ -0,0 +1,196 @@ +--- +slug: '/docs/core/gview' +title: '模板引擎' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,gview,模板管理,缓存机制,自动检测更新,模板目录,模板变量,模板函数] +description: 'GoFrame框架的模板引擎提供了简单、易用、强大的功能,支持多模板目录搜索、layout模板设计以及模板文件自动更新等特性。通过gview模块实现通用视图管理,支持模板对象单例模式及视图对象管理器的使用,方便进行模板目录配置和模板变量、函数渲染。' +--- + +## 模板引擎特点 + +1. 简单、易用、强大; +2. 支持多模板目录搜索; +3. 支持 `layout` 模板设计; +4. 支持模板视图对象单例模式; +5. 与配置管理模块原生集成,使用方便; +6. 底层采用了二级缓存设计,性能高效; +7. 新增模板标签及大量的内置模板变量、模板函数; +8. 支持模板文件修改后自动更新缓存机制,对开发过程更友好; +9. `define`/ `template` 标签支持跨模板调用(同一模板路径包括子目录下的模板文件); +10. `include` 标签支持任意路径的模板文件引入; + +## 通用视图管理 + +通用视图管理即使用原生的模板引擎 `gview` 模块来实现模板管理,包括模板读取展示,模板变量渲染等等。可以使用通过方法 `gview.Instance` 来获取视图单例对象,并可以按照单例对象名称进行获取。同时也可以通过对象管理器 `g.View()` 来获取默认的单例 `gview` 对象。 + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gview](https://pkg.go.dev/github.com/gogf/gf/v2/os/gview) + +简要说明: + +1. `gview.Get` 用于根据给定的一个模板目录路径,获得对应的单例模板引擎对象; +2. `gview.New` 同样可以根据给定的模板目录路径创建模板引擎对象,但没有单例管理; +3. `SetPath/AddPath` 用于设置/添加当前模板引擎对象的模板目录路径,其中 `SetPath` 会覆盖所有的模板目录设置,推荐使用 `AddPath`; +4. `Assign/Assigns` 用于设置模板变量,通过该模板引擎解析的所有模板均可以使用这些模板变量; +5. `BindFunc` 用于绑定模板函数,具体使用方法参考后续示例程序; +6. `Parse/ParseContent` 用于解析模板文件/内容,可以在解析时给定临时的模板变量及模板函数; +7. `SetDelimiters` 用于设置该模板引擎对象的模板解析分隔符号,默认为 `{{ }}`(与 `vuejs` 前端框架有冲突); +:::warning +需要注意,从 `goframe v1.16` 版本开始,所有模板的解析方法都额外增加了第一个输入参数为 `Context` 上下文变量的传递。 +::: +### 示例1,解析模板文件 + +`index.tpl` + +``` +id:{{.id}}, name:{{.name}} +``` + +`main.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/template", func(r *ghttp.Request) { + r.Response.WriteTpl("index.tpl", g.Map{ + "id": 123, + "name": "john", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,访问 [http://127.0.0.1:8199/template](http://127.0.0.1:8199/template) 可以看到解析后的内容为: `id:123, name:john` + +### 示例2,解析模板内容 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/template", func(r *ghttp.Request){ + tplContent := `id:{{.id}}, name:{{.name}}` + r.Response.WriteTplContent(tplContent, g.Map{ + "id" : 123, + "name" : "john", + }) + }) + s.SetPort(8199) + s.Run() +} +``` + +执行后,访问 [http://127.0.0.1:8199/template](http://127.0.0.1:8199/template) 可以看到解析后的内容为: `id:123, name:john` + +### 示例3,自定义模板分隔符 + +在项目中我们经常会遇到 `Golang` 默认模板变量分隔符号与 `Vue` 的变量分隔符号冲突的情况(都使用的是 `{{ }}`),我们可以使用 `SetDelimiters` 方法来自定义全局的 `Golang` 模板变量分隔符号: + +```go +// main.go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + v := g.View() + v.SetDelimiters("${", "}") + b, err := v.Parse( + context.TODO(), + "gview_delimiters.tpl", + map[string]interface{}{ + "k": "v", + }) + fmt.Println(err) + fmt.Println(b) +} +``` + +```html + +test.tpl content, vars: ${.} +``` + +执行后,输出结果为: + +```html + +test.tpl content, vars: map[k:v] +``` + +## 目录配置方法 + +`GoFrame` 框架的模板引擎支持非常灵活的多目录自动搜索功能,通过 `SetPath` 可以修改模板目录为唯一的一个目录地址,同时,我们可以通过 `AddPath` 方法添加多个搜索目录,模板引擎底层将会按照添加目录的顺序作为优先级进行自动检索。直到检索到一个匹配的文件路径为止,如果在所有搜索目录下查找不到模板文件,那么会返回失败。 + +**默认目录配置**: + +`gview` 视图对象初始化时,默认会自动添加以下模板文件搜索目录: + +1. **当前工作目录及其下的 `template` 目录**:例如当前的工作目录为 `/home/www` 时,将会添加 `/home/www` 及 `/home/www/template`; +2. **当前可执行文件所在目录及其下的 `template` 目录**:例如二进制文件所在目录为 `/tmp` 时,将会添加 `/tmp` 及 `/tmp/template`; +3. **当前 `main` 源代码包所在目录及其下的 `template` 目录**(仅对源码开发环境有效):例如 `main` 包所在目录为 `/home/john/workspace/gf-app` 时,将会添加 `/home/john/workspace/gf-app` 及 `/home/john/workspace/gf-app/template`; + +## 修改模板目录 + +我们可以通过以下方式修改视图对象的模板文件搜索目录,视图对象将会只在该指定目录执行配置文件检索: + +1. (推荐)单例模式获取全局View对象,通过 `SetPath` 方法手动修改; +2. 修改命令行启动参数 - `gf.gview.path`; +3. 修改指定的环境变量 - `GF_GVIEW_PATH`; + +例如,我们的执行程序文件为 `main`,那么可以通过以下方式修改模板引擎的模板目录(Linux下): + +1. (推荐)通过单例模式 + +```go +g.View().SetPath("/opt/template") +``` + +2. 通过命令行参数 + +```shell +./main --gf.gview.path=/opt/template/ +``` + +3. 通过环境变量 + - 启动时修改环境变量: + + ```shell + GF_GVIEW_PATH=/opt/config/; ./main + ``` + + - 使用 `genv` 模块来修改环境变量: + + ```go + genv.Set("GF_GVIEW_PATH", "/opt/template") + ``` + +## 自动检测更新 + +模板引擎使用了精心设计的 **缓存机制**,当模板文件第一次被读取后会被缓存到内存,下一次读取时将会直接从缓存中获取,以提高执行效率。并且,模板引擎提供了对模板文件的 **自动检测更新机制**,当模板文件在外部被修改后,模板引擎能够即时地监控到并刷新模板文件的缓存内容。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..b3782574008 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Converter\347\211\271\346\200\247.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/core/gconv-converter' +title: '类型转换-Converter特性' +sidebar_position: 5 +hide_title: true +keywords: [类型转换,Converter特性,GoFrame,自定义转换,结构体转换,属性转换,别名类型,gconv.Scan,gconv.ConvertWithRefer,数据结构] +description: '从GoFrame框架v2.6.2版本开始的转换组件新增的Converter特性,允许开发者自定义类型转换逻辑。通过示例代码展示了如何使用Converter特性进行自定义数据结构和别名类型的转换方法注册和应用,以及如何借助gconv.Scan和gconv.ConvertWithRefer方法处理这些类型的转换。' +--- + +从框架 v2.6.2 版本开始,转换组件提供了 `Converter` 特性,允许开发者可以自定义 `Converter`转换方法指定特定类型之间的转换逻辑。 + +## 转换方法定义 + +转换方法定义如下: + +```go +func(T1) (T2, error) +``` + +其中 `T1` 需要为非指针对象, `T2` 需要为指针类型,如果类型不正确转换方法注册将会报错。 +:::tip +输入参数( `T1`)必须为非指针对象的设计,目的是为了保证输入参数的安全,尽可能避免在转换方法内部修改输入参数引起作用域外问题。 +::: +注册转换方法的函数如下: + +```go +// RegisterConverter to register custom converter. +// It must be registered before you use this custom converting feature. +// It is suggested to do it in boot procedure of the process. +// +// Note: +// 1. The parameter `fn` must be defined as pattern `func(T1) (T2, error)`. +// It will convert type `T1` to type `T2`. +// 2. The `T1` should not be type of pointer, but the `T2` should be type of pointer. +func RegisterConverter(fn interface{}) (err error) +``` + +## 结构体类型转换 + +常见的自定义数据结构是结构体之间的类型转换。我们来看两个例子。 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/util/gconv" +) + +type Src struct { + A int +} + +type Dst struct { + B int +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var ( + src = Src{A: 1} + dst *Dst + ) + err = gconv.Scan(src, &dst) + if err != nil { + panic(err) + } + + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var ( + srcWrap = SrcWrap{Src{A: 1}} + dstWrap *DstWrap + ) + err = gconv.Scan(srcWrap, &dstWrap) + if err != nil { + panic(err) + } + + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +在该示例代码中,演示了两种类型转换场景:自定义结构体转换,以及结构体作为属性的自动转换。转换方法使用的是通用的结构体转换方法 `gconv.Scan`,该方法在内部实现中会自动判断如果存在自定义类型转换函数,会优先使用自定义类型转换函数,否则会走默认的转换逻辑。 + +执行后,终端输出: + +``` +src: {1} +dst: &{1} +srcWrap: {{1}} +dstWrap: &{{1}} +``` + +除了使用 `gconv.Scan` 方法外,我们也可以使 `gconv.ConvertWithRefer` 方法实现类型转换,两者的效果都是一样的: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/util/gconv" +) + +type Src struct { + A int +} + +type Dst struct { + B int +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A}, nil +} + +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var src = Src{A: 1} + dst := gconv.ConvertWithRefer(src, Dst{}) + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var srcWrap = SrcWrap{Src{A: 1}} + dstWrap := gconv.ConvertWithRefer(srcWrap, &DstWrap{}) + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +## 别名类型转换 + +我们也可以使用 `Converter`特性实现 **别名类型** 的转换。别名类型不限于结构体,也可以是 `int, string` 等基础类型的别名。我们来看两个例子。 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +type MyTime = *gtime.Time + +type Src struct { + A MyTime +} + +type Dst struct { + B string +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A.Format("Y-m-d")}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var ( + src = Src{A: gtime.Now()} + dst *Dst + ) + err = gconv.Scan(src, &dst) + if err != nil { + panic(err) + } + + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var ( + srcWrap = SrcWrap{Src{A: gtime.Now()}} + dstWrap *DstWrap + ) + err = gconv.Scan(srcWrap, &dstWrap) + if err != nil { + panic(err) + } + + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` + +代码中的 `type xxx = yyy`是由于 `*gtime.Time` 类型的需要,其他类型可根据需要是否使用别名符号 `=`,例如基础类型 `int, string` 等是不需要别名符号的。 + +执行后,终端输出: + +``` +src: {2024-01-22 21:45:28} +dst: &{2024-01-22} +srcWrap: {{2024-01-22 21:45:28}} +dstWrap: &{{2024-01-22}} +``` + +同样的,除了使用 `gconv.Scan` 方法外,我们也可以使用 `gconv.ConvertWithRefer` 方法实现类型转换,两者的效果都是一样的: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/util/gconv" +) + +type MyTime = *gtime.Time + +type Src struct { + A MyTime +} + +type Dst struct { + B string +} + +type SrcWrap struct { + Value Src +} + +type DstWrap struct { + Value Dst +} + +// SrcToDstConverter is custom converting function for custom type. +func SrcToDstConverter(src Src) (dst *Dst, err error) { + return &Dst{B: src.A.Format("Y-m-d")}, nil +} + +// SrcToDstConverter is custom converting function for custom type. +func main() { + // register custom converter function. + err := gconv.RegisterConverter(SrcToDstConverter) + if err != nil { + panic(err) + } + + // custom struct converting. + var src = Src{A: gtime.Now()} + dst := gconv.ConvertWithRefer(src, &Dst{}) + fmt.Println("src:", src) + fmt.Println("dst:", dst) + + // custom struct attributes converting. + var srcWrap = SrcWrap{Src{A: gtime.Now()}} + dstWrap := gconv.ConvertWithRefer(srcWrap, &DstWrap{}) + fmt.Println("srcWrap:", srcWrap) + fmt.Println("dstWrap:", dstWrap) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..749189c8d2b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Map\350\275\254\346\215\242.md" @@ -0,0 +1,218 @@ +--- +slug: '/docs/core/gconv-map' +title: '类型转换-Map转换' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,类型转换,Map转换,GoFrame框架,属性标签,递归转换,自定义标签,MapDeep,struct转换,gconv] +description: '使用GoFrame框架中的gconv.Map方法实现类型转换,包括将任意map或struct/*struct类型转换为map[string]interface{}类型。支持属性标签和自定义标签,并可通过MapDeep方法实现递归转换,解析出嵌套对象的详细结构,适合多层次数据处理。' +--- + +`gconv.Map` 支持将任意的 `map` 或 `struct`/ `*struct` 类型转换为常用的 `map[string]interface{}` 类型。当转换参数为 `struct`/ `*struct` 类型时,支持自动识别 `struct` 的 `c/gconv/json` 标签,并且可以通过 `Map` 方法的第二个参数 `tags` 指定自定义的转换标签,以及多个标签解析的优先级。如果转换失败,返回 `nil`。 +:::tip +属性标签:当转换 `struct`/ `*struct` 类型时,支持 `c/gconv/json` 属性标签,也支持 `-` 及 `omitempty` 标签属性。当使用 `-` 标签属性时,表示该属性不执行转换;当使用 `omitempty` 标签属性时,表示当属性为空时(空指针 `nil`, 数字 `0`, 字符串 `""`, 空数组 `[]` 等)不执行转换。具体请查看随后示例。 +::: +常用转换方法: + +```go +func Map(value interface{}, tags ...string) map[string]interface{} +func MapDeep(value interface{}, tags ...string) map[string]interface{} +``` + +其中, `MapDeep` 支持递归转换,即会递归转换属性中的 `struct`/ `*struct` 对象。 +:::tip +更多的 `map` 相关转换方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: +## 基本示例 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int `c:"uid"` + Name string `c:"name"` + } + // 对象 + g.Dump(gconv.Map(User{ + Uid: 1, + Name: "john", + })) + // 对象指针 + g.Dump(gconv.Map(&User{ + Uid: 1, + Name: "john", + })) + + // 任意map类型 + g.Dump(gconv.Map(map[int]int{ + 100: 10000, + })) +} +``` + +执行后,终端输出: + +``` +{ + "name": "john", + "uid": 1, +} + +{ + "name": "john", + "uid": 1, +} + +{ + "100": 10000, +} +``` + +## 属性标签 + +我们可以通过 `c/gconv/json` 标签来自定义转换后的 `map` 键名,当多个标签存在时,按照 `gconv/c/json` 的标签顺序进行优先级识别。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string `c:"-"` + NickName string `c:"nickname, omitempty"` + Pass1 string `c:"password1"` + Pass2 string `c:"password2"` + } + user := User{ + Uid: 100, + Name: "john", + Pass1: "123", + Pass2: "456", + } + g.Dump(gconv.Map(user)) +} +``` + +执行后,终端输出: + +``` +{ + "Uid": 100, + "password1": "123", + "password2": "456", + "nickname": "", +} +``` + +## 自定义标签 + +此外,我们也可以给 `struct` 的属性自定义自己的标签名称,并在 `map` 转换时通过第二个参数指定标签优先级。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Id int `c:"uid"` + Name string `my-tag:"nick-name" c:"name"` + } + user := &User{ + Id: 1, + Name: "john", + } + g.Dump(gconv.Map(user, "my-tag")) +} +``` + +执行后,输出结果为: + +``` +{ + "nick-name": "john", + "uid": 1, +} +``` + +## 递归转换 + +当参数为 `map`/ `struct`/ `*struct` 类型时,如果键值/属性为一个对象(或者对象指针)时,并且不是 `embedded` 结构体且没有任何的别名标签绑定, `Map` 方法将会将对象转换为结果的一个键值。我们可以使用 `MapDeep` 方法递归转换参数的子对象,即把属性也转换为 `map` 类型。我们来看个例子。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "reflect" +) + +func main() { + type Base struct { + Id int `c:"id"` + Date string `c:"date"` + } + type User struct { + UserBase Base `c:"base"` + Passport string `c:"passport"` + Password string `c:"password"` + Nickname string `c:"nickname"` + } + user := &User{ + UserBase: Base{ + Id: 1, + Date: "2019-10-01", + }, + Passport: "john", + Password: "123456", + Nickname: "JohnGuo", + } + m1 := gconv.Map(user) + m2 := gconv.MapDeep(user) + g.Dump(m1, m2) + fmt.Println(reflect.TypeOf(m1["base"])) + fmt.Println(reflect.TypeOf(m2["base"])) +} +``` + +执行后,终端输出结果为: + +``` +{ + "base": { + Id: 1, + Date: "2019-10-01", + }, + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", +} +{ + "base": { + "id": 1, + "date": "2019-10-01", + }, + "passport": "john", + "password": "123456", + "nickname": "JohnGuo", +} +main.Base +map[string]interface {} +``` + +看出来差别了吗? \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..3c83dd7bbe4 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Scan\350\275\254\346\215\242.md" @@ -0,0 +1,188 @@ +--- +slug: '/docs/core/gconv-scan' +title: '类型转换-Scan转换' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,类型转换,Scan方法,struct转换,map转换,Go语言,自动识别,参数转换,编程示例] +description: '了解如何使用GoFrame框架中强大的Scan方法实现任意参数到struct、struct数组、map、map数组的自动识别转换。本文介绍Scan方法的定义及其在Go语言中的应用,同时提供多个编程示例帮助快速理解和掌握此功能,为开发者提供高效便捷的类型转换解决方案。' +--- + +前面关于复杂类型的转换功能如果大家觉得还不够的话,那么您可以了解下 `Scan` 转换方法,该方法可以实现对任意参数到 `struct/struct数组/map/map数组` 的转换,并且根据开发者输入的转换目标参数自动识别执行转换。 + +该方法定义如下: + +```go +// Scan automatically calls MapToMap, MapToMaps, Struct or Structs function according to +// the type of parameter `pointer` to implement the converting. +// It calls function MapToMap if `pointer` is type of *map to do the converting. +// It calls function MapToMaps if `pointer` is type of *[]map/*[]*map to do the converting. +// It calls function Struct if `pointer` is type of *struct/**struct to do the converting. +// It calls function Structs if `pointer` is type of *[]struct/*[]*struct to do the converting. +func Scan(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +我们接下来看几个示例便可快速理解。 + +## 自动识别转换 `Struct` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Map{ + "uid": 1, + "name": "john", + } + var user *User + if err := gconv.Scan(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +执行后,输出结果为: + +``` +{ + Uid: 1, + Name: "john", +} +``` + +## 自动识别转换 `Struct` 数组 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + var users []*User + if err := gconv.Scan(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +执行后,终端输出: + +``` +[ + { + Uid: 1, + Name: "john", + }, + { + Uid: 2, + Name: "smith", + }, +] +``` + +## 自动识别转换Map + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + var ( + user map[string]string + params = g.Map{ + "uid": 1, + "name": "john", + } + ) + if err := gconv.Scan(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +执行后,输出结果为: + +``` +{ + "uid": "1", + "name": "john", +} +``` + +## 自动识别转换 `Map` 数组 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + var ( + users []map[string]string + params = g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + ) + if err := gconv.Scan(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +执行后,输出结果为: + +``` +[ + { + "uid": "1", + "name": "john", + }, + { + "uid": "2", + "name": "smith", + }, +] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..f41931078b9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Structs\350\275\254\346\215\242.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/core/gconv-structs' +title: '类型转换-Structs转换' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,类型转换,Struct,Structs,struct数组,转换方法,gconv,使用示例,Go语言] +description: 'GoFrame框架中的类型转换方法,主要聚焦于使用Structs方法实现对struct数组的转换。Structs方法与Struct方法类似,并在其基础上扩展了对struct数组的支持。文中还提供了具体的代码示例,演示了如何在实际应用中使用这一功能。' +--- + +## 基本介绍 + +我们之前提到可以使用 `Struct` 方法实现对 `struct` 对象的转换,那么我们当然也可以实现对 `struct` 数组的转换, `struct` 数组转换使用的是 `Structs` 方法实现。 `Structs` 方法建立在 `Struct` 方法的基础之上,所有的转换规则与 `Struct` 相同,只是增加了对 `struct` 数组类型的支持。在了解 `Structs` 方法之前,建议您先了解 `Struct` 方法介绍: [类型转换-Struct转换](类型转换-Struct转换.md) + +## 方法定义 + +`Structs` 方法定义如下: + +```go +// Structs converts any slice to given struct slice. +func Structs(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +其中 `pointer` 目标转换参数类型需要为 `*[]struct/*[]*struct`。 + +## 使用示例 + +我们来看一个简单示例即可理解。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Slice{ + g.Map{ + "uid": 1, + "name": "john", + }, + g.Map{ + "uid": 2, + "name": "smith", + }, + } + var users []*User + if err := gconv.Structs(params, &users); err != nil { + panic(err) + } + g.Dump(users) +} +``` + +执行后,终端输出: + +``` +[ + { + Uid: 1, + Name: "john", + }, + { + Uid: 2, + Name: "smith", + }, +] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..2ab0a8c9972 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-Struct\350\275\254\346\215\242.md" @@ -0,0 +1,318 @@ +--- +slug: '/docs/core/gconv-struct' +title: '类型转换-Struct转换' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,gconv,结构体转换,数据映射,struct转换,自动创建对象,递归转换,映射规则,结构体,Go框架] +description: '使用GoFrame的gconv模块进行结构体转换,包括从各种数据类型到结构体的映射、自动创建对象、递归转换以及映射规则等实用功能,帮助开发者提升编码效率和项目维护能力。' +--- + +项目中我们经常会遇到大量 `struct` 的使用,以及各种数据类型到 `struct` 的转换/赋值(特别是 `json`/ `xml`/各种协议编码转换)。为提高编码及项目维护效率, `gconv` 模块为各位开发者带来了极大的福利,为数据解析提供了更高的灵活度。 + +`gconv` 模块通过 `Struct` 转换方法执行 `struct` 类型转换,其定义如下: + +```go +// Struct maps the params key-value pairs to the corresponding struct object's attributes. +// The third parameter `mapping` is unnecessary, indicating the mapping rules between the +// custom key name and the attribute name(case sensitive). +// +// Note: +// 1. The `params` can be any type of map/struct, usually a map. +// 2. The `pointer` should be type of *struct/**struct, which is a pointer to struct object +// or struct pointer. +// 3. Only the public attributes of struct object can be mapped. +// 4. If `params` is a map, the key of the map `params` can be lowercase. +// It will automatically convert the first letter of the key to uppercase +// in mapping procedure to do the matching. +// It ignores the map key, if it does not match. +func Struct(params interface{}, pointer interface{}, mapping ...map[string]string) (err error) +``` + +其中: + +1. `params` 为需要转换到 `struct` 的变量参数,可以为任意数据类型,常见的数据类型为 `map`。 +2. `pointer` 为需要执行转的目标 `struct` 对象,这个参数必须为该 `struct` 的对象指针,转换成功后该对象的属性将会更新。 +3. `mapping` 为自定义的 `map键名` 到 `strcut属性` 之间的映射关系,此时 `params` 参数必须为 `map` 类型,否则该参数无意义。大部分场景下使用可以不用提供该参数,直接使用默认的转换规则即可。 +:::tip +更多的 `struct` 相关转换方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: +## 转换规则 + +`gconv` 模块的 `struct` 转换特性非常强大,支持任意数据类型到 `struct` 属性的映射转换。在没有提供自定义 `mapping` 转换规则的情况下,默认的转换规则如下: + +1. `struct` 中需要匹配的属性必须为 **公开属性**(首字母大写)。 +2. 根据 `params` 类型的不同,逻辑会有不同: + - `params` 参数类型为 `map`:键名会自动按照 **不区分大小写** 且 **忽略特殊字符** 的形式与struct属性进行匹配。 + - `params` 参数为其他类型:将会把该变量值与 `struct` 的第一个属性进行匹配。 + -  此外,如果 `struct` 的属性为复杂数据类型如 `slice`, `map`, `strcut` 那么会进行递归匹配赋值。 +3. 如果匹配成功,那么将键值赋值给属性,如果无法匹配,那么忽略该键值。 + +## 匹配规则优先级说明(只针对于map到struct的转换) + +1.如果 `mapping` 参数不为空,将会按照 `mapping` 的 `key ` 到 `strcut字段名` 之间的映射关系。 + +2.如果设置了字段的 `tag`,会使用 `tag` 来匹配 `params` 参数的  `key`。 + +       如果没有设置 `tag`,gconv将会依次按照 `gconv, param, c, p, json` 这个顺序来查找字段是否有对应的 `tag` + +3.按照 `字段名` 匹配。 + +4.如果以上都没有匹配到,gconv将会遍历 `params` 参数所有的 `key`,按照 以下规则来匹配 + +`字段名`:忽略大小写和下划线 + +`key`: 忽略大小写和下划线和特殊字符 + +提示 + +:::warning +没有特殊情况,请尽量满足前三条规则,第四条规则性能较差 +::: + +以下是几个 `map` 键名与 `struct` 属性名称的示例: + +``` +map键名 struct属性 是否匹配 +name Name match +Email Email match +nickname NickName match +NICKNAME NickName match +Nick-Name NickName match +nick_name NickName match +nick name NickName match +NickName Nick_Name match +Nick-name Nick_Name match +nick_name Nick_Name match +nick name Nick_Name match +``` + +## 自动创建对象 + +当给定的 `pointer` 参数类型为 `**struct` 时, `Struct` 方法内部将会自动创建该 `struct` 对象,并修改传递变量指向的指针地址。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type User struct { + Uid int + Name string + } + params := g.Map{ + "uid": 1, + "name": "john", + } + var user *User + if err := gconv.Struct(params, &user); err != nil { + panic(err) + } + g.Dump(user) +} +``` + +执行后,输出结果为: + +``` +{ + Uid: 1, + Name: "john", +} +``` + +## `Struct` 递归转换 + +递归转换是指当 `struct` 对象包含子对象时,并且子对象是 `embedded` 方式定义时,可以将 `params` 参数数据(第一个参数)同时递归地映射到其子对象上,常用于带有继承对象的 `struct` 上。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + type Ids struct { + Id int `json:"id"` + Uid int `json:"uid"` + } + type Base struct { + Ids + CreateTime string `json:"create_time"` + } + type User struct { + Base + Passport string `json:"passport"` + Password string `json:"password"` + Nickname string `json:"nickname"` + } + data := g.Map{ + "id" : 1, + "uid" : 100, + "passport" : "john", + "password" : "123456", + "nickname" : "John", + "create_time" : "2019", + } + user := new(User) + gconv.Struct(data, user) + g.Dump(user) +} +``` + +执行后,终端输出结果为: + +``` +{ + Id: 1, + Uid: 100, + CreateTime: "2019", + Passport: "john", + Password: "123456", + Nickname: "John", +} +``` + +## 示例1,基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +type User struct { + Uid int + Name string + SiteUrl string + NickName string + Pass1 string `c:"password1"` + Pass2 string `c:"password2"` +} + +func main() { + var user *User + + // 使用默认映射规则绑定属性值到对象 + user = new(User) + params1 := g.Map{ + "uid": 1, + "Name": "john", + "site_url": "https://goframe.org", + "nick_name": "johng", + "PASS1": "123", + "PASS2": "456", + } + if err := gconv.Struct(params1, user); err == nil { + g.Dump(user) + } + + // 使用struct tag映射绑定属性值到对象 + user = new(User) + params2 := g.Map{ + "uid": 2, + "name": "smith", + "site-url": "https://goframe.org", + "nick name": "johng", + "password1": "111", + "password2": "222", + } + if err := gconv.Struct(params2, user); err == nil { + g.Dump(user) + } +} +``` + +可以看到,我们可以直接通过 `Struct` 方法将 `map` 按照默认规则绑定到 `struct` 上,也可以使用 `struct tag` 的方式进行灵活的设置。此外, `Struct` 方法有第三个 `map` 参数,用于指定自定义的参数名称到属性名称的映射关系。 + +执行后,输出结果为: + +``` +{ + Uid: 1, + Name: "john", + SiteUrl: "https://goframe.org", + NickName: "johng", + Pass1: "123", + Pass2: "456", +} +{ + Uid: 2, + Name: "smith", + SiteUrl: "https://goframe.org", + NickName: "johng", + Pass1: "111", + Pass2: "222", +} +``` + +## 示例2,复杂属性类型 + +属性支持 `struct` 对象或者 `struct` 对象指针(目标为指针且为 `nil` 时,转换时会自动初始化)转换。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/util/gconv" + "github.com/gogf/gf/v2/frame/g" + "fmt" +) + +func main() { + type Score struct { + Name string + Result int + } + type User1 struct { + Scores Score + } + type User2 struct { + Scores *Score + } + + user1 := new(User1) + user2 := new(User2) + scores := g.Map{ + "Scores": g.Map{ + "Name": "john", + "Result": 100, + }, + } + + if err := gconv.Struct(scores, user1); err != nil { + fmt.Println(err) + } else { + g.Dump(user1) + } + if err := gconv.Struct(scores, user2); err != nil { + fmt.Println(err) + } else { + g.Dump(user2) + } +} +``` + +执行后,输出结果为: + +``` +{ + Scores: { + Name: "john", + Result: 100, + }, +} +{ + Scores: { + Name: "john", + Result: 100, + }, +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" new file mode 100644 index 00000000000..1bea560e132 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-UnmarshalValue.md" @@ -0,0 +1,281 @@ +--- +slug: '/docs/core/gconv-unmarshal-value' +title: '类型转换-UnmarshalValue' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,gconv,UnmarshalValue,类型转换,自定义转换,结构体转换,接口定义,性能优化,反射] +description: '使用GoFrame框架中的gconv模块实现类型转换,尤其是通过UnmarshalValue接口实现自定义转换。详细探讨了反射特性在复杂类型转换中的应用及其性能影响,并提供了多个代码示例,包括自定义结构体转换和TCP数据解包,帮助开发者优化转换效率。' +--- + +当然,想必您已经猜到了,在对一些复杂类型(如 `struct`)的转换时, `gconv` 模块内部其实使用了反射的特性来实现的。这虽然为开发者提供了极大的便捷,但是这确实是以性能损耗为代价的。其实在对于 `struct` 转换时,如果开发者已经 **明确转换规则**,并且对于其中的性能损耗比较在意,那么可以对特定的 `struct` 实现 `UnmarshalValue` 接口来实现 **自定义转换**。当使用 `gconv` 模块对该 `struct` 进行转换时,无论该 `struct` 是直接作为转换对象或者作为转换对象的属性, `gconv` 都将会自动识别其实现的 `UnmarshalValue` 接口并直接调用该接口实现类型转换,而不会使用反射特性来实现转换。 +:::tip +标准库的常用反序列化接口,如 `UnmarshalText(text []byte) error` 其实也是支持的哟,使用方式同 `UnmarshalValue`,只是参数不同。 +::: +## 接口定义 + +```go +// apiUnmarshalValue is the interface for custom defined types customizing value assignment. +// Note that only pointer can implement interface apiUnmarshalValue. +type apiUnmarshalValue interface { + UnmarshalValue(interface{}) error +} +``` + +可以看到,自定义的类型可以通过定义 `UnmarshalValue` 方法来实现自定义的类型转换。这里的输入参数为 `interface{}` 类型,开发者可以在实际使用场景中通过 类型断言 或者其他方式进行类型转换。 +:::warning +需要特别注意,由于 `UnmarshalValue` 类型转换会修改当前对象的属性值,因此需要保证该接口实现的接受者( `Receiver`)是指针类型。 + +正确的接口实现定义示例(使用指针接受): + +```go +func (c *Receiver) UnmarshalValue(interface{}) error +``` + +**错误的** 接口实现定义示例(使用了值传递): + +```go +func (c Receiver) UnmarshalValue(interface{}) error +``` +::: +## 使用示例 + +### 1、自定义数据表查询结果 `struct` 转换 + +数据表结构: + +```sql +CREATE TABLE `user` ( + id bigint unsigned NOT NULL AUTO_INCREMENT, + passport varchar(45), + password char(32) NOT NULL, + nickname varchar(45) NOT NULL, + create_time timestamp NOT NULL, + PRIMARY KEY (id) +) ; +``` + +示例代码: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gtime" + "reflect" +) + +type User struct { + Id int + Passport string + Password string + Nickname string + CreateTime *gtime.Time +} + +// 实现UnmarshalValue接口,用于自定义结构体转换 +func (user *User) UnmarshalValue(value interface{}) error { + if record, ok := value.(gdb.Record); ok { + *user = User{ + Id: record["id"].Int(), + Passport: record["passport"].String(), + Password: "", + Nickname: record["nickname"].String(), + CreateTime: record["create_time"].GTime(), + } + return nil + } + return gerror.Newf(`unsupported value type for UnmarshalValue: %v`, reflect.TypeOf(value)) +} + +func main() { + var ( + err error + users []*User + ) + array := garray.New(true) + for i := 1; i <= 10; i++ { + array.Append(g.Map{ + "id": i, + "passport": fmt.Sprintf(`user_%d`, i), + "password": fmt.Sprintf(`pass_%d`, i), + "nickname": fmt.Sprintf(`name_%d`, i), + "create_time": gtime.NewFromStr("2018-10-24 10:00:00").String(), + }) + } + // 写入数据 + _, err = g.Model("user").Data(array).Insert() + if err != nil { + panic(err) + } + // 查询数据 + err = g.Model("user").Order("id asc").Scan(&users) + if err != nil { + panic(err) + } + g.Dump(users) +} +``` + +执行后,终端输出: + +``` +[ + { + Id: 1, + Passport: "user_1", + Password: "", + Nickname: "name_1", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 2, + Passport: "user_2", + Password: "", + Nickname: "name_2", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 3, + Passport: "user_3", + Password: "", + Nickname: "name_3", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 4, + Passport: "user_4", + Password: "", + Nickname: "name_4", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 5, + Passport: "user_5", + Password: "", + Nickname: "name_5", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 6, + Passport: "user_6", + Password: "", + Nickname: "name_6", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 7, + Passport: "user_7", + Password: "", + Nickname: "name_7", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 8, + Passport: "user_8", + Password: "", + Nickname: "name_8", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 9, + Passport: "user_9", + Password: "", + Nickname: "name_9", + CreateTime: "2018-10-24 10:00:00", + }, + { + Id: 10, + Passport: "user_10", + Password: "", + Nickname: "name_10", + CreateTime: "2018-10-24 10:00:00", + }, +] +``` +:::tip +可以看到自定义的 `UnmarshalValue` 类型转换方法中没有使用到反射特性,因此转换的性能会得到极大的提升。小伙伴们可以尝试着增加写入的数据量(例如 `100W`),同时对比一下去掉 `UnmarshalValue` 后的类型转换所开销的时间。 +::: +### 2、自定义二进制TCP数据解包 + +一个TCP通信的数据包解包示例。 + +```go +package main + +import ( + "errors" + "fmt" + "github.com/gogf/gf/v2/crypto/gcrc32" + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/util/gconv" +) + +type Pkg struct { + Length uint16 // Total length. + Crc32 uint32 // CRC32. + Data []byte +} + +// NewPkg creates and returns a package with given data. +func NewPkg(data []byte) *Pkg { + return &Pkg{ + Length: uint16(len(data) + 6), + Crc32: gcrc32.Encrypt(data), + Data: data, + } +} + +// Marshal encodes the protocol struct to bytes. +func (p *Pkg) Marshal() []byte { + b := make([]byte, 6+len(p.Data)) + copy(b, gbinary.EncodeUint16(p.Length)) + copy(b[2:], gbinary.EncodeUint32(p.Crc32)) + copy(b[6:], p.Data) + return b +} + +// UnmarshalValue decodes bytes to protocol struct. +func (p *Pkg) UnmarshalValue(v interface{}) error { + b := gconv.Bytes(v) + if len(b) < 6 { + return errors.New("invalid package length") + } + p.Length = gbinary.DecodeToUint16(b[:2]) + if len(b) < int(p.Length) { + return errors.New("invalid data length") + } + p.Crc32 = gbinary.DecodeToUint32(b[2:6]) + p.Data = b[6:] + if gcrc32.Encrypt(p.Data) != p.Crc32 { + return errors.New("crc32 validation failed") + } + return nil +} + +func main() { + var p1, p2 *Pkg + + // Create a demo pkg as p1. + p1 = NewPkg([]byte("123")) + fmt.Println(p1) + + // Convert bytes from p1 to p2 using gconv.Struct. + err := gconv.Struct(p1.Marshal(), &p2) + if err != nil { + panic(err) + } + fmt.Println(p2) +} +``` + +执行后,终端输出: + +``` +&{9 2286445522 [49 50 51]} +&{9 2286445522 [49 50 51]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" new file mode 100644 index 00000000000..3afc600c6e0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\345\237\272\346\234\254\347\261\273\345\236\213.md" @@ -0,0 +1,79 @@ +--- +slug: '/docs/core/gconv-basic' +title: '类型转换-基本类型' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,类型转换,基本类型,gconv,Int转换,Uint转换,Float转换,Bool转换,字符串转换] +description: '使用GoFrame框架进行常用基本类型转换的方法。重点阐述了gconv包在整数、浮点数、布尔值、字符串等类型转换中的应用。提供了简单易懂的代码示例,展示了如何在实际开发中使用这些转换函数进行高效的类型转换操作。' +--- + +常用基本类型的转换方法比较简单,我们这里使用一个例子来演示转换方法的使用及效果。 + +## 基本示例 +:::tip +更多的类型转换方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) +::: +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + i := 123.456 + fmt.Printf("%10s %v\n", "Int:", gconv.Int(i)) + fmt.Printf("%10s %v\n", "Int8:", gconv.Int8(i)) + fmt.Printf("%10s %v\n", "Int16:", gconv.Int16(i)) + fmt.Printf("%10s %v\n", "Int32:", gconv.Int32(i)) + fmt.Printf("%10s %v\n", "Int64:", gconv.Int64(i)) + fmt.Printf("%10s %v\n", "Uint:", gconv.Uint(i)) + fmt.Printf("%10s %v\n", "Uint8:", gconv.Uint8(i)) + fmt.Printf("%10s %v\n", "Uint16:", gconv.Uint16(i)) + fmt.Printf("%10s %v\n", "Uint32:", gconv.Uint32(i)) + fmt.Printf("%10s %v\n", "Uint64:", gconv.Uint64(i)) + fmt.Printf("%10s %v\n", "Float32:", gconv.Float32(i)) + fmt.Printf("%10s %v\n", "Float64:", gconv.Float64(i)) + fmt.Printf("%10s %v\n", "Bool:", gconv.Bool(i)) + fmt.Printf("%10s %v\n", "String:", gconv.String(i)) + fmt.Printf("%10s %v\n", "Bytes:", gconv.Bytes(i)) + fmt.Printf("%10s %v\n", "Strings:", gconv.Strings(i)) + fmt.Printf("%10s %v\n", "Ints:", gconv.Ints(i)) + fmt.Printf("%10s %v\n", "Floats:", gconv.Floats(i)) + fmt.Printf("%10s %v\n", "Interfaces:", gconv.Interfaces(i)) +} + +``` + +执行后,输出结果为: + +``` + Int: 123 + Int8: 123 + Int16: 123 + Int32: 123 + Int64: 123 + Uint: 123 + Uint8: 123 + Uint16: 123 + Uint32: 123 + Uint64: 123 + Float32: 123.456 + Float64: 123.456 + Bool: true + String: 123.456 + Bytes: [119 190 159 26 47 221 94 64] + Strings: [123.456] + Ints: [123] + Floats: [123.456] +Interfaces: [123.456] +``` + +## 注意事项 + +数字转换方法例如 `gconv.Int/Uint` 等等,当给定的转换参数为字符串时,会自动识别十六进制、八进制。 + +### 十六进制转换 + +`gconv` 将 `0x` 开头的数字字符串当做十六进制转换。例如, `gconv.Int("0xff")` 将会返回 `255`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..63d5ae66d35 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,38 @@ +--- +slug: '/docs/core/gconv-benchmark' +title: '类型转换-性能测试' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,类型转换,性能测试,基准测试,gconv,GoFrame框架,基本类型转换,Go语言,优化,性能] +description: '通过性能基准测试对GoFrame框架中的基本类型转换使用进行了详细的介绍和分析。通过多个不同的数据类型转换性能测试,展示了其在不同条件下的效率表现,为开发者在使用GoFrame框架时提供优化参考和指导。' +--- + +基本类型转换性能基准测试: + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/util/gconv$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +BenchmarkString-4 20000000 71.8 ns/op 24 B/op 2 allocs/op +BenchmarkInt-4 100000000 22.2 ns/op 8 B/op 1 allocs/op +BenchmarkInt8-4 100000000 24.5 ns/op 8 B/op 1 allocs/op +BenchmarkInt16-4 50000000 23.8 ns/op 8 B/op 1 allocs/op +BenchmarkInt32-4 100000000 24.1 ns/op 8 B/op 1 allocs/op +BenchmarkInt64-4 100000000 21.7 ns/op 8 B/op 1 allocs/op +BenchmarkUint-4 100000000 22.2 ns/op 8 B/op 1 allocs/op +BenchmarkUint8-4 50000000 25.6 ns/op 8 B/op 1 allocs/op +BenchmarkUint16-4 50000000 32.1 ns/op 8 B/op 1 allocs/op +BenchmarkUint32-4 50000000 27.7 ns/op 8 B/op 1 allocs/op +BenchmarkUint64-4 50000000 28.1 ns/op 8 B/op 1 allocs/op +BenchmarkFloat32-4 10000000 155 ns/op 24 B/op 2 allocs/op +BenchmarkFloat64-4 10000000 177 ns/op 24 B/op 2 allocs/op +BenchmarkTime-4 5000000 240 ns/op 72 B/op 4 allocs/op +BenchmarkTimeDuration-4 50000000 26.2 ns/op 8 B/op 1 allocs/op +BenchmarkBytes-4 10000000 149 ns/op 128 B/op 3 allocs/op +BenchmarkStrings-4 10000000 223 ns/op 40 B/op 3 allocs/op +BenchmarkInts-4 20000000 55.0 ns/op 16 B/op 2 allocs/op +BenchmarkFloats-4 10000000 186 ns/op 32 B/op 3 allocs/op +BenchmarkInterfaces-4 20000000 66.6 ns/op 24 B/op 2 allocs/op +PASS +ok command-line-arguments 35.356s +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..ad23a0d95d0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\261\273\345\236\213\350\275\254\346\215\242/\347\261\273\345\236\213\350\275\254\346\215\242.md" @@ -0,0 +1,35 @@ +--- +slug: '/docs/core/gconv' +title: '类型转换' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,类型转换,gconv,高效转换,结构体转换,数据类型,编程效率,接口文档,无缝转换] +description: 'GoFrame框架中的gconv包,该包提供了高效的类型转换功能,支持从常用数据类型到指定类型的转换,并可将任意类型转换为struct对象。gconv广泛使用断言,有效提升程序性能,是开发者实现类型转换的理想选择。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了非常强大易用的类型转换包 `gconv`,可以实现将常用数据类型转换为指定的数据类型,对常用基本数据类型之间的无缝转换,同时也支持任意类型到 `struct` 对象的转换。由于 `gconv` 模块内部大量优先使用了断言而非反射,因此执行的效率非常高。 + +**注意事项:** + +`gconv` 包的主要目标是提供简便且高效的类型转换功能,开发者应当注意转换的输入参数以及当前使用的业务场景,部分方法如果转换失败,那么方法并不会返回错误原因,也不会产生 `panic`,而是直接以转换失败后的数值返回。因此,开发者往往需要结合返回值以及当前使用的业务场景来综合判断结果的正确性。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/util/gconv" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv](https://pkg.go.dev/github.com/gogf/gf/v2/util/gconv) + +**方法列表:** + +方法列表可能滞后于代码,详情请查看接口文档。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..dbbbacbc5f5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-Redis\347\274\223\345\255\230.md" @@ -0,0 +1,61 @@ +--- +slug: '/docs/core/gcache-redis' +title: '缓存管理-Redis缓存' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,缓存管理,Redis缓存,gcache,redis适配,Session共享,数据库查询缓存,redis db,缓存对象] +description: 'GoFrame框架中的缓存管理模块,重点讲解Redis缓存的适配实现及其使用方法,通过示例展示如何在多节点环境下确保数据一致性。文中提供了设置Redis客户端和使用Redis缓存适配器的详细步骤,同时探讨了Clear和Size方法在多对象连接情况下的操作注意事项,并建议针对不同业务配置独立的Redis db。' +--- + +## 基本介绍 + +缓存组件同时提供了 `gcache` 的 `Redis` 缓存适配实现。 `Redis` 缓存在多节点保证缓存的数据一致性时非常有用,特别是 `Session` 共享、数据库查询缓存等场景中。 + +## 使用示例 + +```go +func ExampleCache_SetAdapter() { + var ( + err error + ctx = gctx.New() + cache = gcache.New() + redisConfig = &gredis.Config{ + Address: "127.0.0.1:6379", + Db: 9, + } + cacheKey = `key` + cacheValue = `value` + ) + // Create redis client object. + redis, err := gredis.New(redisConfig) + if err != nil { + panic(err) + } + // Create redis cache adapter and set it to cache object. + cache.SetAdapter(gcache.NewAdapterRedis(redis)) + + // Set and Get using cache object. + err = cache.Set(ctx, cacheKey, cacheValue, time.Second) + if err != nil { + panic(err) + } + fmt.Println(cache.MustGet(ctx, cacheKey).String()) + + // Get using redis client. + fmt.Println(redis.MustDo(ctx, "GET", cacheKey).String()) + + // Output: + // value + // value +} +``` + +## 注意事项 + +### 关于 `Clear/Size` 等方法 + +首先,相同的 `gredis.Config` 总是会连接到相同的 `redis db` 中,而由于 `Redis` 本身没有数据分组功能,所以当多个 `gcache.Cache` 对象连接到同个 `redis db` 时,将会共享整个 `redis db`,而不会单独开辟一个分组用来存储当前 `gcache.Cache` 对象中的内容。因此当使用 `Clear`、 `Size` 这类操作时,将会对整个 `redis db` 进行操作,而不是与内存缓存一样的仅操作当前 `gcache.Cache` 对象中的内容,存在一定的反直觉,请务必谨慎使用。 + +### 建议使用不同的 `redis db` 区分业务缓存类型 + +另外,建议大家在使用 `Redis` 缓存时,单独配置不同的 `db` 来使用,而不是和其他的业务数据共用一个 `db`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" new file mode 100644 index 00000000000..a6ef497d9ad --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\345\206\205\345\255\230\347\274\223\345\255\230.md" @@ -0,0 +1,304 @@ +--- +slug: '/docs/core/gcache-memory' +title: '缓存管理-内存缓存' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,缓存管理,内存缓存,高效缓存,gcache,Go语言,性能优化,LRU淘汰,并发控制] +description: '使用GoFrame框架的内存缓存进行高效缓存管理,包括缓存的基本使用方法、过期控制、GetOrSetFunc函数的使用以及LRU缓存淘汰控制。通过示例代码详细展示了如何设置缓存、获取缓存值以及进行并发控制等操作,旨在帮助用户优化程序性能。' +--- + +缓存组件默认提供了一个高速的内存缓存,操作效率非常高效, `CPU` 性能损耗在 `ns` 纳秒级别。 + +## 使用示例 + +### 基本使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + // 创建一个缓存对象, + // 当然也可以便捷地直接使用gcache包方法 + var ( + ctx = gctx.New() + cache = gcache.New() + ) + + // 设置缓存,不过期 + err := cache.Set(ctx, "k1", "v1", 0) + if err != nil { + panic(err) + } + + // 获取缓存值 + value, err := cache.Get(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(value) + + // 获取缓存大小 + size, err := cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + // 缓存中是否存在指定键名 + b, err := cache.Contains(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(b) + + // 删除并返回被删除的键值 + removedValue, err := cache.Remove(ctx, "k1") + if err != nil { + panic(err) + } + fmt.Println(removedValue) + + // 关闭缓存对象,让GC回收资源 + if err = cache.Close(ctx); err != nil { + panic(err) + } +} +``` + +执行后,输出结果为: + +```v1 +1 +true +v1 +``` + +### 过期控制 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + // 当键名不存在时写入,设置过期时间1000毫秒 + _, err := gcache.SetIfNotExist(ctx, "k1", "v1", time.Second) + if err != nil { + panic(err) + } + + // 打印当前的键名列表 + keys, err := gcache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) + + // 打印当前的键值列表 + values, err := gcache.Values(ctx) + if err != nil { + panic(err) + } + fmt.Println(values) + + // 获取指定键值,如果不存在时写入,并返回键值 + value, err := gcache.GetOrSet(ctx, "k2", "v2", 0) + if err != nil { + panic(err) + } + fmt.Println(value) + + // 打印当前的键值对 + data1, err := gcache.Data(ctx) + if err != nil { + panic(err) + } + fmt.Println(data1) + + // 等待1秒,以便k1:v1自动过期 + time.Sleep(time.Second) + + // 再次打印当前的键值对,发现k1:v1已经过期,只剩下k2:v2 + data2, err := gcache.Data(ctx) + if err != nil { + panic(err) + } + fmt.Println(data2) +} +``` + +执行后,输出结果为: + +``` +[k1] +[v1] +v2 +map[k1:v1 k2:v2] +map[k2:v2] +``` + +### `GetOrSetFunc*` + +`GetOrSetFunc` 获取一个缓存值,当缓存不存在时执行指定的 `f func(context.Context) (interface{}, error)`,缓存该 `f` 方法的结果值,并返回该结果。 + +需要注意的是, `GetOrSetFunc` 的缓存方法参数 `f` 是在缓存的 **锁机制外执行**,因此在 `f` 内部也可以嵌套调用 `GetOrSetFunc`。但如果 `f` 的执行比较耗时,高并发的时候容易出现 `f` 被多次执行的情况(缓存设置只有第一个执行的 `f` 返回结果能够设置成功,其余的被抛弃掉)。而 `GetOrSetFuncLock` 的缓存方法 `f` 是在缓存的 **锁机制内执行**,因此可以保证当缓存项不存在时只会执行一次 `f`,但是缓存写锁的时间随着 `f` 方法的执行时间而定。 + +我们来看一个示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ch = make(chan struct{}, 0) + ctx = gctx.New() + key = `key` + value = `value` + ) + for i := 0; i < 10; i++ { + go func(index int) { + <-ch + _, err := gcache.GetOrSetFuncLock(ctx, key, func(ctx context.Context) (interface{}, error) { + fmt.Println(index, "entered") + return value, nil + }, 0) + if err != nil { + panic(err) + } + }(i) + } + close(ch) + time.Sleep(time.Second) +} +``` + +执行后,终端输出(带有随机性,但是只会输出一条信息): + +``` +9 entered +``` + +可以看到,多个 `goroutine` 同时调用 `GetOrSetFuncLock` 方法时,由于该方法有并发安全控制,因此最终只有一个 `goroutine` 的数值生成函数执行成功,成功之后其他 `goroutine` 拿到数据后则立即返回不再执行对应的数值生成函数。 + +### `LRU` 缓存淘汰控制 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + + var ( + ctx = gctx.New() + cache = gcache.New(2) // 设置LRU淘汰数量 + ) + + // 添加10个元素,不过期 + for i := 0; i < 10; i++ { + if err := cache.Set(ctx, i, i, 0); err != nil { + panic(err) + } + } + size, err := cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + keys, err := cache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) + + // 读取键名1,保证该键名是优先保留 + value, err := cache.Get(ctx, 1) + if err != nil { + panic(err) + } + fmt.Println(value) + + // 等待一定时间后(默认1秒检查一次), + // 元素会被按照从旧到新的顺序进行淘汰 + time.Sleep(3 * time.Second) + size, err = cache.Size(ctx) + if err != nil { + panic(err) + } + fmt.Println(size) + + keys, err = cache.Keys(ctx) + if err != nil { + panic(err) + } + fmt.Println(keys) +} +``` + +执行后,输出结果为: + +```10 +[2 3 5 6 7 0 1 4 8 9] +1 +2 +[1 9] +``` + +## 性能测试 + +### 测试环境 + +``` +CPU: Intel(R) Core(TM) i5-4460 CPU @ 3.20GHz +MEM: 8GB +SYS: Ubuntu 16.04 amd64 +``` + +### 测试结果 + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/os/gcache$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +Benchmark_CacheSet-4 2000000 897 ns/op 249 B/op 4 allocs/op +Benchmark_CacheGet-4 5000000 202 ns/op 49 B/op 1 allocs/op +Benchmark_CacheRemove-4 50000000 35.7 ns/op 0 B/op 0 allocs/op +Benchmark_CacheLruSet-4 2000000 880 ns/op 399 B/op 4 allocs/op +Benchmark_CacheLruGet-4 3000000 212 ns/op 33 B/op 1 allocs/op +Benchmark_CacheLruRemove-4 50000000 35.9 ns/op 0 B/op 0 allocs/op +Benchmark_InterfaceMapWithLockSet-4 3000000 477 ns/op 73 B/op 2 allocs/op +Benchmark_InterfaceMapWithLockGet-4 10000000 149 ns/op 0 B/op 0 allocs/op +Benchmark_InterfaceMapWithLockRemove-4 50000000 39.8 ns/op 0 B/op 0 allocs/op +Benchmark_IntMapWithLockWithLockSet-4 5000000 304 ns/op 53 B/op 0 allocs/op +Benchmark_IntMapWithLockGet-4 20000000 164 ns/op 0 B/op 0 allocs/op +Benchmark_IntMapWithLockRemove-4 50000000 33.1 ns/op 0 B/op 0 allocs/op +PASS +ok command-line-arguments 47.503s +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..fd0a33a9fcb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\216\245\345\217\243\350\256\276\350\256\241.md" @@ -0,0 +1,40 @@ +--- +slug: '/docs/core/gcache-interface' +title: '缓存管理-接口设计' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,缓存管理,接口设计,Adapter接口,gcache,缓存接口,自定义实现,接入缓存,GoFrame缓存] +description: 'GoFrame框架中缓存管理组件的接口设计与实现,提供了Adapter接口,使得开发者可以灵活地注册并自定义缓存管理对象,实现不同缓存策略的无缝接入。详细描述了如何通过SetAdapter和GetAdapter方法进行接口实现的注册与获取。' +--- + +缓存组件采用了接口化设计,提供了 `Adapter` 接口,任何实现了 `Adapter` 接口的对象均可注册到缓存管理对象中,使得开发者可以对缓存管理对象进行灵活的自定义实现和扩展。 + +## 接口定义 + +`Adapter` 接口定义如下: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache#Adapter) + +## 注册接口实现 + +通过该方法将实现的 `adapter` 应用到对应的 `Cache` 对象上: + +```go +// SetAdapter changes the adapter for this cache. +// Be very note that, this setting function is not concurrent-safe, which means you should not call +// this setting function concurrently in multiple goroutines. +func (c *Cache) SetAdapter(adapter Adapter) +``` + +具体示例请参考 [缓存管理-Redis缓存](缓存管理-Redis缓存.md) 章节。 + +## 获取接口实现 + +通过该方法获取当前注册的 `adapter` 接口实现对象上: + +```go +// GetAdapter returns the adapter that is set in current Cache. +func (c *Cache) GetAdapter() Adapter +``` + +具体示例请参考 [缓存管理-Redis缓存](缓存管理-Redis缓存.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..431e3704543 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1070 @@ +--- +slug: '/docs/core/gcache-funcs' +title: '缓存管理-方法介绍' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,缓存管理,数据存储,方法介绍,高效缓存,Go语言,接口实现,缓存适配器,数据处理,缓存更新] +description: '在GoFrame框架中使用缓存管理的方法,包括基本的设置和获取操作、适配器设置方法及缓存更新策略。用户可以通过示例代码了解如何在GoFrame框架中高效管理和操作缓存数据。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache) +::: +## `Set` + +- 说明:使用 `key-value` 键值对设置缓存,键值可以是任意类型。 +- 格式: + +```go +Set(ctx context.Context, key interface{}, value interface{}, duration time.Duration) error +``` + + +- 示例:将 `slice` 切片设置到键名 `k1` 的缓存中。 + +```go +func ExampleCache_Set() { + c := gcache.New() + c.Set(ctx, "k1", g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}, 0) + fmt.Println(c.Get(ctx, "k1")) + + // Output: + // [1,2,3,4,5,6,7,8,9] +} +``` + + +## `SetAdapter` + +- 说明: `SetAdapter` 更改此缓存对象的底层适配器。请注意,此设置函数不是并发安全的。 +- 格式: + +```go +SetAdapter(adapter Adapter) +``` + +- 示例:可以自己根据需要实现任意缓存适配器,实现接口方法即可。 + +```go +func ExampleCache_SetAdapters() { + c := gcache.New() + adapter := gcache.New() + c.SetAdapter(adapter) + c.Set(ctx, "k1", g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}, 0) + fmt.Println(c.Get(ctx, "k1")) + + // Output: + // [1,2,3,4,5,6,7,8,9] +} +``` + + +## `SetIfNotExist` + +- 说明: 当指定 `key` 的键值不存在时设置其对应的键值 `value` 并返回 `true`,否则什么都不做并返回 `false`。 +- 格式: + +```go +SetIfNotExist(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (ok bool, err error) +``` + +- 示例:通过 `SetIfNotExist` 直接 `判断写入`,并设置过期时间。 + +```go +func ExampleCache_SetIfNotExist() { + c := gcache.New() + // Write when the key name does not exist, and set the expiration time to 1000 milliseconds + k1, err := c.SetIfNotExist(ctx, "k1", "v1", 1000*time.Millisecond) + fmt.Println(k1, err) + + // Returns false when the key name already exists + k2, err := c.SetIfNotExist(ctx, "k1", "v2", 1000*time.Millisecond) + fmt.Println(k2, err) + + // Print the current list of key values + keys1, _ := c.Keys(ctx) + fmt.Println(keys1) + + // It does not expire if `duration` == 0. It deletes the `key` if `duration` < 0 or given `value` is nil. + c.SetIfNotExist(ctx, "k1", 0, -10000) + + // Wait 1 second for K1: V1 to expire automatically + time.Sleep(1200 * time.Millisecond) + + // Print the current key value pair again and find that K1: V1 has expired + keys2, _ := c.Keys(ctx) + fmt.Println(keys2) + + // Output: + // true + // false + // [k1] + // [] +} +``` + + +## `SetMap` + +- 说明: 批量设置键值对,输入参数类型为 `map[interface{}]interface{}`。 +- 格式: + +```go +SetMap(ctx context.Context, data map[interface{}]interface{}, duration time.Duration) error +``` + +- 示例: + +```go +func ExampleCache_SetMap() { + c := gcache.New() + // map[interface{}]interface{} + data := g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + } + c.SetMap(ctx, data, 1000*time.Millisecond) + + // Gets the specified key value + v1, _ := c.Get(ctx, "k1") + v2, _ := c.Get(ctx, "k2") + v3, _ := c.Get(ctx, "k3") + + fmt.Println(v1, v2, v3) + + // Output: + // v1 v2 v3 +} +``` + + +## `Size` + +- 说明: `Size` 返回缓存中的 `项数`。 +- 格式: + +```go +Size(ctx context.Context) (size int, err error) +``` + +- 示例: + +```go +func ExampleCache_Size() { + c := gcache.New() + + // Add 10 elements without expiration + for i := 0; i < 10; i++ { + c.Set(ctx, i, i, 0) + } + + // Size returns the number of items in the cache. + n, _ := c.Size(ctx) + fmt.Println(n) + + // Output: + // 10 +} +``` + + +## `Update` + +- 说明: `Update` 更新 `key` 的对应的键值,但不更改其 `过期时间`,并返回旧值。如果缓存中不存在 `key`,则返回的 `exist` 值为 `false`。 +- 格式: + +```go +Update(ctx context.Context, key interface{}, value interface{}) (oldValue *gvar.Var, exist bool, err error) +``` + +- 示例:通过 `SetMap` 添加多个缓存,通过 `Update` 指定 `key` 修改 `value`。 + +```go +func ExampleCache_Update() { + c := gcache.New() + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3"}, 0) + + k1, _ := c.Get(ctx, "k1") + fmt.Println(k1) + k2, _ := c.Get(ctx, "k2") + fmt.Println(k2) + k3, _ := c.Get(ctx, "k3") + fmt.Println(k3) + + re, exist, _ := c.Update(ctx, "k1", "v11") + fmt.Println(re, exist) + + re1, exist1, _ := c.Update(ctx, "k4", "v44") + fmt.Println(re1, exist1) + + kup1, _ := c.Get(ctx, "k1") + fmt.Println(kup1) + kup2, _ := c.Get(ctx, "k2") + fmt.Println(kup2) + kup3, _ := c.Get(ctx, "k3") + fmt.Println(kup3) + + // Output: + // v1 + // v2 + // v3 + // v1 true + // false + // v11 + // v2 + // v3 +} +``` + + +## `UpdateExpire` + +- 说明: `UpdateExpire` 更新 `key` 的过期时间并返回旧的 `过期时间值`。如果缓存中不存在 `key`,则返回 `-1`。 +- 格式: + +```go +UpdateExpire(ctx context.Context, key interface{}, duration time.Duration) (oldDuration time.Duration, err error) +``` + +- 示例:通过 `UpdateExpire` 更新 `key` 的过期时间并打印查看。 + +```go +func ExampleCache_UpdateExpire() { + c := gcache.New() + c.Set(ctx, "k1", "v1", 1000*time.Millisecond) + expire, _ := c.GetExpire(ctx, "k1") + fmt.Println(expire) + + c.UpdateExpire(ctx, "k1", 500*time.Millisecond) + + expire1, _ := c.GetExpire(ctx, "k1") + fmt.Println(expire1) + + // Output: + // 1s + // 500ms +} +``` + + +## `Values` + +- 说明: 通过 `Values` 获取缓存中的所有值,以切片方式返回。 +- 格式: + +```go +Values(ctx context.Context) (values []interface{}, err error) +``` + +- 示例: + +```go +func ExampleCache_Values() { + c := gcache.New() + + c.Set(ctx, "k1", g.Map{"k1": "v1", "k2": "v2"}, 0) + + // Values returns all values in the cache as slice. + data, _ := c.Values(ctx) + fmt.Println(data) + + // May Output: + // [map[k1:v1 k2:v2]] +} +``` + + +## `Close` + +- 说明: 关闭缓存,让 `GC` 回收资源,默认情况下 `可不关闭`。 +- 格式: + +```go +Close(ctx context.Context) error +``` + +- 示例:通过 `Close` 即可关闭缓存。 + +```go +func ExampleCache_Close() { + c := gcache.New() + + c.Set(ctx, "k1", "v", 0) + data, _ := c.Get(ctx, "k1") + fmt.Println(data) + + // Close closes the cache if necessary. + c.Close(ctx) + + data1, _ := c.Get(ctx, "k1") + + fmt.Println(data1) + + // Output: + // v + // v +} +``` + + +## `Contains` + +- 说明: 如果缓存中存在指定的 `key`,则 `Contains` 返回 `true`,否则返回 `false`。 +- 格式: + +```go +Contains(ctx context.Context, key interface{}) (bool, error) +``` + +- 示例: + +```go +func ExampleCache_Contains() { + c := gcache.New() + + // Set Cache + c.Set(ctx, "k", "v", 0) + + data, _ := c.Contains(ctx, "k") + fmt.Println(data) + + // return false + data1, _ := c.Contains(ctx, "k1") + fmt.Println(data1) + + // Output: + // true + // false +} +``` + + +## `Data` + +- 说明: 数据以 `map` 类型返回缓存中所有 `键值对('key':'value')` 的拷贝。 +- 格式: + +```go +Data(ctx context.Context) (data map[interface{}]interface{}, err error) +``` + +- 示例:获取所有缓存数据以 `map[interface{}]interface{}` 返回 + +```go +func ExampleCache_Data() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1"}, 0) + //c.Set(ctx, "k5", "v5", 0) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k1:v1] +} +``` + + +## `Get` + +- 说明: `Get` 检索并返回给定 `key` 的关联值。如果它不存在、值为零或已过期,则返回 `nil`。 +- 格式: + +```go +Get(ctx context.Context, key interface{}) (*gvar.Var, error) +``` + +- 示例: + +```go +func ExampleCache_Get() { + c := gcache.New() + + // Set Cache Object + c.Set(ctx, "k1", "v1", 0) + + data, _ := c.Get(ctx, "k1") + fmt.Println(data) + // Output: + // v1 +} +``` + + +## `GetExpire` + +- 说明: `GetExpire` 检索并返回缓存中 `key` 的过期时间。注意,如果 `key` 为永不过期,则返回 `0`。如果缓存中不存在 `key`,则返回 `-1`。 +- 格式: + +```go +GetExpire(ctx context.Context, key interface{}) (time.Duration, error) +``` + +- 示例: + +```go +func ExampleCache_GetExpire() { + c := gcache.New() + + // Set cache without expiration + c.Set(ctx, "k", "v", 10000*time.Millisecond) + + expire, _ := c.GetExpire(ctx, "k") + fmt.Println(expire) + + // Output: + // 10s +} +``` + + +## `GetOrSet` + +- 说明: 检索并返回 `key` 的值,或者设置 `key-value` 对,如果缓存中不存在 `key`,则直接设置。 +- 格式: + +```go +GetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) (result *gvar.Var, err error) +``` + +- 示例:用 `GetOrSet` 判断 `key` 不存在则直接设置,并设置 `duration` 时间。 + +```go +func ExampleCache_GetOrSet() { + c := gcache.New() + + data, _ := c.GetOrSet(ctx, "k", "v", 10000*time.Millisecond) + fmt.Println(data) + + data1, _ := c.Get(ctx, "k") + fmt.Println(data1) + + // Output: + // v + // v +} +``` + + +## `GetOrSetFunc` + +- 说明: 检索并返回 `key` 的值,如果 `key` 对应的值不存在则使用函数 `func` 的结果设置 `key`,如果缓存中存在 `key`,则返回其结果。 +- 格式: + +```go +GetOrSetFunc(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) (result *gvar.Var, err error) +``` + +- 示例: `k1` 的设置返回 `func` 执行结果, `k2` 返回 `nil` 则不执行任何操作。 + +```go +func ExampleCache_GetOrSetFunc() { + c := gcache.New() + + c.GetOrSetFunc(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "v1", nil + }, 10000*time.Millisecond) + v, _ := c.Get(ctx, "k1") + fmt.Println(v) + + c.GetOrSetFunc(ctx, "k2", func(ctx context.Context) (value interface{}, err error) { + return nil, nil + }, 10000*time.Millisecond) + v1, _ := c.Get(ctx, "k2") + fmt.Println(v1) + + // Output: + // v1 +} +``` + + +## `GetOrSetFuncLock` + +- 说明: 与 `GetOrSetFunc` 一致,但是不能重复或者 `覆盖注册` 缓存。 +- 格式: + +```go +GetOrSetFuncLock(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) (result *gvar.Var, err error) +``` + +- 示例:第1次的设置返回 `func` 执行结果,第2次设置操作则失效。 + +```go +func ExampleCache_GetOrSetFuncLock() { + c := gcache.New() + + c.GetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "v1", nil + }, 0) + v, _ := c.Get(ctx, "k1") + fmt.Println(v) + + c.GetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (value interface{}, err error) { + return "update v1", nil + }, 0) + v, _ = c.Get(ctx, "k1") + fmt.Println(v) + + c.Remove(ctx, g.Slice{"k1"}...) + + // Output: + // v1 + // v1 +} +``` + + +## `Keys` + +- 说明: 缓存中所有的 `key` 所有键名并以 `切片格式(Slice)` 返回。 +- 格式: + +```go +Keys(ctx context.Context) (keys []interface{}, err error) +``` + +- 示例: + +```go +func ExampleCache_Keys() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1"}, 0) + + // Print the current list of key values + keys1, _ := c.Keys(ctx) + fmt.Println(keys1) + + // Output: + // [k1] +} +``` + + +## `KeyStrings` + +- 说明: `KeyStrings` 以字符串 `切片` 的形式返回缓存中的 `所有键`。 +- 格式: + +```go +func (c *Cache) KeyStrings(ctx context.Context) ([]string, error) { + keys, err := c.Keys(ctx) + if err != nil { + return nil, err + } + return gconv.Strings(keys), nil +} +``` + +- 示例: + +```go +func ExampleCache_KeyStrings() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // KeyStrings returns all keys in the cache as string slice. + keys,_ := c.KeyStrings(ctx) + fmt.Println(keys) + + // May Output: + // [k1 k2] +} +``` + + +## `Remove` + +- 说明: 移除从缓存中 `删除一个或多个键`,并返回最后一个删除的键值。 +- 格式: + +```go +Remove(ctx context.Context, keys ...interface{}) (lastValue *gvar.Var, err error) +``` + +- 示例: + +```go +func ExampleCache_Remove() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + c.Remove(ctx, "k1") + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k2:v2] +} +``` + + +## `Removes` + +- 说明: 从缓存中 `删除多个键`。 +- 格式: + +```go +func (c *Cache) Removes(ctx context.Context, keys []interface{}) error { + _, err := c.Remove(ctx, keys...) + return err +} +``` + +- 示例: + +```go +func ExampleCache_Removes() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4"}, 0) + + c.Removes(ctx, g.Slice{"k1", "k2", "k3"}) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[k4:v4] +} +``` + + +## `Clear` + +- 说明: 清除 `所有缓存`。 +- 格式: + +```go +func (c *Cache) Clear(ctx context.Context) error +``` + +- 示例: + +```go +func ExampleCache_Clear() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2", "k3": "v3", "k4": "v4"}, 0) + + c.Clear(ctx) + + data, _ := c.Data(ctx) + fmt.Println(data) + + // Output: + // map[] +} +``` + + +## `MustGet` + +- 说明: `MustGet` 检索并返回给定 `key` 的关联值。如果它不存在、值为零或已过期,则返回 `nil` 如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustGet(ctx context.Context, key interface{}) *gvar.Var { + v, err := c.Get(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustGet() { + c := gcache.New() + + c.Set(ctx, "k1", "v1", 0) + k2 := c.MustGet(ctx, "k2") + + k1 := c.MustGet(ctx, "k1") + fmt.Println(k1) + + fmt.Println(k2) + + // Output: + // v1 + // +} +``` + + +## `MustGetOrSet` + +- 说明: 检索并返回 `key` 的值,或者设置 `key-value` 对,如果缓存中不存在 `key`,则直接设置。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustGetOrSet(ctx context.Context, key interface{}, value interface{}, duration time.Duration) *gvar.Var { + v, err := c.GetOrSet(ctx, key, value, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustGetOrSet() { + + // Create a cache object, + // Of course, you can also easily use the gcache package method directly + c := gcache.New() + + // MustGetOrSet acts like GetOrSet, but it panics if any error occurs. + k1 := c.MustGetOrSet(ctx, "k1", "v1", 0) + fmt.Println(k1) + + k2 := c.MustGetOrSet(ctx, "k1", "v2", 0) + fmt.Println(k2) + + // Output: + // v1 + // v1 + +} +``` + + +## `MustGetOrSetFunc` + +- 说明: 检索并返回 `key` 的值,如果 `key` 对应的值不存在则使用函数 `func` 的结果设置 `key`,如果缓存中存在 `key`,则返回其结果。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustGetOrSetFunc(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) *gvar.Var { + v, err := c.GetOrSetFunc(ctx, key, f, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustGetOrSetFunc() { + c := gcache.New() + + c.MustGetOrSetFunc(ctx, 1, func(ctx context.Context) (interface{}, error) { + return 111, nil + }, 10000*time.Millisecond) + v := c.MustGet(ctx, 1) + fmt.Println(v) + + c.MustGetOrSetFunc(ctx, 2, func(ctx context.Context) (interface{}, error) { + return nil, nil + }, 10000*time.Millisecond) + v1 := c.MustGet(ctx, 2) + fmt.Println(v1) + + // Output: + // 111 + // +} +``` + + +## `MustGetOrSetFuncLock` + +- 说明: 与 `MustGetOrSetFunc` 一致,但是不能重复或者 `覆盖注册` 缓存。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustGetOrSetFuncLock(ctx context.Context, key interface{}, f func(ctx context.Context) (interface{}, error), duration time.Duration) *gvar.Var { + v, err := c.GetOrSetFuncLock(ctx, key, f, duration) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustGetOrSetFuncLock() { + c := gcache.New() + + c.MustGetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (interface{}, error) { + return "v1", nil + }, 0) + v := c.MustGet(ctx, "k1") + fmt.Println(v) + + // Modification failed + c.MustGetOrSetFuncLock(ctx, "k1", func(ctx context.Context) (interface{}, error) { + return "update v1", nil + }, 0) + v = c.MustGet(ctx, "k1") + fmt.Println(v) + + // Output: + // v1 + // v1 +} +``` + + +## `MustContains` + +- 说明: 如果缓存中存在指定的 `key`,则 `Contains` 返回 `true`,否则返回 `false`。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustContains(ctx context.Context, key interface{}) bool { + v, err := c.Contains(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustContains() { + c := gcache.New() + + // Set Cache + c.Set(ctx, "k", "v", 0) + + // Contains returns true if `key` exists in the cache, or else returns false. + // return true + data := c.MustContains(ctx, "k") + fmt.Println(data) + + // return false + data1 := c.MustContains(ctx, "k1") + fmt.Println(data1) + + // Output: + // true + // false + +} +``` + + +## `MustGetExpire` + +- 说明:  `MustGetExpire` 检索并返回缓存中 `key` 的过期时间。注意,如果 `key` 为永不过期,则返回 `0`。如果缓存中不存在 `key`,则返回 `-1`,如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustGetExpire(ctx context.Context, key interface{}) time.Duration { + v, err := c.GetExpire(ctx, key) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustGetExpire() { + c := gcache.New() + + // Set cache without expiration + c.Set(ctx, "k", "v", 10000*time.Millisecond) + + // MustGetExpire acts like GetExpire, but it panics if any error occurs. + expire := c.MustGetExpire(ctx, "k") + fmt.Println(expire) + + // May Output: + // 10s +} +``` + + +## `MustSize` + +- 说明:  `MustSize` 返回缓存中的项数。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustSize(ctx context.Context) int { + v, err := c.Size(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustSize() { + c := gcache.New() + + // Add 10 elements without expiration + for i := 0; i < 10; i++ { + c.Set(ctx, i, i, 0) + } + + // Size returns the number of items in the cache. + n := c.MustSize(ctx) + fmt.Println(n) + + // Output: + // 10 +} +``` + + +## `MustData` + +- 说明: 数据以 `map` 类型返回缓存中所有 `键值对('key':'value')` 的拷贝。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustData(ctx context.Context) map[interface{}]interface{} { + v, err := c.Data(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustData() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1"}, 0) + + data := c.MustData(ctx) + fmt.Println(data) + + // Set Cache + c.Set(ctx, "k5", "v5", 0) + data1, _ := c.Get(ctx, "k1") + fmt.Println(data1) + + // Output: + // map[k1:v1] + // v1 +} +``` + + +## `MustKeys` + +- 说明: `MustKeys` 以 `(slice)切片` 的形式返回缓存中的所有键,如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustKeys(ctx context.Context) []interface{} { + v, err := c.Keys(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustKeys() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // MustKeys acts like Keys, but it panics if any error occurs. + keys1 := c.MustKeys(ctx) + fmt.Println(keys1) + + // May Output: + // [k1 k2] + +} +``` + + +## `MustKeyStrings` + +- 说明: `MustKeyStrings` 以 `字符串(slice)切片` 的形式返回缓存中的 `所有键`。如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustKeyStrings(ctx context.Context) []string { + v, err := c.KeyStrings(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustKeyStrings() { + c := gcache.New() + + c.SetMap(ctx, g.MapAnyAny{"k1": "v1", "k2": "v2"}, 0) + + // MustKeyStrings returns all keys in the cache as string slice. + // MustKeyStrings acts like KeyStrings, but it panics if any error occurs. + keys := c.MustKeyStrings(ctx) + fmt.Println(keys) + + // May Output: + // [k1 k2] +} +``` + + +## `MustValues` + +- 说明: `MustValues` 以 `(slice)切片` 的形式返回缓存中的所有值,如果 `err` 返回不为空则会 `panic(err)`。 +- 格 式: + +```go +func (c *Cache) MustValues(ctx context.Context) []interface{} { + v, err := c.Values(ctx) + if err != nil { + panic(err) + } + return v +} +``` + +- 示例: + +```go +func ExampleCache_MustValues() { + c := gcache.New() + + // Write value + c.Set(ctx, "k1", "v1", 0) + + // Values returns all values in the cache as slice. + data := c.MustValues(ctx) + fmt.Println(data) + + // Output: + // [v1] +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..2cf84a7dd30 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\347\274\223\345\255\230\347\256\241\347\220\206/\347\274\223\345\255\230\347\256\241\347\220\206.md" @@ -0,0 +1,127 @@ +--- +slug: '/docs/core/gcache' +title: '缓存管理' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcache,缓存管理,内存缓存,缓存适配,键值对,接口设计,数据类型转换,适配器接口] +description: 'GoFrame框架中的gcache模块,它提供了统一的缓存管理功能,包括内存缓存适配实现。gcache支持自定义键名的数据类型和存储任意的数据类型,通过泛型对象进行类型转换,避免直接使用类型断言带来的风险。此外,gcache还提供了缓存过期时间设置,灵活适用于各种缓存应用场景。' +--- + +## 基本介绍 + +`gcache` 是提供统一的缓存管理模块,提供了开发者可自定义灵活接入的缓存适配接口,并默认提供了高速内存缓存适配实现。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gcache" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcache) + +**简要介绍:** + +1. `gcache` 默认提供默认的高速内存缓存对象,可以通过包方法操作内存缓存,也可以通过 `New` 方法创建内存缓存对象。在通过包方法使用缓存功能时,操作的是 `gcache` 默认提供的一个 `gcache.Cache` 对象,具有全局性,因此在使用时注意全局键名的覆盖。 + +2. `gcache` 使用的键名类型是 `interface{}`,而不是 `string` 类型,这意味着我们可以使用任意类型的变量作为键名,但大多数时候建议使用 `string` 或者 `[]byte` 作为键名,并且统一键名的数据类型,以便维护。 + +3. `gcache` 存储的键值类型是 `interface{}`,也就是说可以存储任意的数据类型,当获取数据时返回的也是 `interface{}` 类型,若需要转换为其他的类型可以通过 `gcache` 的 `Get*` 方法便捷获取常见类型。注意,如果您确定知道自己使用的是内存缓存,那么可以直接使用断言方式对返回的 `interface{}` 变量进行类型转换,否则建议通过获取到的泛型对象对应方法完成类型转换。 + +4. 另外需要注意的是, `gcache` 的缓存过期时间参数 `duration` 的类型为 `time.Duration` 类型,在 `Set` 缓存变量时,如果缓存时间参数 `duration = 0` 表示不过期, `duration < 0` 表示立即过期, `duration > 0` 表示超时过期。 + + +## 注意事项 + +### 关于键名数据类型 + +大家可以发现缓存组件中关于键值对的数据类型都是 `interface{}`,这种设计主要是为了考虑通用性和易用性,但是使用上需要注意 `interface{}` 的比较:只有 **数据** 和 **类型** 都相等才算真正匹配。我们来看个例子。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key1 int32 = 1 + key2 float64 = 1 + value = `value` + ) + _ = gcache.Set(ctx, key1, value, 0) + fmt.Println(gcache.MustGet(ctx, key1).Val()) + fmt.Println(gcache.MustGet(ctx, key2).Val()) +} +``` + +执行后,终端输出: + +``` +value + +``` + +可以看到,虽然 `key1` 和 `key2` 的数值都是相同的,但是两者类型不同,因此通过 `key2` 无法获取到键值。 + +### 关于获取对象键值 + +由于键值的类型也是 `interface{}`,往往在获取后会通过类型转换为需要的数据类型。常见的类型转换是直接使用类型断言,这种做法有种风险,就是由于 `gcache` 组件采用 **适配器接口设计模式**,因此底层的实现上(除了默认的内存适配器)往往会改变原有的数据类型(非内存实现往往都会涉及到序列化/反序列化存储),因此不推荐大家直接使用类型断言进行数据类型转换。 + +因此缓存组件在获取键值上做了改进,并不是直接返回 `interface{}` 的键值类型,而是以框架泛型 `*gvar.Var` 对象返回,开发者根据业务场景转换为所需的数据类型。特别是针对于对象缓存存储和读取的场景特别有用,我们来看一个示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gcache" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Id int + Name string + Site string + } + var ( + ctx = gctx.New() + user *User + key = `UserKey` + value = &User{ + Id: 1, + Name: "GoFrame", + Site: "https://goframe.org", + } + ) + err := gcache.Set(ctx, key, value, 0) + if err != nil { + panic(err) + } + v, err := gcache.Get(ctx, key) + if err != nil { + panic(err) + } + if err = v.Scan(&user); err != nil { + panic(err) + } + fmt.Printf(`%#v`, user) +} +``` + +执行后,终端输出: + +```bash +&main.User{Id:1, Name:"GoFrame", Site:"https://goframe.org"} +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..ef4fbfc2a64 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\260\203\350\257\225\346\250\241\345\274\217.md" @@ -0,0 +1,66 @@ +--- +slug: '/docs/core/debugging' +title: '调试模式' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,调试模式,日志输出,环境变量,命令行参数,终端标准输出,v1.10.0,v1.14.0,性能优化] +description: 'GoFrame框架的调试模式,该模式从v1.10.0版本开始对所有使用者开放。在该模式下,关键功能节点将以[INTE]级别日志输出调试信息。开发者可通过命令行参数或环境变量打开调试模式,但调试信息仅输出到终端标准输出,不支持日志文件。在GoFrame v1.14.0版本中,使用g.SetDebug方法手动控制调试模式。' +--- + +## 基本介绍 + +`goframe` 框架的各个组件在一些关键的功能节点会打印出一些调试信息,原本仅供框架内部开发者在开发阶段使用。由于功能强大,从 `goframe` 框架 `v1.10.0` 版本开始,全面开放给所有的框架使用者。 + +框架调试模式下打印的调试信息将会以 `[INTE]` 级别的日志前缀输出到终端标准输出,并且会打印出所在源文件的名称以及代码行号,例如: + +```html +2021-04-14 15:24:52.954 [INTE] gdb_driver_mysql.go:49 Open: root:12345678@tcp(127.0.0.1:3306)/test +2021-04-14 15:24:52.954 [INTE] gdb.go:492 open new connection success, master:false, config:&gdb.ConfigNode{Host:"", Port:"", User:"", Pass:"", Name:"", Type:"mysql", Role:"", Debug:false, Prefix:"", DryRun:false, Weight:0, Charset:"", LinkInfo:"root:12345678@tcp(127.0.0.1:3306)/test", MaxIdleConnCount:0, MaxOpenConnCount:0, MaxConnLifeTime:0, QueryTimeout:0, ExecTimeout:0, TranTimeout:0, PrepareTimeout:0, CreatedAt:"", UpdatedAt:"", DeletedAt:"", TimeMaintainDisabled:false}, node:&gdb.ConfigNode{Host:"", Port:"", User:"", Pass:"", Name:"", Type:"mysql", Role:"", Debug:false, Prefix:"", DryRun:false, Weight:0, Charset:"utf8", LinkInfo:"root:12345678@tcp(127.0.0.1:3306)/test", MaxIdleConnCount:0, MaxOpenConnCount:0, MaxConnLifeTime:0, QueryTimeout:0, ExecTimeout:0, TranTimeout:0, PrepareTimeout:0, CreatedAt:"", UpdatedAt:"", DeletedAt:"", TimeMaintainDisabled:false} +``` + +## 特性开启 + +这些调试信息默认情况下是关闭的,不会影响框架性能,框架的开发者和使用者可以通过以下方式打开: + +1. 命令行启动参数 \- `gf.debug=true`。 +2. 指定的环境变量 \- `GF_DEBUG=true`。 +3. 在 `GoFrame v1.14.0` 版本以后,在程序启动 `boot` 包中使用 `g.SetDebug` 方法手动打开/关闭。该方法是非并发安全的,意味着您不能在运行时异步多协程去调用该方法动态设置调试模式。 +:::tip +您可以发现 `goframe` 框架的许多功能模块特性也是按照 **命令行启动参数+环境变量** 的形式按照一定规则进行配置🐸。 +::: +:::info +需要注意的是,框架的各个模块关键调试信息仅会输出到 **终端标准输出**,不支持输出到日志文件中。 +::: +## 使用示例 + +### 通过环境变量启用调试模式 + +我们以 `Goland IDE` 为例,在运行模板中添加 `GF_DEBUG` 环境变量即可。 + +![](/markdown/ea1f4c4aa8ca8da50174f8240c34912a.png) + +![](/markdown/a4c6819caeacadf867d8ca621372cb8f.png) + +### 通过命令行参数启用调试模式 + +启动程序的时候带上 `--gf.debug=true` 即可,例如: + +```bash +$ ./app --gf.debug=true +``` + +```bash +$ ./app --gf.debug true +``` + +或者 + +```bash +$ ./app --gf.debug=1 +``` + +```bash +$ ./app --gf.debug 1 +``` + +![](/markdown/38ed97756a955abfab1df56acaea5b07.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..41ac76c7f47 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\344\275\277\347\224\250\347\244\272\344\276\213.md" @@ -0,0 +1,116 @@ +--- +slug: '/docs/core/gres-example' +title: '资源管理-使用示例' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,资源管理,WebServer,静态服务,配置管理,模板引擎,资源文件,示例,编程] +description: '一个使用GoFrame框架进行资源管理的示例,展示在WebServer的静态服务、配置管理、以及模板引擎中的应用。通过导入资源文件,无需对代码做额外设置,实现便捷的资源管理。' +--- + +我们来看一个使用示例,在该示例中,展示了资源管理在 `WebServer` 的静态服务、配置管理、模板引擎中的使用。 + +### 资源文件 + +资源文件源码: [https://github.com/gogf/gf/tree/master/os/gres/testdata/example/files](https://github.com/gogf/gf/tree/master/os/gres/testdata/example/files) + +资源文件打包: [https://github.com/gogf/gf/tree/master/os/gres/testdata/example/boot](https://github.com/gogf/gf/tree/master/os/gres/testdata/example/boot) + +资源文件列表: + +``` +2020-03-28T13:04:10+00:00 0.00B config +2020-03-28T13:03:06+00:00 135.00B config/config.toml +2020-03-28T13:04:10+00:00 0.00B public +2020-03-28T12:57:54+00:00 6.00B public/index.html +2020-03-28T13:04:10+00:00 0.00B template +2020-03-28T13:03:17+00:00 15.00B template/index.tpl +TOTAL FILES: 6 +``` + +三个文件的内容分别为: + +1. `config.toml` + +```toml + [server] + Address = ":8888" + ServerRoot = "public" + + [viewer] + DefaultFile = "index.tpl" + Delimiters = ["${", "}"] +``` + + +该文件为应用的配置文件。 + +2. `index.html` + +```html + Hello! +``` + + +该文件用于静态资源请求。 + +3. `index.tpl` + +```html + Hello ${.name}! +``` + + +该文件用于模板文件解析展示。 + + +### 创建应用 + +```go +package main + +import ( + _ "github.com/gogf/gf/v2/os/gres/testdata/example/boot" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/template", func(r *ghttp.Request) { + r.Response.WriteTplDefault(g.Map{ + "name": "GoFrame", + }) + }) + }) + s.Run() +} +``` + +可以看到,整个代码中除了 `import` 中额外增加了一个 `_ "github.com/gogf/gf/v2/os/gres/testdata/example/boot"` 的包引入外,没有其他任何设置。这也是 `GoFrame` 框架的资源管理比较便捷的地方,资源管理并不需要开发阶段对代码做任何特殊设置,在应用程序部署之前打包好资源文件,并通过 `import` 增加资源文件的引入即可。 + +运行后,终端输出: + +```html +2020-03-28 21:36:19.828 75892: http server started listening on [:8888] + + SERVER | DOMAIN | ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +|---------|---------|---------|--------|-----------|-------------------|------------| + default | default | :8888 | GET | /template | main.main.func1.1 | +|---------|---------|---------|--------|-----------|-------------------|------------| +``` + +可以看到,配置文件被自动读取并应用到了 `WebServer` 上。 + +我们通过 `curl` 命令测试一下静态文件以及模板引擎的访问。 + +```bash +$ curl http://127.0.0.1:8888/ +Hello! + +$ curl http://127.0.0.1:8888/template +Hello GoFrame! +``` + +可以看到, `index.html` 静态文件以及 `index.tpl` 模板文件都被成功访问到了。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" new file mode 100644 index 00000000000..6cadf745f3a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\345\267\245\345\205\267\346\211\223\345\214\205.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/core/gres-pack-using-cli' +title: '资源管理-工具打包' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf命令行工具,资源打包,gres模块,Go文件生成,项目目录结构,资源管理,命令行工具,Goland IDE] +description: '使用GoFrame框架中的gf命令行工具进行资源打包。通过gf pack命令可以将项目的config, public, template目录打包成Go文件,生成的文件自动引入到项目中。在boot包中优先引入packed资源包,并通过gres模块操作打包的资源文件。通过gres.Dump()方法可以打印资源文件列表,方便管理与调试。' +--- + +我们可以通过 `gf` 命令行工具的 `pack` 命令实现对任意文件/目录的打包,关于 `gf` 命令行工具的安装和使用具体请查看 [资源打包-pack](../../开发工具/资源打包-pack.md) 章节。由于通过命令行工具进行打包比较简便,因此也是推荐的打包方式。 + +## `gf pack` 生成 `Go` 文件 + +比较推荐的方式是将 `Go` 文件直接生成到 `boot` 启动目录,并设置生成 `Go` 文件的包名为 `boot`,这样该资源文件将会被自动引入到项目中。我们将项目的 `config,public,template` 三个目录的文件打包到 `Go` 文件,打包命令为: + +``` +gf pack config,public,template packed/data.go -n packed +``` + +生成的 `Go` 文件内容类似于: + +```go +package packed + +import "github.com/gogf/gf/v2/os/gres" + +func init() { + if err := gres.Add("H4sIAAAAAAAC/5y8c5Bl0Zbuu9O2bVaq0rZZ6Urbtm3bNnfatipto9"); err != nil { + panic(err) + } +} +``` + +可以看到,生成的 `Go` 文件中通过 `gres.Add` 方法将资源文件的二进制内容添加到默认的资源管理器中,该方法的参数是压缩过后的BASE64字符串,将会在程序启动的时候做解压并在内存中生成一个文件树对象,便于在运行时快速操作文件。 + +## 使用打包的 `Go` 文件 + +### 在 `boot` 包中优先引入 `packed` 资源包 + +在项目的 `boot` 程序启动设置包中自动引入 `packed` 资源包,并且应当作为第一个引入的包,以便于其他引入的包在初始化时( `init` 方法中)便能使用到资源内容,例如像这样( `module` 名称为 `my-app`): + +```go +import ( + _ "my-app/packed" + + // 其他包 +) +``` + +这里建议引入 `packed` 包和其他包之间加入一个空行以作区分,特别是 `Goland` IDE的 `import` 插件不会将引入包进行自动排序。 + +### 在 `main` 包中优先引入 `boot` 包 + +由于项目的 `main` 入口程序文件会引入 `boot` 包,并且应当作为第一个引入的包: + +```go +import ( + _ "my-app/boot" + + // 其他包 +) +``` + +这里建议引入 `boot` 包和其他包之间加入一个空行以作区分,特别是 `Goland` IDE的 `import` 插件不会将引入包进行自动排序。 + +随后可以在项目的任何地方使用 `gres` 模块来访问打包的资源文件。 + +> 如果使用 `GoFrame` 推荐的项目目录结构(新建项目),在目录结构中会存在 `boot` 目录(对应包名也是 `boot`),用于程序启动设置。因此如果将 `Go` 文件生成到 `boot` 目录下,那么将会被自动编译进可执行文件中。 + +## 打印资源管理文件列表 + +可以通过 `gres.Dump()` 方法打印出当前资源管理器中所有的文件列表,输出内容类似于: + +``` +2019-09-15T13:36:28+00:00 0.00B config +2019-07-27T07:26:12+00:00 1.34K config/config.toml +2019-09-15T13:36:28+00:00 0.00B public +2019-06-25T17:03:56+00:00 0.00B public/resource +2018-12-04T12:50:16+00:00 0.00B public/resource/css +2018-12-17T12:54:26+00:00 0.00B public/resource/css/document +2018-12-17T12:54:26+00:00 4.20K public/resource/css/document/style.css +2018-08-24T01:46:58+00:00 32.00B public/resource/css/index.css +2019-05-23T03:51:24+00:00 0.00B public/resource/image +2018-08-20T05:02:08+00:00 24.01K public/resource/image/cover.png +2019-05-23T03:51:24+00:00 4.19K public/resource/image/favicon.ico +2018-08-23T01:44:50+00:00 4.19K public/resource/image/gf.ico +2018-12-04T13:04:34+00:00 0.00B public/resource/js +2019-06-27T11:06:12+00:00 0.00B public/resource/js/document +2019-06-27T11:06:12+00:00 11.67K public/resource/js/document/index.js +2019-09-15T13:36:28+00:00 0.00B template +2019-02-02T09:08:56+00:00 0.00B template/document +2018-12-04T12:49:08+00:00 0.00B template/document/include +2018-12-04T12:49:08+00:00 329.00B template/document/include/404.html +2019-03-06T01:52:56+00:00 3.42K template/document/index.html +... +``` + +需要注意的是,在使用资源管理器中的资源文件时,需要严格按照其路径进行检索获取。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..a716a4f2141 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,337 @@ +--- +slug: '/docs/core/gres-funcs' +title: '资源管理-方法介绍' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,资源管理,方法介绍,Add,Load,Get,GetWithIndex,GetContent,Contains,ScanDir] +description: '在GoFrame框架中进行资源管理的方法,包括如何添加资源、加载资源文件、获取指定路径文件、检查资源是否存在、扫描目录中的文件,并提供相关示例代码以便于理解和使用。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) +::: +## Add + +- 说明: `Add` 将 `content` 解压并添加到默认资源对象。 `prefix` 是非必要参数,表示存储到当前资源对象中的每个文件的前缀。 + +- 格式: + +```go +func Add(content string, prefix ...string) error +``` + +- 示例: + +```go +package main + +import "github.com/gogf/gf/v2/os/gres" + +func main() { + if err := gres.Add("H4sIAAAAAAAC/5TWV1TTydsH8B+EpoAginEBDVIEQYp0kUTpSjAJhCwsxSwlgEtRwVCkKwJSo9IWCCChSAkoTZoivfeF0JEEQpMgvQrvcc/x1X/Zi/9zMTfzzPfMc/OZQcFBLKcBDoAD6DTsRQE/FS9wDLC/64xzl3PHuXng3GRt3R5iTFgBJt6X83cexsJLqjT4mrd9w0fQ6OPh+uAVEbGTj1k4TK1bdBu6nrT2Sab5GyC7ao5yc613l3Ly29Yfbl6fSovmph6AbK6L+TBrhl03yQhO9w7hHUcIcWSAEJd/z6I0MtJHaaKscBAb6STB5unZoVdhgDIFqQaCzyEuLwhza87xLoKCvMJtmjmYMYn63Sjx/X3IuE66VuXJKcVB08OurMS3IObBV6ilar8HjNhjTAGCzixCKecGJI4HfU3NBhngff1mmvfuihCWagSzqpwiiWaLhGkW8ZUMtrUOV0nZWSiGUl8ADw2xVAvrVMqUy2Fdmmmo4nl5CLKwynbmI/hlC5qZSd5INHqjEshnzRH8Sy60vatCdrS2MpPTWqGMEQEfxExDuC0T6/aRAmQZc8NLZ/0reVtVbeKKswKpFFBw4NFq4J7WWY2yE9p6i/oLwNCCT2HkioAirGf/cEcnDGH+6SCBPXAaCsiyOHNOMrfy8MpzqcyqbmcIUh9NqM3d240wHkieqIsLVQvYCCrFHtxYwQsjoEQNW6VrAbc/d9estPr1Cspt8kdMzResBrnITixfkXV+dH6IvwaET9i1fEFbKv2SHOHvYj8S86uPXU3zWv6LnglKVf37PpeU0WIVfU/lJFEJS++4ZAPt2x7NU1Ci+DwkxJCcWDN2IknmvjBEBoro/g0UJw2rLTQ17sCt42H05ZJ1KHyoWakNRBajN4lEB7di4EUDnXH9yfEp40qd719eKB6BRsV2o63HvMbKgno9JmOKCeTCKsmcLKPqdg+xwplYW9fUSI1IqgeMEV1/710WoQUzxyPavxZc3se1g7g4r9fS8uaZYvb7d0QmP1Qgof6MyYqy8ojAytWI2CZkYyHDerexvnz/tHI3+bNekf4Ted1wm4CRRXXwPpNvaf/RKdEvJ0PVw1h4zbCdtZVH5/Y3nlk9ciD31aPS+v4wPytp7FgDk2t6hUhHQ3pUryFB05l6FWr8Y50oQUWOCdzmfMJaGR5zDKIizdPHTiZBLNr2KKSEiQdJ/XyDq790DfrfjEmWdw/fqATXWcJqy8GeaC4g3dVTUAxU0vjc1n3Rcwwvv+RPt2iKQStpOVGrPat55gbqaiaq8LgBNkRdj3T7nBUE1+OB2o7aCJNRjZP7QFukFSRqGONqF7atay17bt2payyZJ5YEWuBmVnMC7KqjKHXLUCj0y3ZTgsnp3ZyPMc7U7ArlJj5cckJfjJFD3PM6ZL7o7zuVxT5eZWttT8do+Lwy+5fzU63SjjL41Mk9fzWuui63P1nLWsLF19Y+NmEDbqWVPDu3UZuzgWUCgKMjFJydY3YuFzkBAgAHVgD4H4xwwnn/vxHOg6p8+cPMLL0pmJqUvac3hnndgyJUmS8oRH7R8yrZw65fJCKuLSzOHKykcdwIGoL0zB2pnefaBe+QnvRMsV25wEJjYk2l6E6yahC5s051Gl8yKcNwSdom5bmPkaKirYs9P0SSM9wXevPii0iGUv2PxJTM8bRPKsfL1lfvk1O6kqX0Exhj3mnyDZf1FMCqqZ9sinQaph574+UOFgU3H0uIwNuVcYbRZ2S06elo89E8pajoz3OZCPHLq9Ikd4M8GjMX0TsiFdasw8gQumbczMQLiVK64At+IEO9jrKshRT0FtiHS97tHnJXik2CrTTmKLzeEigV2yHBamdlVzt/Oy+uY388ttv0KnXsEJHtLZdrDZS7HDyhpvC3oMhdPgf7KClBBwlHi5HY11hxb1Zrd33LYKE7Dfbs6lCLeyliUt7Jt++vi4dq3VXJ8ctsGM6ylFyVlfsietoLY6+Axns8mbGLdyFY28cyncgHm4QKfxIK1Bo9gZeKdGrmdngtkd6WoJ8aDksDw1uwbCXZX1MkUC9JW8/G4UPxBltxXLbVrnrCjg/gOr1hlRKEnrZZY8adj5KcebmdIkRpi9C8+fKrIImcV8QL8UeTe8uGHNTbyMpmSpLG80n/xvUXIQ0Or7Qe7xMgVKZqecdUOPnrznT9G2FU7cAWnfc859l7PAm65sBNtB84QJeueNjRvxFoVKWQqZ6ngI9Qi2JFaj8t3lQvNxMprPhzl3oT+dd6pfXUHySH34lvNyUT6plmYFEMuU3DCIr628SKmRGY0OZqsdwfpdoybrHg2HEx3Y8Du2vsvB08nypJIn1/Xerq8icM1iM7HK+LRZ2KYeFnGLnBtk4UXIljwBTrPsNDUkWmyhtquc055AbRDTqOqtUfK6adlDC2AreiOSUzDUhW2bZK0vG/uLedasI62TKvzrOdCTqz3Fb2m0UUsRFNC7ORz56dyDxWk0agq/fh2QfcorOXDhTuqJHqUy7uVCe60IDcvI8ZJZinotfsta+Li0HUFgweztjFPM4It8cqO+H5pa2KwcTKc3Iho0R72ssr+fQnzhlWmPJhq6CYItPnckjp9nuHkSnRmzym6C2GN5I8I8WVbWKju75d+OvbFldDghD25N2GIFMzPee7ngK5wwbJ7cGaR8NJqa1r8gEM053Lh9BKii86rjgew7aqAoS/f33+2YdCXrDwEM7kkAzBhl4d3UX2HGTXkQ1iz1RpJLp2okp8fL9aJt9zWTnvAEncndWB9RusvDMeNiehnp/zO86fTyUk+BZkmS4r2gnR4zbV98UF8TlTjf1RuItnalBGiXnMoa0dydUvRMX9fV2cd212waFRny7EQ4L4/FqalQRFkzC5C+ocOv3L+FOSL97dku7TetZ7Ur3g1Ko2ZnvuAbPyEvmAme6vmBQ1QJq0v42aWHzE4u+9Z/RG69ZtoEs6h9IbrccQe+yK9kgl+yIyE7jPpk2wjL6/SVacpfnZKef+FdwlZMZZgDbBalpLcncNr++XOPVuXbbzHuqjLmpdmuad6MtiPLWK2fSvWSHSbuSedlgifhWqgA/FebVe9NopPn5Dd9xRPFBrlB5KL5q+SbnDSP0gQHe7r5+o2VHNaYUIaRHkWygeRyC72aU5sT6mRuUR0W/4eBw0ZQvzfDpz1/TuZ7T4zfLpSJfOz6GbMkrTitSSv4ws1kn55L1h4RyMbcUaB4p/VVU+4vmOKYfAsggbKwBEsP3AFACgAWFGP2PK+h3TvwUNyejBfjv7cwcKzsR8GvSD4p+R/kbx98oM+rb+4+ft32N+vt6/xoixAP/s+4+Y/z7L9yfiSPPBMeA/JmNl+7YLAkBAOwAA5L/b/y8AAP//A6tAlY0KAAA="); err != nil { + panic("add binary content to resource manager failed: " + err.Error()) + } +} +``` + + +## `Load` + +- 说明:`Load` 加载、解压并将路径为 `path` 的文件数据读取到默认资源对象中。 `prefix` 是非必要参数,表示存储到当前资源对象中的每个文件的前缀。 + +- 格式: + +```go +func Load(path string, prefix ...string) error +``` + +- 示例: + +```go +package main + +import "github.com/gogf/gf/v2/os/gres" + +func main() { + if err := gres.Load("../res/myfile"); err != nil { + panic("load binary content to resource manager failed: " + err.Error()) + } +} +``` + + +## `Get` + +- 说明:`Get` 返回指定路径的文件。 + +- 格式: + +```go +func Get(path string) *File +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + file := gres.Get("../res/myfile") + if file == nil { + glog.Error(gctx.New(), "get file failed!") + return + } + + fmt.Println("Get File Name:", file.Name()) +} +``` + + +## `GetWithIndex` + +- 说明: `GetWithIndex` 用给定路径path搜索文件,如果文件是目录,那么它会在这个目录下进行索引文件搜索。 `GetWithIndex` 通常用于http静态文件服务。 + +- 格式: + +```go +func GetWithIndex(path string, indexFiles []string) *File +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + file := gres.GetWithIndex("../res", []string{"myfile", "myconfig"}) + if file == nil { + glog.Error(gctx.New(), "get file failed!") + return + } + + fmt.Println("Get File Name:", file.Name()) +} +``` + + +## `GetContent` + +- 说明:`GetContent` 在默认资源对象中直接返回路径为 `path` 的内容。 + +- 格式: + +```go +func GetContent(path string) []byte +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + fileContent := gres.GetContent("../res/myfile") + fmt.Println("Get File Content:", fileContent) +} +``` + + +## `Contains` + +- 说明: `Contains` 检查路径为 `path` 的资源是否存在于默认资源对象中。 + +- 格式: + +```go +func Contains(path string) bool +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + if gres.Contains("../res/myfile") { + fmt.Println("myfile is exist!") + } else{ + fmt.Println("myfile is not exist!") + } +} +``` + + +## `IsEmpty` + +- 说明: `IsEmpty` 检查并返回资源管理器是否为空。 + +- 格式: + +```go +func IsEmpty() bool +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + fmt.Println(gres.IsEmpty()) + + gres.Add("xxxxxxxxxxxxxxxxx") + + fmt.Println(gres.IsEmpty()) + + // Output: + // true + // false +} +``` + + +## `ScanDir` + +- 说明:`ScanDir` 返回给定路径下的文件,参数 `path` 应该是文件夹类型。参数 `pattern` 支持多个文件名模式,使用 `,` 符号分隔多个模式。如果参数 `recursive` 为 `true`,它会递归地扫描目录。 + +- 格式: + +```go +func ScanDir(path string, pattern string, recursive ...bool) []*File +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + files := gres.ScanDir("../res", "*.doc,*.go", true) + if len(files) > 0 { + for _, file := range files { + fmt.Println("ScanDir Result:", file.Name()) + } + } +} +``` + + +## `ScanDirFile` + +- 说明:`ScanDirFile` 返回所有具有给定 `path` 的绝对路径的子文件,如果参数 `recursive` 为 `true`,则会递归扫描目录。 + +- 注意:只返回文件,不返回目录。 +- 格式: + +```go +func ScanDirFile(path string, pattern string, recursive ...bool) []*File +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + files := gres.ScanDirFile("../res", "*.*", true) + if len(files) > 0 { + for _, file := range files { + fmt.Println("ScanDirFile Result:", file.Name()) + } + } +} +``` + + +## `Export` + +- 说明:`Export` 将指定路径 `src` 及其所有子文件递归保存到指定的系统路径 `dst`。 + +- 格式: + +```go +func Export(src, dst string, option ...ExportOption) error +``` + +- 示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + err := gres.Export("../res/src", "../res/dst") + if err != nil { + fmt.Println("gres.Export Error:", err) + } +} +``` + + +## `Dump` + +- 说明:`Dump` 打印默认资源对象的文件。 + +- 格式: + +```go +func Dump() +``` + +- 示例: + +```go +package main + +import ( + "github.com/gogf/gf/v2/os/gres" +) + +func main() { + gres.Add("xxxxxxxxx") + + gres.Dump() +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" new file mode 100644 index 00000000000..cfaf181f302 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\226\271\346\263\225\346\211\223\345\214\205.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/core/gres-pack-using-funcs' +title: '资源管理-方法打包' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,资源管理,方法打包,文件打包,二进制文件,加密解密,Pack方法,Unpack方法,ScanDir,ScanDirFile] +description: '使用GoFrame框架进行资源管理和方法打包。通过自定义的方法实现文件和目录的打包解包操作,支持二进制和Go代码文件。同时,示例演示了如何通过自定义加解密来保护资源文件内容,提供了详细的接口文档和实现细节。' +--- +:::tip +本章节的示例演示打包/解包的同时也演示了对数据的加密/解密。大部分业务项目其实并不需要加密/解密操作,因此直接使用工具打包即可。 +::: +在上一章节我们介绍通过 `gf` 工具链进行文件/目录打包,并生成 `Go` 文件编译到可执行文件中。在本章节中,我们介绍资源管理中涉及到的方法,并通过一个打包/解包二进制资源文件的示例,介绍这些方法实现自定义的打包/解包功能。同时,我们也演示了如何通过自定义加解密来保护我们的资源文件内容。 + +**接口文档:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) + +**简要介绍:** + +1. 通过 `Pack*`/ `Unpack*` 方法可以实现对任意文件的打包/解包功能,可以打包到二进制文件或者Go代码文件。 +2. 资源管理由 `Resource` 对象实现,可实现对打包内容的添加,文件的检索查找,以及对目录的遍历等功能。 +3. 资源文件由 `File` 对象实现,该文件对象和 `os.File` 文件对象类似,并且该对象实现了 `http.File` 接口。 +4. `ScanDir` 用于针对于特定目录下的文件/目录检索,并且支持递归检索。 +5. `ScanDirFile` 用于针对于特定目录下的文件检索,并且支持递归检索。 +6. 通过 `Dump` 方法在终端打印出 `Resource` 资源对象所有的文件列表,资源管理器中的文件分隔符号统一为 `/`。 +7. 此外, `gres` 资源管理模块提供了默认的 `Resource` 对象,并通过包方法提供了对该默认对象的操作。 + +## 自定义打包示例 + +我们将项目根目录下的 `public` 和 `config` 目录打包为 `data.bin` 二进制文件,并通过 `gaes` 加密算法对生成的二进制内容进行加密。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/crypto/gaes" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" +) + +var ( + CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0") +) + +func main() { + binContent, err := gres.Pack("public,config") + if err != nil { + panic(err) + } + binContent, err = gaes.Encrypt(binContent, CryptoKey) + if err != nil { + panic(err) + } + if err := gfile.PutBytes("data.bin", binContent); err != nil { + panic(err) + } +} +``` + +## 自定义解包示例 + +我们使用将刚才打包生成的 `data.bin`,需要解密和解包两步操作。 + +```go +package main + +import ( + "github.com/gogf/gf/v2/crypto/gaes" + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/os/gres" +) + +var ( + CryptoKey = []byte("x76cgqt36i9c863bzmotuf8626dxiwu0") +) + +func main() { + binContent := gfile.GetBytes("data.bin") + binContent, err := gaes.Decrypt(binContent, CryptoKey) + if err != nil { + panic(err) + } + if err := gres.Add(binContent); err != nil { + panic(err) + } + gres.Dump() +} +``` + +最后,我们使用 `gres.Dump()` 打印出添加成功的文件列表查看资源文件是否添加成功。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..63ba5a5dc2c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/core/gres-practice' +title: '资源管理-最佳实践' +sidebar_position: 4 +hide_title: true +keywords: [资源管理,GoFrame,框架工程,静态文件,开发阶段,CLI工具,交叉编译,二进制文件,配置管理,开发效率] +description: '在GoFrame框架中进行资源管理的最佳实践。通过使用GoFrame提供的工程化目录结构和CLI工具,开发者可以在不影响开发过程的前提下有效管理静态资源。在发布阶段,资源组件会打包静态文件到二进制可执行文件中,实现资源的高效发布和管理。' +--- + +资源管理设计的目标之一是在开发阶段不影响静态文件的开发管理,只有在发布的时候执行打包即可,打包完毕后清理临时文件,因此只会影响生成的二进制可执行文件,对开发者来说无感知且使用便捷。 + +## 准备工程 + +建议大家使用 `GoFrame` 官方提供的工程化目录结构,并且使用 `CLI` 工具来创建自己的项目工程,因为整个框架工程化思想以及一些示例都是基于标准化的工程目录结构,这样更有利于大家学习和使用整个开发框架,提高开发效率。特别是,在工程目录中存在一个 `packed` 目录,用以存放资源管理组件的相关打包内容,在默认情况下,里面是一个空的 `go` 文件,什么都不会做。 + +![](/markdown/f684a4fd1a310e760d058df443cf2108.png) + +## 开发阶段 + +在开发阶段,一般来说,开发者并不需要关心资源管理的事情,该怎么撸码就怎么撸,静态的文件按照建议放到 `resource` 目录下。在开发阶段的静态文件访问都会直接通过文件系统访问。 + +## 准备发布 + +当开发完毕后,想要将静态文件、模板文件、配置文件打包到二进制可执行文件中,随着二进制可执行文件发布。好的,这个时候资源组件开始介入展示能力了。您需要配置到交叉编译的配置,具体请参考命令行相关章节 [交叉编译-build](../../开发工具/交叉编译-build.md) 。我们需要使用到CLI工具来执行可执行文件编译,将您的编译配置通过配置文件管理起来(放到hack/config.yaml文件中),一个参考的编译配置如下: + +``` +gfcli: + build: + name: "my-app" + arch: "amd64" + system: "linux" + mod: "none" + cgo: 0 + packSrc: "manifest/config,manifest/i18n,resource/public,resource/template" + version: "" + output: "./bin" + extra: "" +``` + +请注意其中的 `pack` 配置,表示我们在执行编译的时候自动打包到二进制可执行文件中的目录。随后,我们在项目根目录下执行编译命令: + +``` +gf build +``` + +该命令在编译时会自动将配置文件中指定的目录进行打包成临时的打包 `go` 文件,再执行编译,编译完毕后自动清理临时的打包 `go` 文件。 +:::tip +大部分场景下,配置文件可能不需要打包进二进制可执行文件,具体根据您的业务场景自行选择。 +::: +## 发布运行 + +将二进制执行发布执行即可。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..e012e1d3dd8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\350\265\204\346\272\220\347\256\241\347\220\206/\350\265\204\346\272\220\347\256\241\347\220\206.md" @@ -0,0 +1,49 @@ +--- +slug: '/docs/core/gres' +title: '资源管理' +sidebar_position: 12 +hide_title: true +keywords: [资源管理,GoFrame,GoFrame框架,Golang,文件管理,内存操作,二进制资源,IO效率,embed.FS,工程化管理] +description: '通过GoFrame框架进行资源管理,将任意文件或目录打包为Golang源码文件,实现高效的内存操作。资源文件支持自定义加解密和压缩,并可作为独立二进制资源文件,使得文件操作更快捷。' +--- + +## 基本介绍 + +`资源管理` 是指可以将任意文件/目录打包为 `Golang` 源码文件,并且编译到可执行文件中,随着可执行文件发布。 + +资源文件在程序启动时将会自解压释放到内存中,供程序只读访问,可以将它当做基于内存的文件管理器。同时, `GoFrame` 的资源管理特性也支持将文件/目录打包为独立的二进制资源文件使用。由于资源文件在程序运行时是基于内存的文件操作,没有磁盘 `IO` 的开销,因此其文件操作效率非常高。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gres" + +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gres](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) + +## 组件特性 + +`gres` 资源管理组件具有以下显著特点: + +1. 可将任意的文件/目录打包为 `Go` 文件,支持自定义加解密。 +2. 打包的 `Go` 文件/资源文件自动压缩,常见 `css/js` 等文件可达到 `50~90%` 的压缩率。 +3. 支持对打包为 `Go` 文件的资源内容,方便地导出到本地文件系统。 +4. 资源管理器内容完全基于内存,并且内容只读,无法动态修改。 +5. 资源管理器默认整合支持到了 `WebServer`、配置管理、模板引擎模块中。 +6. 任意文件如网站静态文件、配置文件等可编译到二进制文件中,也可编译到发布的可执行文件中。 +7. 开发者可只需编译发布一个可执行文件,除了方便了软件分发,也为保护软件知识产权内容提供了可能。 + +## 与 `embed.FS` 的比较 + +从 `Golang v1.16` 版本开始官方提供了静态文件嵌入的 `embed.FS` 特性,整体底层设计和 `gres` 组件类似,压缩比、执行效率也差不多,只是在使用设计和工程化管理上有比较大的差别。 `GoFrame` 资源管理组件的功能更加丰富,框架的核心组件已经完全对接了 `gres` 资源管理组件,并且在 `GoFrame` 提供的标准的工程化管理下能够做到对开发者无感知地使用资源管理特性,具体可以参考章节 [资源管理-最佳实践](资源管理-最佳实践.md) 。 + +未来 `GoFrame` 基础框架底层不会考虑内置对 `embed.FS` 组件的支持, `embed.FS` 与 `gres` 组件可以独立使用互不影响。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..c96a999316b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-TOML\346\240\274\345\274\217.md" @@ -0,0 +1,310 @@ +--- +slug: '/docs/core/gcfg-toml' +title: '配置管理-TOML格式' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,TOML配置,配置管理,TOML语法,键值对,表格数组,数据序列化,YAML比较,JSON解析,开源框架] +description: 'TOML格式在GoFrame框架中的使用,包括其与其他文件格式的比较、基础语法、数值与BOOL值处理、日期时间、数组、表格、表格数组等内容,帮助用户更好地理解和应用TOML配置管理。' +--- + +## 基本介绍 + +`Toml` 是一种易读、 `mini` 语言,由 `github` 前CEO `Tom` 创建。 `Tom's Obvious, Minimal Language`。 + +`TOML` 致力于配置文件的小型化和易读性。 + +1. WIKI介绍: [https://github.com/toml-lang/toml/wiki](https://github.com/toml-lang/toml/wiki) +2. 官方地址: [https://github.com/toml-lang/toml](https://github.com/toml-lang/toml) +3. 汉化版: [https://github.com/LongTengDao/TOML/blob/%E9%BE%99%E8%85%BE%E9%81%93-%E8%AF%91/toml-v1.0.0.md](https://github.com/LongTengDao/TOML/blob/%E9%BE%99%E8%85%BE%E9%81%93-%E8%AF%91/toml-v1.0.0.md) + +## 与其他格式比较 + +`TOML` 与用于应用程序配置和数据序列化的其他文件格式(如 `YAML` 和 `JSON`)具有相同的特性。 `TOML` 和 `JSON` 都很简单,并且使用普遍存在的数据类型,这使得它们易于编写代码或使用机器进行解析。 `TOML` 和 `YAML` 都强调人的可读性,比如注释,它使理解给定行的目的变得更容易。 `TOML` 的不同之处在于,它支持注释(不像 `JSON`),但保持了简单性(不像 `YAML`)。 + +由于 `TOML` 被显式地设计为一种配置文件格式,所以解析它很容易,但并不打算序列化任意的数据结构。 `TOML` 的文件顶层是一个哈希表,它很容易在键中嵌套数据,但是它不允许顶级数组或浮点数,所以它不能直接序列化一些数据。也没有标准来标识 `TOML` 文件的开始或结束,这会使通过流发送文件变得复杂。这些细节必须在应用层进行协商。 + +`INI` 文件经常与 `TOML` 进行比较,因为它们在语法和用作配置文件方面具有相似性。然而, `INI` 没有标准化的格式,它们不能优雅地处理超过一两个层次的嵌套。 + +## 基础语法 + +```toml +title = "TOML 例子" + +[owner] +name = "Tom Preston-Werner" +organization = "GitHub" +bio = "GitHub Cofounder & CEO\nLikes tater tots and beer." +dob = 1979-05-27T07:32:00Z # 日期时间是一等公民。为什么不呢? + +[database] +server = "192.168.1.1" +ports = [ 8001, 8001, 8002 ] +connection_max = 5000 +enabled = true + +[servers] + # 你可以依照你的意愿缩进。使用空格或Tab。TOML不会在意。 + [servers.alpha] + ip = "10.0.0.1" + dc = "eqdc10" + + [servers.beta] + ip = "10.0.0.2" + dc = "eqdc10" + +[clients] +data = [ ["gamma", "delta"], [1, 2] ] + +#在数组里换行没有关系。 +hosts = [ + "alpha", + "omega" +] +``` + +特点: + +- 大小写敏感,必须是 `UTF-8` 编码 +- 注释: `#` +- 空白符: `tab(0x09)` 或 `space(0x20)` +- 换行符: `LF(0x0A)` 或 `CRLF(0x0D 0x0A)` +- 键值对:同一行,无值的键不可用,每行只能保存一个键值对 + +`TOML` 主要结构是键值对,与 `JSON` 类似。值必须是如下类型: `String`, `Integer`, `Float`, `Boolean`, `Datetime`, `Array`, `Table` + +## 注释 + +使用 `#` 表示注释: + +```toml +# I am a comment. Hear me roar. Roar. +key = "value" # Yeah, you can do this. +``` + +## 字符串 + +`TOML` 中有4种字符串表示方法:基本、多行-基本、字面量、多行-字面量 + +### 1\. 基本字符串 + +由双引号包裹,所有 `Unicode` 字符均可出现,除了双引号、反斜线、控制字符( `U+0000` to `U+001F`)需要转义。 + +### 2\. 多行-基本字符串 + +由三个双引号包裹,除了分隔符开始的换行外,字符串内的换行将被保留: + +```toml +str1 = """ +Roses are red +Violets are blue""" +``` + +### 3\. 字面量字符串 + +由单引号包裹,其内不允许转义,因此可以方便的表示基本字符串中需要转义的内容: + +```toml +winpath = 'C:\Users\nodejs\templates' +``` + +### 4\. 多行-字面量字符串 + +与多行-基本字符串相似: + +```toml +str1 = ''' +Roses are red +Violets are blue''' +``` + +## 数值与BOOL值 + +```toml +int1 = +99 +flt3 = -0.01 +bool1 = true +``` + +## 日期时间 + +```toml +date = 1979-05-27T07:32:00Z +``` + +## 数组 + +数组使用方括号包裹。空格会被忽略。元素使用逗号分隔。 + +注意,同一个数组下不允许混用数据类型。 + +```toml +array1 = [ 1, 2, 3 ] +array2 = [ "red", "yellow", "green" ] +array3 = [ [ 1, 2 ], [3, 4, 5] ] +array4 = [ [ 1, 2 ], ["a", "b", "c"] ] # 这是可以的。 +array5 = [ 1, 2.0 ] # 注意:这是不行的。 +``` + +## 表格 + +表格(也叫哈希表或字典)是键值对的集合。它们在方括号内,自成一行。注意和数组相区分,数组只有值。 + +```toml +[table] +``` + +在此之下,直到下一个 `table` 或 `EOF` 之前,是这个表格的键值对。键在左,值在右,等号在中间。键以非空字符开始,以等号前的非空字符为结尾。键值对是无序的。 + +```toml +[table] +key = "value" +``` + +你可以随意缩进,使用 `Tab` 或 `空格`。为什么要缩进呢?因为你可以嵌套表格。 + +嵌套表格的表格名称中使用 `.` 符号。你可以任意命名你的表格,只是不要用点,点是保留的。 + +```toml +[dog.tater] +type = "pug" +``` + +以上等价于如下的 `JSON` 结构: + +``` +{ "dog": { "tater": { "type": "pug" } } } +``` + +如果你不想的话,你不用声明所有的父表。TOML 知道该如何处理。 + +```toml +# [x] 你 +# [x.y] 不需要 +# [x.y.z] 这些 +[x.y.z.w] # 可以直接写 +``` + +空表是允许的,其中没有键值对。 + +只要父表没有被直接定义,而且没有定义一个特定的键,你可以继续写入: + +```toml +[a.b] +c = 1 + +[a] +d = 2 +``` + +然而你不能多次定义键和表格。这么做是不合法的。 + +```toml +# 别这么干! + +[a] +b = 1 + +[a] +c = 2 +# 也别这个干 + +[a] +b = 1 + +[a.b] +c = 2 +``` + +## 表格数组 + +最后要介绍的类型是表格数组。表格数组可以通过包裹在双方括号内的表格名来表达。使用相同的双方括号名称的表格是同一个数组的元素。表格按照书写的顺序插入。双方括号表格如果没有键值对,会被当成空表。 + +```toml +[[products]] +name = "Hammer" +sku = 738594937 + +[[products]] + +[[products]] +name = "Nail" +sku = 284758393 +color = "gray" +``` + +等价于以下的 `JSON` 结构: + +``` +{ + "products": [ + { "name": "Hammer", "sku": 738594937 }, + { }, + { "name": "Nail", "sku": 284758393, "color": "gray" } + ] +} +``` + +表格数组同样可以嵌套。只需在子表格上使用相同的双方括号语法。每一个双方括号子表格从属于最近定义的上层表格元素。 + +```toml +[[fruit]] + name = "apple" + + [fruit.physical] + color = "red" + shape = "round" + + [[fruit.variety]] + name = "red delicious" + + [[fruit.variety]] + name = "granny smith" + +[[fruit]] + name = "banana" + + [[fruit.variety]] + name = "plantain" +``` + +等价于如下的 `JSON` 结构: + +``` +{ + "fruit": [ + { + "name": "apple", + "physical": { + "color": "red", + "shape": "round" + }, + "variety": [ + { "name": "red delicious" }, + { "name": "granny smith" } + ] + }, + { + "name": "banana", + "variety": [ + { "name": "plantain" } + ] + } + ] +} +``` + +尝试定义一个普通的表格,使用已经定义的数组的名称,将抛出一个解析错误: + +```toml +# 不合法的 TOML + +[[fruit]] + name = "apple" + + [[fruit.variety]] + name = "red delicious" + + # 和上面冲突了 + [fruit.variety] + name = "granny smith" +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..7c40abcb484 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-YAML\346\240\274\345\274\217.md" @@ -0,0 +1,373 @@ +--- +slug: '/docs/core/gcfg-yaml' +title: '配置管理-YAML格式' +sidebar_position: 4 +hide_title: true +keywords: [配置管理,YAML格式,GoFrame,数据串行化,映射,数组,纯量,对象,复合结构,JavaScript转换] +description: '在GoFrame框架中使用YAML格式进行配置管理。YAML是一种便于人类读写的数据串行化格式,支持对象、数组和纯量等数据结构。文章还提供了YAML与JavaScript之间的转换示例,帮助读者更好地理解YAML格式的应用和实现。' +--- + +### 一、简介 + +YAML 语言(发音 /ˈjæməl/ )的设计目标,就是方便人类读写。它实质上是一种通用的数据串行化格式。 + +它的基本语法规则如下。 + +> - 大小写敏感 +> - 使用缩进表示层级关系 +> - 缩进时不允许使用Tab键,只允许使用空格。 +> - 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可 + +`#` 表示注释,从这个字符一直到行尾,都会被解析器忽略。 + +YAML 支持的数据结构有三种。 + +> - 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary) +> - 数组:一组按次序排列的值,又称为序列(sequence) / 列表(list) +> - 纯量(scalars):单个的、不可再分的值 + +以下分别介绍这三种数据结构。 + +### 二、对象 + +对象的一组键值对,使用冒号结构表示。 + +> ```javascript +> animal: pets +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { animal: 'pets' } +> ``` + +Yaml 也允许另一种写法,将所有键值对写成一个行内对象。 + +> ```javascript +> hash: { name: Steve, foo: bar } +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { hash: { name: 'Steve', foo: 'bar' } } +> ``` + +### 三、数组 + +一组连词线开头的行,构成一个数组。 + +> ```javascript +> - Cat +> - Dog +> - Goldfish +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> [ 'Cat', 'Dog', 'Goldfish' ] +> ``` + +数据结构的子成员是一个数组,则可以在该项下面缩进一个空格。 + +> ```javascript +> - +> - Cat +> - Dog +> - Goldfish +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> [ [ 'Cat', 'Dog', 'Goldfish' ] ] +> ``` + +数组也可以采用行内表示法。 + +> ```javascript +> animal: [Cat, Dog] +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { animal: [ 'Cat', 'Dog' ] } +> ``` + +### 四、复合结构 + +对象和数组可以结合使用,形成复合结构。 + +> ```javascript +> languages: +> - Ruby +> - Perl +> - Python +> websites: +> YAML: yaml.org +> Ruby: ruby-lang.org +> Python: python.org +> Perl: use.perl.org +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { languages: [ 'Ruby', 'Perl', 'Python' ], +> websites: +> { YAML: 'yaml.org', +> Ruby: 'ruby-lang.org', +> Python: 'python.org', +> Perl: 'use.perl.org' } } +> ``` + +### 五、纯量 + +纯量是最基本的、不可再分的值。以下数据类型都属于 JavaScript 的纯量。 + +> - 字符串 +> - 布尔值 +> - 整数 +> - 浮点数 +> - Null +> - 时间 +> - 日期 + +数值直接以字面量的形式表示。 + +> ```javascript +> number: 12.30 +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { number: 12.30 } +> ``` + +布尔值用 `true` 和 `false` 表示。 + +> ```javascript +> isSet: true +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { isSet: true } +> ``` + +`null` 用 `~` 表示。 + +> ```javascript +> parent: ~ +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { parent: null } +> ``` + +时间采用 ISO8601 格式。 + +> ```javascript +> iso8601: 2001-12-14t21:59:43.10-05:00 +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { iso8601: new Date('2001-12-14t21:59:43.10-05:00') } +> ``` + +日期采用复合 iso8601 格式的年、月、日表示。 + +> ```javascript +> date: 1976-07-31 +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { date: new Date('1976-07-31') } +> ``` + +YAML 允许使用两个感叹号,强制转换数据类型。 + +> ```javascript +> e: !!str 123 +> f: !!str true +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { e: '123', f: 'true' } +> ``` + +### 六、字符串 + +字符串是最常见,也是最复杂的一种数据类型。 + +字符串默认不使用引号表示。 + +> ```javascript +> str: 这是一行字符串 +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { str: '这是一行字符串' } +> ``` + +如果字符串之中包含空格或特殊字符,需要放在引号之中。 + +> ```javascript +> str: '内容: 字符串' +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { str: '内容: 字符串' } +> ``` + +单引号和双引号都可以使用,双引号不会对特殊字符转义。 + +> ```javascript +> s1: '内容\n字符串' +> s2: "内容\n字符串" +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { s1: '内容\\n字符串', s2: '内容\n字符串' } +> ``` + +单引号之中如果还有单引号,必须连续使用两个单引号转义。 + +> ```javascript +> str: 'labor''s day' +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { str: 'labor\'s day' } +> ``` + +字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为空格。 + +> ```javascript +> str: 这是一段 +> 多行 +> 字符串 +> ``` + +转为 JavaScript 如下。 + +> ```javascript +> { str: '这是一段 多行 字符串' } +> ``` + +多行字符串可以使用 `|` 保留换行符,也可以使用 `>` 折叠换行。 + +> ```javascript +> this: | +> Foo +> Bar +> that: > Foo +> Bar +> ``` + +转为 JavaScript 代码如下。 + +> ```javascript +> { this: 'Foo\nBar\n', that: 'Foo Bar\n' } +> ``` + +`+` 表示保留文字块末尾的换行, `-` 表示删除字符串末尾的换行。 + +> ```javascript +> s1: | +> Foo +> s2: |+ +> Foo +> s3: |- +> Foo +> ``` + +转为 JavaScript 代码如下。 + +> ```javascript +> { s1: 'Foo\n', s2: 'Foo\n\n\n', s3: 'Foo' } +> ``` + +字符串之中可以插入 HTML 标记。 + +> ```javascript +> message: | +>

    段落 +>

    ``` + +转为 JavaScript 如下。 + +> ```javascript +> { message: '\n

    \n 段落\n

    \n' } +> ``` + +### 七、引用 + +锚点 `&` 和别名 `*`,可以用来引用。 + +> ```javascript +> defaults: &defaults +> adapter: postgres +> host: localhost +> development: +> database: myapp_development +> <<: *defaults +> test: +> database: myapp_test +> <<: *defaults +> ``` + +等同于下面的代码。 + +> ```javascript +> defaults: +> adapter: postgres +> host: localhost +> development: +> database: myapp_development +> adapter: postgres +> host: localhost +> test: +> database: myapp_test +> adapter: postgres +> host: localhost +> ``` + +`&` 用来建立锚点( `defaults`), `<<` 表示合并到当前数据, `*` 用来引用锚点。 + +下面是另一个例子。 + +> ```javascript +> - &showell Steve +> - Clark +> - Brian +> - Oren +> - *showell +> ``` + +转为 JavaScript 代码如下。 + +> ```javascript +> [ 'Steve', 'Clark', 'Brian', 'Oren', 'Steve' ] +> ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..80dea88ff1f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" @@ -0,0 +1,389 @@ +--- +slug: '/docs/core/gcfg-funcs' +title: '配置管理-常用方法' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,配置管理,GetWithEnv,GetWithCmd,MustGetWithCmd,Data方法,Golang配置,环境变量获取,命令行获取,GoFrame框架] +description: 'GoFrame框架中的配置管理常用方法,包括如何从环境变量及命令行获取配置数据,Data方法用于获取和组装配置数据,以及配置适配器的使用。通过示例代码,帮助开发者更好地掌握配置管理相关的技术要点。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg) +::: +## `GetWithEnv` + +- 说明 + - `GetWithEnv` 方法会先从默认的配置文件中获取配置数据,获取为空的时候,将会去当前的环境变量中进行获取。需要注意的是名称命名转换规则: + - 环境变量会将名称转换为大写,名称中的 `.` 字符转换为 `_` 字符。 + - 参数名称中会将名称转换为小写,名称中的 `_` 字符转换为 `.` 字符。 +- 格式: + +```go +GetWithEnv(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + + +- 示例: + +```go +func ExampleConfig_GetWithEnv() { + var ( + key = `env.test` + ctx = gctx.New() + ) + v, err := g.Cfg().GetWithEnv(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("env:%s\n", v) + if err = genv.Set(`ENV_TEST`, "gf"); err != nil { + panic(err) + } + v, err = g.Cfg().GetWithEnv(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("env:%s", v) + + // Output: + // env: + // env:gf +} +``` + + +## `GetWithCmd` + +- 说明: `GetWithCmd` 方法与 `GetWithEnv` 方法类似,也是先从默认的配置对象中获取配置数据,但是获取为空的时候,是去命令行中获取配置信息。 +- 格式: + +```go +GetWithCmd(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + + +- 示例: + +```go +func ExampleConfig_GetWithCmd() { + var ( + key = `cmd.test` + ctx = gctx.New() + ) + v, err := g.Cfg().GetWithCmd(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("cmd:%s\n", v) + // Re-Initialize custom command arguments. + os.Args = append(os.Args, fmt.Sprintf(`--%s=yes`, key)) + gcmd.Init(os.Args...) + // Retrieve the configuration and command option again. + v, err = g.Cfg().GetWithCmd(ctx, key) + if err != nil { + panic(err) + } + fmt.Printf("cmd:%s", v) + + // Output: + // cmd: + // cmd:yes +} +``` + + +## `MustGetWithCmd` + +- 说明: `MustGetWithCmd` 方法与 `GetWithCmd` 方法类似,该方法只会返回配置内容,一旦内部发生任何错误,将会有 `panic。` +- 格式: + +```go +MustGetWithCmd(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + + +- 示例: + +```go +func ExampleConfig_MustGetWithCmd() { + var ( + key = `cmd.test` + ctx = gctx.New() + ) + v := g.Cfg().MustGetWithCmd(ctx, key) + + fmt.Printf("cmd:%s\n", v) + // Re-Initialize custom command arguments. + os.Args = append(os.Args, fmt.Sprintf(`--%s=yes`, key)) + gcmd.Init(os.Args...) + // Retrieve the configuration and command option again. + v = g.Cfg().MustGetWithCmd(ctx, key) + + fmt.Printf("cmd:%s", v) + + // Output: + // cmd: + // cmd:yes +} +``` + + +## `MustGetWithEnv` + +- 说明: `MustGetWithEnv` 方法与 `GetWithEnv` 方法类似,该方法只会返回配置内容,一旦内部发生任何错误,将会有 `panic。` +- 格式: + +```go +MustGetWithEnv(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + + +- 示例: + +```go +func ExampleConfig_MustGetWithEnv() { + var ( + key = `env.test` + ctx = gctx.New() + ) + v := g.Cfg().MustGetWithEnv(ctx, key) + + fmt.Printf("env:%s\n", v) + if err := genv.Set(`ENV_TEST`, "gf"); err != nil { + panic(err) + } + v = g.Cfg().MustGetWithEnv(ctx, key) + + fmt.Printf("env:%s", v) + + // Output: + // env: + // env:gf +} +``` + + +## `Data` + +- 说明: `Data` 方法从配置对象中获取配置数据,组装成 `map[string]interface{}` 类型。 +- 格式: + +```go +Data(ctx context.Context) (data map[string]interface{}, err error) +``` + + +- 示例: + +```go +func ExampleConfig_Data() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data, err := c.Data(ctx) + if err != nil{ + panic(err) + } + + fmt.Println(data) + + // Output: + // map[array:[1 2 3] redis:map[cache:127.0.0.1:6379,1 disk:127.0.0.1:6379,0] v1:1 v2:true v3:off v4:1.23] +} +``` + + +## `MustData` + +- 说明: `MustData` 方法从配置对象中获取配置数据,组装成 `map[string]interface{}` 类型。当该方法内部产生错误时不会返回错误,而是直接 `panic。` +- 格式: + +```go +MustData(ctx context.Context) map[string]interface{} +``` + + +- 示例: + +```go +func ExampleConfig_MustData() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data := c.MustData(ctx) + + fmt.Println(data) + + // Output: + // map[array:[1 2 3] redis:map[cache:127.0.0.1:6379,1 disk:127.0.0.1:6379,0] v1:1 v2:true v3:off v4:1.23] +} +``` + + +## `Get` + +- 说明: `Get` 方法从配置对象中获取配置数据,返回 `gvar` 泛型对象。 +- 格式: + +```go +Get(ctx context.Context, pattern string, def ...interface{}) (*gvar.Var, error) +``` + + +- 示例: + +```go +func ExampleConfig_Get() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data,err := c.Get(ctx,"redis") + + if err != nil { + panic(err) + } + fmt.Println(data) + + // Output: + // {"cache":"127.0.0.1:6379,1","disk":"127.0.0.1:6379,0"} +} +``` + + +## `MustGet` + +- 说明: `MustGet` 方法与 `Get` 类似,也是配置对象中获取配置数据,组装成 `gvar` 结构,但是返回参数只有一个: `*gvar.Var` +- 注意:当配置文件不存在或者是其他 `error` 的情况下,会直接 `panic`,需要做好异常捕获措施。 +- 格式: + +```go +MustGet(ctx context.Context, pattern string, def ...interface{}) *gvar.Var +``` + + +- 示例: + +```go +func ExampleConfig_MustGet() { + ctx := gctx.New() + content := ` +v1 = 1 +v2 = "true" +v3 = "off" +v4 = "1.23" +array = [1,2,3] +[redis] + disk = "127.0.0.1:6379,0" + cache = "127.0.0.1:6379,1" +` + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + c.GetAdapter().(*gcfg.AdapterFile).SetContent(content) + data := c.MustGet(ctx,"redis") + + fmt.Println(data) + + // Output: + // {"cache":"127.0.0.1:6379,1","disk":"127.0.0.1:6379,0"} +} +``` + + +## `GetAdapter` + +- 说明: `GetAdapter` 方法是获取当前运行的 `gcfg` 适配器信息,关于适配器相关,可以请点击 [配置管理-接口化设计](./配置管理-接口化设计/配置管理-接口化设计.md) +- 格式: + +```go +GetAdapter() Adapter +``` + + +- 示例: + +```go +func ExampleConfig_GetAdapter() { + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + adapter := c.GetAdapter() + fmt.Println(adapter) + + // Output: + // &{config.toml 0xc00014d720 0xc000371880 false} +} +``` + + +## `SetAdapter` + +- 说明: `SetAdapter` 方法是设置当前运行的 `gcfg` 适配器信息,关于适配器相关,可以请点击 [配置管理-接口化设计](./配置管理-接口化设计/配置管理-接口化设计.md) +- 格式: + +```go +SetAdapter(adapter Adapter) +``` + + +- 示例: + +```go +func ExampleConfig_SetAdapter() { + c, err := gcfg.New() + if err != nil{ + panic(err) + } + + adapter := c.GetAdapter() + c.SetAdapter(adapter) + fmt.Println(adapter) + + // Output: + // &{config.toml 0xc00014d720 0xc000371880 false} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" new file mode 100644 index 00000000000..74595acd5aa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterContent.md" @@ -0,0 +1,58 @@ +--- +slug: '/docs/core/gcfg-interface-adapter-content' +title: '配置管理-AdapterContent' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,配置管理,AdapterContent,配置内容,配置格式,g.Cfg单例,gcfg,配置示例,Golang框架] +description: '使用GoFrame框架中的AdapterContent接口来管理配置。用户可以通过给定具体的配置内容生成相应的Adapter接口对象,支持多种格式。通过示例代码展示了如何使用g.Cfg单例对象进行基于文件的配置管理。' +--- + +## `AdapterContent` + +`AdapterContent` 是基于配置内容的实现,用户可以给定具体的配置内容,生成 `Adapter` 接口对象。配置内容支持多种格式,格式列表同配置管理组件。 + +## 使用示例 + +大部分场景下,我们可以通过框架已经封装好的g.Cfg单例对象来便捷使用基于文件的配置管理实现。例如: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +const content = ` +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +` + +func main() { + var ctx = gctx.New() + adapter, err := gcfg.NewAdapterContent(content) + if err != nil { + panic(err) + } + config := gcfg.NewWithAdapter(adapter) + fmt.Println(config.MustGet(ctx, "server.address").String()) + fmt.Println(config.MustGet(ctx, "database.default").Map()) +} +``` + +运行后,终端输出: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" new file mode 100644 index 00000000000..a82fab9b53e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-AdapterFile.md" @@ -0,0 +1,107 @@ +--- +slug: '/docs/core/gcfg-interface-adapter-file' +title: '配置管理-AdapterFile' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,配置管理,AdapterFile,g.Cfg对象,基于文件的配置,gcfg.NewWithAdapter,配置组件,配置加载,Go语言] +description: 'GoFrame框架中配置管理的实现,主要通过AdapterFile进行基于文件的配置加载和读取。用户可以通过g.Cfg单例对象便捷地使用配置管理,亦可通过gcfg.NewWithAdapter方法创建配置管理对象。示例代码展示了如何在Go语言中实现和运行这些配置操作。' +--- + +## `AdapterFile` + +`AdapterFile` 是框架默认的配置管理实现方式,基于文件的配置加载和读取。 + +## 通过 `g.Cfg` 单例对象使用 + +大部分场景下,我们可以通过框架已经封装好的g.Cfg单例对象来便捷使用基于文件的配置管理实现。例如: + +`config.yaml` + +```yaml +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +``` + +`main.go` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(g.Cfg().MustGet(ctx, "server.address").String()) + fmt.Println(g.Cfg().MustGet(ctx, "database.default").Map()) +} +``` + +运行后,终端输出: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` + +## 通过 `gcfg.NewWithAdapter` 使用 + +我们也可以通过配置组件的 `NewWithAdapter` 方法来创建一个基于给定 `Adapter` 的配置管理对象,当然,在这里我们给一个 `AdapterFile` 接口对象。 + +`config.yaml` + +```yaml +server: + address: ":8888" + openapiPath: "/api.json" + swaggerPath: "/swagger" + dumpRouterMap: false + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true +``` + +`main.go` + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + adapter, err := gcfg.NewAdapterFile("config") + if err != nil { + panic(err) + } + config := gcfg.NewWithAdapter(adapter) + fmt.Println(config.MustGet(ctx, "server.address").String()) + fmt.Println(config.MustGet(ctx, "database.default").Map()) +} +``` + +运行后,终端输出: + +```html +:8888 +map[debug:true link:mysql:root:12345678@tcp(127.0.0.1:3306)/test] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..e577d93f332 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241/\351\205\215\347\275\256\347\256\241\347\220\206-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,57 @@ +--- +slug: '/docs/core/gcfg-interface' +title: '配置管理-接口化设计' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,配置管理,接口化设计,高扩展性,配置获取方式,etcd,zookeeper,consul,kubernetes,apollo] +description: 'GoFrame框架中gcfg组件的接口化设计及其高扩展性。通过接口化设计,用户可以自定义配置获取方式,包括使用etcd、zookeeper、consul、kubernetes configmap和apollo等,满足多样化的配置管理需求。详细的接口定义和实现设置指导为您提供配置功能的灵活性。' +--- + +`gcfg` 组件采用了接口化设计,以实现高扩展性。通过接口化设计的方式,使用者可以自定义对接的配置获取方式,例如 `etcd, zookeeper, consul, kubernetes configmap, apollo` 等等。 + +## 接口定义 + +[https://github.com/gogf/gf/blob/master/os/gcfg/gcfg\_adaper.go](https://github.com/gogf/gf/blob/master/os/gcfg/gcfg_adaper.go) + +```go +// Adapter is the interface for configuration retrieving. +type Adapter interface { + // Available checks and returns the configuration service is available. + // The optional parameter `resource` specifies certain configuration resource. + // + // It returns true if configuration file is present in default AdapterFile, or else false. + // Note that this function does not return error as it just does simply check for backend configuration service. + Available(ctx context.Context, resource ...string) (ok bool) + + // Get retrieves and returns value by specified `pattern`. + Get(ctx context.Context, pattern string) (value interface{}, err error) + + // Data retrieves and returns all configuration data as map type. + // Note that this function may lead lots of memory usage if configuration data is too large, + // you can implement this function if necessary. + Data(ctx context.Context) (data map[string]interface{}, err error) +} +``` + +## 设置接口实现 + +配置对象可以通过 `SetAdapter` 方法设置当前使用的接口实现。 + +```go +// SetAdapter sets the adapter of current Config object. +func (c *Config) SetAdapter(adapter Adapter) +``` + +## 获取接口实现 + +配置对象可以通过 `GetAdapter` 方法获取当前使用的接口实现。 + +```go +// GetAdapter returns the adapter of current Config object. +func (c *Config) GetAdapter() Adapter +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" new file mode 100644 index 00000000000..d1e9ecde4e1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\346\226\207\344\273\266\351\205\215\347\275\256.md" @@ -0,0 +1,208 @@ +--- +slug: '/docs/core/gcfg-file' +title: '配置管理-文件配置' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcfg,配置管理,文件配置,自动检测更新,目录配置,多格式支持,层级访问,缓存机制] +description: 'GoFrame框架中的gcfg组件,该组件支持多种配置文件格式如JSON、XML、YAML等,并提供灵活的目录配置和层级访问功能。它采用接口化设计,允许通过设置环境变量和命令行参数来动态修改配置项。独特的缓存机制和自动检测更新功能确保配置的高效性和实时性,是进行配置管理的理想选择。' +--- + +`gcfg` 组件采用接口化设计,默认提供的是基于文件系统的接口实现。支持的数据文件格式包括: `JSON/XML/YAML(YML)/TOML/INI/PROPERTIES`,项目中开发者可以灵活地选择自己熟悉的配置文件格式来进行配置管理。 + +## 配置文件 + +### 默认配置文件 + +配置对象我们推荐使用单例方式获取,单例对象将会按照文件后缀 `toml/yaml/yml/json/ini/xml/properties` 文自动检索配置文件。默认情况下会自动检索配置文件 `config.toml/yaml/yml/json/ini/xml/properties` 并缓存,配置文件在外部被修改时将会自动刷新缓存。 + +如果想要自定义文件格式,可以通过 `SetFileName` 方法修改默认读取的配置文件名称(如: `default.yaml`, `default.json`, `default.xml` 等等)。例如,我们可以通过以下方式读取 `default.yaml` 配置文件中的数据库 `database` 配置项。 + +```go +// 设置默认配置文件,默认读取的配置文件设置为 default.yaml +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("default.yaml") + +// 后续读取时将会读取到 default.yaml 配置文件内容 +g.Cfg().Get(ctx, "database") +``` + +### 默认文件修改 +:::tip +文件可以是一个具体的文件名称或者完整的文件绝对路径。 +::: +我们可以通过多种方式修改默认文件名称: + +1. 通过配置管理方法 `SetFileName` 修改。 +2. 修改命令行启动参数 - `gf.gcfg.file`。 +3. 修改指定的环境变量 - `GF_GCFG_FILE`。 + +假如我们的执行程序文件为 `main`,那么可以通过以下方式修改配置管理器的配置文件目录( `Linux` 下): + +1. **通过单例模式** + +```go +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetFileName("default.yaml") +``` + +2. **通过命令行启动参数** + +```shell + ./main --gf.gcfg.file=config.prod.toml +``` + +3. **通过环境变量(常用在容器中)** + - 启动时修改环境变量: + + ```shell + GF_GCFG_FILE=config.prod.toml; ./main + ``` + + - 使用 `genv` 模块来修改环境变量: + + ```go + genv.Set("GF_GCFG_FILE", "config.prod.toml") + ``` + +## 配置目录 + +### 目录配置方法 + +`gcfg` 配置管理器支持非常灵活的多目录自动搜索功能,通过 `SetPath` 可以修改目录管理目录为 **唯一** 的目录地址,同时,我们推荐通过 `AddPath` 方法添加多个搜索目录,配置管理器底层将会按照添加目录的顺序作为优先级进行自动检索。直到检索到一个匹配的文件路径为止,如果在所有搜索目录下查找不到配置文件,那么会返回失败。 + +### 默认目录配置 + +`gcfg` 配置管理对象初始化时,默认会自动添加以下配置文件搜索目录: + +1. **当前工作目录及其下的 `config`、 `manifest/config` 目录**:例如当前的工作目录为 `/home/www` 时,将会添加: +1. `/home/www` +2. `/home/www/config` +3. `/home/www/manifest/config` +2. **当前可执行文件所在目录及其下的 `config`、 `manifest/config` 目录**:例如二进制文件所在目录为 `/tmp` 时,将会添加: +1. `/tmp` +2. `/tmp/config` +3. `/tmp/manifest/config` +3. **当前 `main` 源代码包所在目录及其下的 `config`、 `manifest/config` 目录**(仅对源码开发环境有效):例如 `main` 包所在目录为 `/home/john/workspace/gf-app` 时,将会添加: +1. `/home/john/workspace/gf-app` +2. `/home/john/workspace/gf-app/config` +3. `/home/john/workspace/gf-app/manifest/config` + +### 默认目录修改 +:::warning +注意这里修改的参数必须是一个目录,不能是文件路径。 +::: +我们可以通过以下方式修改配置管理器的配置文件搜索目录,配置管理对象将会只在该指定目录执行配置文件检索: + +1. 通过配置管理器的 `SetPath` 方法手动修改; +2. 修改命令行启动参数 - `gf.gcfg.path`; +3. 修改指定的环境变量 - `GF_GCFG_PATH`; + +假如我们的执行程序文件为 `main`,那么可以通过以下方式修改配置管理器的配置文件目录(Linux下): + +1. **通过单例模式** + +```go +g.Cfg().GetAdapter().(*gcfg.AdapterFile).SetPath("/opt/config") +``` + +2. **通过命令行启动参数** + +```shell + ./main --gf.gcfg.path=/opt/config/ +``` + +3. **通过环境变量(常用在容器中)** + - 启动时修改环境变量: + + ```shell + GF_GCFG_PATH=/opt/config/; ./main + ``` + + - 使用 `genv` 模块来修改环境变量: + + ```go + genv.Set("GF_GCFG_PATH", "/opt/config") + ``` + +## 内容配置 + +`gcfg` 包也支持直接内容配置,而不是读取配置文件,常用于程序内部动态修改配置内容。通过以下包配置方法实现全局的配置: + +```go +func (c *AdapterFile) SetContent(content string, file ...string) +func (c *AdapterFile) GetContent(file ...string) string +func (c *AdapterFile) RemoveContent(file ...string) +func (c *AdapterFile) ClearContent() +``` + +需要注意的是该配置是全局生效的,并且优先级会高于读取配置文件。因此,假如我们通过 `SetContent("v = 1", "config.toml")` 配置了 `config.toml` 的配置内容,并且也同时存在 `config.toml` 配置文件,那么只会使用到 `SetContent` 的配置内容,而配置文件内容将会被忽略。 + +## 层级访问 + +在默认提供的文件系统接口实现下, `gcfg` 组件支持按层级获取配置数据,层级访问默认通过英文 `.` 号指定,其中 `pattern` 参数和 [通用编解码-gjson](../../组件列表/编码解码/通用编解码-gjson/通用编解码-gjson.md) 的 `pattern` 参数一致。例如以下配置( `config.yaml`): + +```yaml +server: + address: ":8199" + serverRoot: "resource/public" + +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/focus" + debug: true +``` + +例如针对以上配置文件内容的层级读取: + +```go +// :8199 +g.Cfg().Get(ctx, "server.address") + +// true +g.Cfg().Get(ctx, "database.default.debug") +``` + +## 注意事项 + +大家都知道,在 `Golang` 里面, `map/slice` 类型其实是一个”引用类型”(也叫”指针类型”),因此当你对这种类型的变量 键值对/索引项 进行修改时,会同时修改到其对应的底层数据。从效率上考虑, `gcfg` 包某些获取方法返回的数据类型为 `map/slice` 时,没有对其做值拷贝,因此当你对返回的数据进行修改时,会同时修改 `gcfg` 对应的底层数据。 + +例如: + +配置文件: + +```go +// config.json: +`{"map":{"key":"value"}, "slice":[59,90]}` +``` + +示例代码: + +```go +var ctx = gctx.New() + +m := g.Cfg().MustGet(ctx, "map").Map() +fmt.Println(m) + +// Change the key-value pair. +m["key"] = "john" + +// It changes the underlying key-value pair. +fmt.Println(g.Cfg().MustGet(ctx, "map").Map()) + +s := g.Cfg().MustGet(ctx, "slice").Slice() +fmt.Println(s) + +// Change the value of specified index. +s[0] = 100 + +// It changes the underlying slice. +fmt.Println(g.Cfg().MustGet(ctx, "slice").Slice()) + +// output: +// map[key:value] +// map[key:john] +// [59 90] +// [100 90] +``` + +## 检测更新 + +配置管理器使用了 **缓存机制**,当配置文件第一次被读取后会被缓存到内存中,下一次读取时将会直接从缓存中获取,以提高性能。同时,配置管理器提供了对配置文件的 **自动检测更新机制**,当配置文件在外部被修改后,配置管理器能够即时地刷新配置文件的缓存内容。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..7a99cf524a5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206-\351\205\215\347\275\256\345\257\271\350\261\241.md" @@ -0,0 +1,81 @@ +--- +slug: '/docs/core/gcfg-cfg' +title: '配置管理-配置对象' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,配置管理,全局配置,单例模式,g.Cfg,gcfg.Instance,MySQL数据库,配置读取,自动检索,模板引擎] +description: '使用GoFrame框架进行配置管理,使用单例模式获取配置对象。通过g.Cfg()和gcfg.Instance方法,可以方便地读取全局配置,如数据库连接信息和模板引擎目录配置。支持通过文件后缀名自动检索和缓存配置文件,提升开发效率。' +--- + +我们推荐使用单例模式获取配置管理对象。我们可以方便地通过 `g.Cfg()` 获取默认的全局配置管理对象。同时,我们也可以通过 `gcfg.Instance` 包方法获取配置管理对象单例。 + +### 使用 `g.Cfg` + +我们来看一个示例,演示如何读取全局配置的信息。需要注意的是,全局配置是与框架相关的,因此统一使用 `g.Cfg()` 进行获取。以下是一个默认的全局配置文件,包含了模板引擎的目录配置以及 `MySQL` 数据库集群(两台 `master`)的配置。 + +示例配置: + +```yaml +viewpath: "/home/www/templates/" +database: + default: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "master" + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + role: "slave" +``` + +示例代码: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(g.Cfg().Get(ctx, "viewpath")) + fmt.Println(g.Cfg().Get(ctx, "database.default.0.role")) +} +``` + +以上示例为读取数据库的第一个配置的 `role` 信息。运行后输出: + +``` +/home/www/templates/ +master +``` + +可以看到,我们可以通过 `g.Cfg()` 方法获取一个全局的配置管理器单例对象。配置文件内容可以通过英文“ `.`”号进行层级访问(数组默认从 `0` 开始), `pattern` 参数 `database.default.0.role` 表示读取 `database` 配置项中 `default` 数据库集群中的第 `0` 项数据库服务器的 `role` 数据。 + +### 使用 `gcfg.Instance` + +当然也可以独立使用 `gcfg` 包,通过 `Instance` 方法获取单例对象。 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gcfg" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + fmt.Println(gcfg.Instance().Get(ctx, "viewpath")) + fmt.Println(gcfg.Instance().Get(ctx, "database.default.0.role")) +} +``` + +### 自动检索特性 + +单例对象在创建时会按照文件后缀 `toml/yaml/yml/json/ini/xml/properties` 自动检索配置文件。默认情况下会自动检索配置文件 `config.toml/yaml/yml/json/ini/xml/properties` 并缓存,配置文件在外部被修改时将会自动刷新缓存。 + +为方便多文件场景下的配置文件调用,简便使用并提高开发效率,单例对象在创建时将会自动使用 **单例名称** 进行文件检索。例如: `g.Cfg("redis")` 获取到的单例对象将默认会自动检索 `redis.toml/yaml/yml/json/ini/xml/properties`,如果检索成功那么将该文件加载到内存缓存中,下一次将会直接从内存中读取;当该文件不存在时,则使用默认的配置文件( `config.toml`)。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..f19366aac73 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\205\215\347\275\256\347\256\241\347\220\206/\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/core/gcfg' +title: '配置管理' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcfg,配置管理,文件系统接口,配置文件格式,环境变量,命令行参数,热更新特性,单例管理模式] +description: 'GoFrame框架的配置管理组件gcfg,该组件支持并发安全操作,提供文件系统接口实现,支持多种配置文件格式如yaml、toml、json等,并允许在配置项缺失时读取环境变量或命令行参数,具有自动检测配置文件热更新、单例管理等特性。' +--- + +## 基本介绍 + +`GoFrame` 的配置管理由 `gcfg` 组件实现, `gcfg` 组件的所有方法是并发安全的。 `gcfg` 组件采用接口化设计,默认提供的是基于文件系统的接口实现。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gcfg" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcfg) + +## 组件特性 + +`gcfg` 组件具有以下显著特性: + +- 接口化设计,很高的灵活性及扩展性,默认提供文件系统接口实现 +- 支持多种常见配置文件格式: `yaml/toml/json/xml/ini/properties` +- 支持配置项不存在时读取指定环境变量或命令行参数 +- 支持检索读取资源管理组件中的配置文件 +- 支持配置文件自动检测热更新特性 +- 支持层级访问配置项 +- 支持单例管理模式 + +## 注意事项 + +框架配置组件支持多种常用的数据格式,但在后续的示例代码中均使用 `yaml` 数据格式来做演示说明。在使用中,请随意使用习惯的数据格式 **不用局限于官网示例使用的 `yaml` 数据格式**。例如,在业务项目模板中提供的是 `config.yaml` 配置文件模板(因为默认模板只能提供一种啊),您也可以直接修改为 `config.toml` 或者 `config.ini` 等支持的数据格式, **配置组件也能自动根据文件名后缀识别读取**。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..250b5027692 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\205\266\344\273\226\347\211\271\346\200\247.md" @@ -0,0 +1,218 @@ +--- +slug: '/docs/core/gerror-other' +title: '错误处理-其他特性' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,错误处理,NewOption,fmt格式化,日志输出,堆栈打印,错误组件,代码调用链,堆栈信息,环境变量] +description: '在GoFrame框架中进行错误处理的其他特性,涵盖了如何使用NewOption自定义错误对象创建、通过fmt格式化打印完整的堆栈信息,以及日志输出的强大支持。此外,从v2.6.0版本开始,错误组件在打印堆栈信息时提供了不同的打印模式以供选择,满足不同场景下的开发需求。' +--- + +## `NewWithOption` 自定义的错误创建 + +- 说明:用于自定义配置的错误对象创建。 +- 格式: + + ```go + // NewWithOption creates and returns a custom error with Option. + // It is the senior usage for creating error, which is often used internally in framework. + NewWithOption(option Option) error + ``` + +- 示例: + + ```go + func ExampleNewWithOption() { + err := gerror.NewWithOption(gerror.Option{ + Text: "this feature is disabled in this storage", + Code: gcode.CodeNotSupported, + }) + } + ``` + 其中,`Option`的定义如下: + ```go + // Option is option for creating error. + type Option struct { + Error error // Wrapped error if any. + Stack bool // Whether recording stack information into error. + Text string // Error text, which is created by New* functions. + Code gcode.Code // Error code if necessary. + } + ``` + + +## `fmt` 格式化 + +通过前面示例我们可以看到,通过 `%+v` 的打印格式可以打印出完整的堆栈信息,当然 `gerror.Error` 对象支持多种`fmt`格式: + +| 格式符 | 输出内容 | +| --- | --- | +| `%v`, `%s` | 打印所有的层级错误信息,构成完成的字符串返回,多个层级使用 `:` 拼接。 | +| `%-v`, `%-s` | 打印当前层级的错误信息,返回字符串。 | +| `%+s` | 打印完整的堆栈信息列表。 | +| `%+v` | 打印所有的层级错误信息字符串,以及完整的堆栈信息,等同于 `%s\n%+s`。 | + +使用示例: + +```go +package main + +import ( + "errors" + "fmt" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Printf(" %%s: %s\n", err) + fmt.Printf("%%-s: %-s\n", err) + fmt.Println("%+s: ") + fmt.Printf("%+s\n", err) +} +``` +执行后,输出示例: +```text + %s: api calling failed: adding failed: sql error +%-s: api calling failed +%+s: +1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 +2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 +3. sql error +``` + +## 日志组件支持 + +`glog` 日志组件天然支持对 `gerror` 错误堆栈打印支持,这种支持不是强耦合性的,而是通过 `fmt` 格式化打印接口支持的。 + +使用示例: + +```go +package main + +import ( + "errors" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + g.Log().Printf("%+v", err) +} + + +``` +执行后,输出示例: +```text +2020-10-17 15:22:26.793 api calling failed: adding failed: sql error +1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 +2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 +3. sql error +``` + +## 堆栈打印模式 + +该特性从框架 `v2.6.0` 版本开始提供。 + +错误组件在打印堆栈信息时,支持使用者通过环境变量( `GF_GERROR_STACK_MODE`)或者命令行启动参数( `gf.gerror.stack.mode`)指定堆栈打印信息模式: + +| 堆栈模式 | 是否默认 | 说明 | +| --- | --- | --- | +| `brief` | 是 | **简略模式**。错误堆栈打印时,不会打印框架相关的堆栈。 | +| `detail` | | **详情模式**。错误堆栈打印时,会打印完整的框架组件代码调用链路。 | + +**在详情模式( `detail`)下,将会打印错误对象中完整的框架堆栈信息。** 比如,在以下错误堆栈示例中,可以看到,大部分框架堆栈信息对于错误定位来说,其实是没有太大意义的,作为开发者,更多关心的自身代码层面的堆栈信息。 + +```text +2022-10-08 21:07:00.751 [ERRO] {328d1204e2191c179a09086890c857b8} request done, cost: 3 ms, code: -1, message: "", detail: , error: GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0}: rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +1. GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0} + 1). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).doGetParamsJson + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:66 + 2). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).GetParams + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:36 + 3). github.com/khaos/eros/app/khaos-oss/internal/controller.(*cParams).GetOne + /root/workspace/khaos/eros/app/khaos-oss/internal/controller/params.go:21 + 4). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:152 + 5). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 6). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:129 + 7). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:75 + 8). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 9). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 10). github.com/khaos/eros/app/khaos-oss/internal/logic/middleware.(*sMiddleware).CheckLimit + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/middleware/middleware.go:27 + 11). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 12). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 13). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 14). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 15). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 16). github.com/khaos/eros/utility/server.MiddlewareCommonResponse + /root/workspace/khaos/eros/utility/server/server_common.go:14 + 17). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 18). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 19). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 20). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 21). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 22). github.com/khaos/eros/utility/server.MiddlewareLogging + /root/workspace/khaos/eros/utility/server/server.go:46 + 23). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 24). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 + 25). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:95 + 26). github.com/gogf/gf/v2/util/gutil.TryCatch + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/util/gutil/gutil.go:56 + 27). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:49 + 28). github.com/gogf/gf/v2/net/ghttp.MiddlewareCORS + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_middleware_cors.go:12 + 29). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5 + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_request_middleware.go:96 + 30). github.com/gogf/gf/v2/net/ghttp.niceCallFunc + /root/go/pkg/mod/github.com/gogf/gf/v2@v2.1.4/net/ghttp/ghttp_func.go:55 +2. rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +``` + +而**在简略模式( `brief`)下,堆栈信息中将不会打印框架的堆栈信息**。例如: + +```text +2022-10-08 21:07:00.751 [ERRO] {328d1204e2191c179a09086890c857b8} request done, cost: 3 ms, code: -1, message: "", detail: , error: GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0}: rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +1. GetParams failed: {ResourceId:tdxxxx-a2c378bd Component: Version:0} + 1). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).doGetParamsJson + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:66 + 2). github.com/khaos/eros/app/khaos-oss/internal/logic/params.(*sParams).GetParams + /root/workspace/khaos/eros/app/khaos-oss/internal/logic/params/params.go:36 + 3). github.com/khaos/eros/app/khaos-oss/internal/controller.(*cParams).GetOne + /root/workspace/khaos/eros/app/khaos-oss/internal/controller/params.go:21 +2. rpc error: code = NotFound desc = cluster.khaos.tencent.com "tdxxxx-a2c378bd" not found +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..1a426adb068 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\240\206\346\240\210\347\211\271\346\200\247.md" @@ -0,0 +1,256 @@ +--- +slug: '/docs/core/gerror-stack' +title: '错误处理-堆栈特性' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,错误处理,堆栈特性,gerror,Go语言,错误追溯,堆栈记录,Wrap方法,错误信息,调用链] +description: 'GoFrame框架中的错误处理堆栈特性,通过gerror模块实现对错误的堆栈追溯,增强错误信息的可读性和可维护性。文中详细讲解了如何利用Wrap方法叠加错误信息、检查错误堆栈的HasStack方法、获取堆栈信息的Stack方法以及访问不同层级错误信息的方法。' +--- + +## 错误堆栈 + +标准库的 `error` 错误实现比较简单,无法进行堆栈追溯,对于产生错误时的上层调用者来讲不是很友好,无法获得错误的调用链详细信息。 `gerror` 支持错误堆栈记录,通过 `New/Newf`、 `Wrap/Wrapf`, `WrapSkip/WrapSkipf` 等方法均会自动记录当前错误产生时的堆栈信息。 + +示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/errors/gerror" +) + +func OpenFile() error { + return gerror.New("permission denied") +} + +func OpenConfig() error { + return gerror.Wrap(OpenFile(), "configuration file opening failed") +} + +func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") +} + +func main() { + fmt.Printf("%+v", ReadConfig()) +} +``` +执行后,输出示例: +```text +reading configuration failed: configuration file opening failed: permission denied +1. reading configuration failed + 1). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 2). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +2. configuration file opening failed + 1). main.OpenConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:14 + 2). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 3). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +3. permission denied + 1). main.OpenFile + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:10 + 2). main.OpenConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:14 + 3). main.ReadConfig + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:18 + 4). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/errors/gerror/gerror2.go:25 +``` + +:::tip +其中打印的错误堆栈路径为笔者本地执行的代码路径。 +::: + +可以看到,调用端可以通过 `Wrap` 方法将底层的错误信息进行层级叠加,并且包含完整的错误堆栈信息。 + +## `HasStack` 判断错误是否带堆栈 + +- 说明:通过 `HasStack` 方法我们可以判断给定的 `error` 接口对象是否实现(包含)了堆栈信息。 +- 格式: + + ```go + // HasStack checks and reports whether `err` implemented interface `gerror.IStack`. + HasStack(err error) bool + ``` + +- 示例: + + ```go + func ExampleHasStack() { + err1 := errors.New("sql error") + err2 := gerror.New("write error") + fmt.Println(gerror.HasStack(err1)) + fmt.Println(gerror.HasStack(err2)) + + // Output: + // false + // true + } + ``` + +:::warning +目前该方法是通过判断错误对象是否实现`gerror.IStack`接口来实现的,如果错误对象未实现该接口那么将会无法识别其是否带有堆栈信息。 +::: + +## `Stack` 获取堆栈信息 + +- 说明:通过 `Stack` 方法我们可以获得 `error` 对象的完整堆栈信息,返回堆栈列表字符串。 +- 格式: + + ```go + // Stack returns the stack callers as string. + // It returns the error string directly if the `err` does not support stacks. + Stack(err error) string + ``` + +- 示例: + + ```go + func ExampleStack() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Println(gerror.Stack(err)) + } + ``` + 执行后,输出示例信息: + ```go + 1. api calling failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:14 + 2. adding failed + 1). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.example/other/test.go:13 + 3. sql error + ``` + +:::warning +目前该方法是通过获取错误对象实现的`gerror.IStack`接口来实现的,如果错误对象未实现该接口那么该方法将会返回空字符串。 +::: + +## `Current` 获取当前 `error` + +- 说明: `Current` 方法用于获取当前层级的错误信息,通过 `error` 接口对象返回。 +- 格式: + + ```go + // Current creates and returns the current level error. + // It returns nil if current level error is nil. + Current(err error) error + ``` + +- 示例: + + ```go + func ExampleCurrent() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + fmt.Println(err) + fmt.Println(gerror.Current(err)) + + // Output: + // api calling failed: adding failed: sql error + // api calling failed + } + ``` + + +## `Unwrap` 获取下一层 `error` + +- 说明: `Unwrap` 方法用于获取层级错误的下一级错误 `error` 接口对象。当下一层级不存在时,返回 `nil`。 +- 格式: + + ```go + // Unwrap returns the next level error. + // It returns nil if current level error or the next level error is nil. + Unwrap(err error) error + ``` + +- 示例1:简单的错误层级访问示例。 + + ```go + func ExampleUnwrap() { + var err error + err = errors.New("sql error") + err = gerror.Wrap(err, "adding failed") + err = gerror.Wrap(err, "api calling failed") + + fmt.Println(err) + + err = gerror.Unwrap(err) + fmt.Println(err) + + err = gerror.Unwrap(err) + fmt.Println(err) + + // Output: + // api calling failed: adding failed: sql error + // adding failed: sql error + // sql error + } + ``` + +- 示例2:常见遍历逻辑代码示例。 + + ```go + func IsGrpcErrorNotFound(err error) bool { + if err != nil { + for e := err; e != nil; e = gerror.Unwrap(e) { + if s, ok := status.FromError(e); ok && s != nil && s.Code() == codes.NotFound { + return true + } + } + } + return false + } + ``` + + +## `Cause` 获取根错误 `error` + +- 说明:通过 `Cause` 方法我们可以获得 `error` 对象的根错误信息(原始错误)。 +- 格式: + + ```go + // Cause returns the root cause error of `err`. + Cause(err error) error + ``` + +- 示例: + + ```go + package main + + import ( + "fmt" + "github.com/gogf/gf/v2/errors/gerror" + ) + + func OpenFile() error { + return gerror.New("permission denied") + } + + func OpenConfig() error { + return gerror.Wrap(OpenFile(), "configuration file opening failed") + } + + func ReadConfig() error { + return gerror.Wrap(OpenConfig(), "reading configuration failed") + } + + func main() { + fmt.Println(gerror.Cause(ReadConfig())) + } + + // Output: + // permission denied + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..9e3ec2fa28f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\345\270\270\347\224\250\346\226\271\346\263\225.md" @@ -0,0 +1,74 @@ +--- +slug: '/docs/core/gerror-funcs' +title: '错误处理-常用方法' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误创建,New,Wrap,NewSkip,错误码,gerror,堆栈信息] +description: 'GoFrame框架中关于错误处理的常用方法,包括错误创建、错误包装以及错误码相关的函数,提供了创建和包装自定义错误信息的多种方式,以便开发者在使用GoFrame框架时能够有效管理错误和调试代码。' +--- + +本章节仅介绍一些常用方法,完整的错误方法请参考接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror) + + +## `New/Newf` + +- 说明:用于创建一个自定义错误信息的 `error` 对象,并包含堆栈信息。 +- 格式: + + ```go + // New creates and returns an error which is formatted from given text. + New(text string) error + + // Newf returns an error that formats as the given format and args. + Newf(format string, args ...interface{}) error + ``` + + +## `Wrap/Wrapf` + +- 说明:用于包裹其他错误 `error` 对象,构造成多级的错误信息,包含堆栈信息。 +- 格式: + + ```go + // Wrap wraps error with text. It returns nil if given err is nil. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func Wrap(err error, text string) error + + // Wrapf returns an error annotating err with a stack trace at the point Wrapf is called, and the format specifier. + // It returns nil if given `err` is nil. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func Wrapf(err error, format string, args ...interface{}) error + ``` + + +## `NewSkip/NewSkipf` + +- 说明:用于创建一个自定义错误信息的 `error` 对象,并且忽略部分堆栈信息(按照当前调用方法位置往上忽略)。高级功能,一般开发者很少用得到。 +- 格式: + + ```go + // NewSkip creates and returns an error which is formatted from given text. + // The parameter `skip` specifies the stack callers skipped amount. + func NewSkip(skip int, text string) error + + // NewSkipf returns an error that formats as the given format and args. + // The parameter `skip` specifies the stack callers skipped amount. + func NewSkipf(skip int, format string, args ...interface{}) error + ``` + +## `WrapSkip/WrapSkipf` + +- 说明:类似于`Warp/Wrapf`方法,但会忽略部分堆栈信息。 +- 格式: + + ```go + // WrapSkip wraps error with text. It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func WrapSkip(skip int, err error, text string) error + + // WrapSkipf wraps error with text that is formatted with given format and args. It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + // Note that it does not lose the error code of wrapped error, as it inherits the error code from it. + func WrapSkipf(skip int, err error, format string, args ...interface{}) error + ``` diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..1920d9070c6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/core/gerror-benchmark' +title: '错误处理-性能测试' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,性能测试,基准测试,性能优化,Go语言,编程框架,代码效率,Go开发] +description: '涵盖了使用GoFrame框架进行错误处理的基准性能测试,提供了常用方法的性能对比结果。通过这些性能测试,开发者可以更好地理解和优化Go语言编程中的错误处理效率,从而提升整个应用程序的性能表现。数据来源于GitHub上GoFrame的开源项目,详细列举了不同错误处理方法在不同配置下的性能表现,供开发者参考。' +--- + +常用方法的基准性能测试: [https://github.com/gogf/gf/blob/master/errors/gerror/gerror\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/errors/gerror/gerror_z_bench_test.go) + +```bash +$ go test *.go -bench=".*" -benchmem +goos: linux +goarch: amd64 +Benchmark_New-4 1890454 589 ns/op 256 B/op 1 allocs/op +Benchmark_Newf-4 1569258 762 ns/op 324 B/op 3 allocs/op +Benchmark_Wrap-4 2012910 600 ns/op 256 B/op 1 allocs/op +Benchmark_Wrapf-4 1600197 749 ns/op 324 B/op 3 allocs/op +Benchmark_NewSkip-4 2009928 579 ns/op 256 B/op 1 allocs/op +Benchmark_NewSkipf-4 1578370 752 ns/op 324 B/op 3 allocs/op +Benchmark_NewCode-4 2060835 591 ns/op 256 B/op 1 allocs/op +Benchmark_NewCodef-4 1603306 752 ns/op 324 B/op 3 allocs/op +Benchmark_NewCodeSkip-4 2047794 584 ns/op 256 B/op 1 allocs/op +Benchmark_NewCodeSkipf-4 1524495 779 ns/op 324 B/op 3 allocs/op +Benchmark_WrapCode-4 1972147 603 ns/op 256 B/op 1 allocs/op +Benchmark_WrapCodef-4 1651316 735 ns/op 324 B/op 3 allocs/op +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" new file mode 100644 index 00000000000..317e9862340 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\346\234\200\344\275\263\345\256\236\350\267\265.md" @@ -0,0 +1,116 @@ +--- +slug: '/docs/core/gerror-practice' +title: '错误处理-最佳实践' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,最佳实践,错误堆栈,Wrap方式,错误对象,Golang,打印错误,错误信息] +description: '在GoFrame框架中有效地处理错误,包括打印错误对象的堆栈信息和正确使用Wrap方式包裹错误对象,避免错误信息的重复。通过具体代码示例,展示应如何优化错误处理,提高GoFrame框架的应用程序稳定性。' +--- + +## 打印错误对象中的堆栈信息 + +通过 `fmt/glog` 或者其他包打印错误对象时,默认情况下不会输出错误堆栈信息。例如: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" +) + +func main() { + _, err := gjson.Encode(func() {}) + fmt.Printf("err: %v", err) +} +``` + +执行后,终端输出: + +```html +err: json.Marshal failed: json: unsupported type: func() +``` + +打印错误对象中的错误堆栈信息,应当使用 `%+v` 的格式化方式。例如: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" +) + +func main() { + _, err := gjson.Encode(func() {}) + fmt.Printf("err: %+v", err) +} +``` + +执行后,终端输出: + +```text +err: json.Marshal failed: json: unsupported type: func() +1. json.Marshal failed + 1). github.com/gogf/gf/v2/internal/json.Marshal + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/internal/json/json.go:30 + 2). github.com/gogf/gf/v2/encoding/gjson.Encode + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/encoding/gjson/gjson_stdlib_json_util.go:41 + 3). main.main + /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/.test/main.go:10 +2. json: unsupported type: func() +``` + +## 使用Wrap方法时,避免重复的错误信息打印 + +当对错误对象进行 `Wrap` 时,不要将错误对象打印到 `Wrap` 的错误信息中,因为 `Wrap` 时本身会将目标错误对象包裹到创建的新的错误对象内部。如果将错误信息再打印包含在错误字符串中,那么在打印错误堆栈的时候会出现错误信息重复。例如(为简化示例,这里没有打印堆栈信息): + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + _, err1 := gjson.Encode(func() {}) + err2 := gerror.Wrapf(err1, `error occurred: %v`, err1) + fmt.Printf("err: %v", err2) +} +``` + +执行后,终端输出,可以看到,打印的错误信息出现了重复: + +```text +err: error occurred: json.Marshal failed: json: unsupported type: func(): json.Marshal failed: json: unsupported type: func() +``` + +我们将上面的示例代码修复一下,如下: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/errors/gerror" +) + +func main() { + _, err1 := gjson.Encode(func() {}) + err2 := gerror.Wrap(err1, `error occurred`) + fmt.Printf("err: %v", err2) +} +``` + +执行后,终端输出: + +```text +err: error occurred: json.Marshal failed: json: unsupported type: func() +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" new file mode 100644 index 00000000000..89c6488a8f3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\346\257\224\350\276\203.md" @@ -0,0 +1,80 @@ +--- +slug: '/docs/core/gerror-comparison' +title: '错误处理-错误比较' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误比较,Equal方法,Is方法,错误对象,接口定义,错误组件,标准库] +description: 'GoFrame框架中错误处理的比较方法,包括Equal和Is方法的使用。Equal方法用于判断两个错误对象是否相同,而Is方法用于判断给定错误是否在指定的错误链路中。并提供了接口定义和使用示例,帮助开发者更好地理解和应用这些方法。' +--- + +## `Equal` 比较方法 + +错误对象支持比较, `Equal` 方法用于完整判断两个 `error` 是否相同。主要通过以下方法: + +```go +// Equal reports whether current error `err` equals to error `target`. +// Please note that, in default comparison for `Error`, +// the errors are considered the same if both the `code` and `text` of them are the same. +func Equal(err, target error) bool +``` + +### 接口定义 + +如果自定义的错误数据结构需要支持比较,需要自定义的错误结构实现以下接口: + +```go +// IEqual is the interface for Equal feature. +type IEqual interface { + Error() string + Equal(target error) bool +} +``` + +通过 `GoFrame` 框架错误组件创建的错误对象已经实现实现了该接口,组件默认比较逻辑判断错误码和错误信息。 +:::info +需要注意,如果两个错误均不带错误码,并且错误信息相等,那么组件认为两个错误相等。 +::: +### 使用示例 + +```go +func ExampleEqual() { + err1 := errors.New("permission denied") + err2 := gerror.New("permission denied") + err3 := gerror.NewCode(gcode.CodeNotAuthorized, "permission denied") + fmt.Println(gerror.Equal(err1, err2)) + fmt.Println(gerror.Equal(err2, err3)) + + // Output: + // true + // false +} +``` + +## `Is` 包含判断 + +错误对象支持**包含**判断, `Is` 方法用于判断给定的 `error` 是否在指定的 `error` 错误链路中(如果 `error` 带有堆栈,会递归判断)。主要通过以下方法: + +```go +// Is reports whether current error `err` has error `target` in its chaining errors. +// It is just for implements for stdlib errors.Unwrap from Go version 1.17. +func Is(err, target error) bool +``` + +使用示例 + +```go +func ExampleIs() { + err1 := errors.New("permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.Is(err1, err1)) + fmt.Println(gerror.Is(err2, err2)) + fmt.Println(gerror.Is(err2, err1)) + fmt.Println(gerror.Is(err1, err2)) + + // Output: + // false + // true + // true + // false +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" new file mode 100644 index 00000000000..9cd825b916b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\345\206\205\347\275\256\351\224\231\350\257\257\347\240\201.md" @@ -0,0 +1,16 @@ +--- +slug: '/docs/core/gerror-code-builtin' +title: '错误处理-内置错误码' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame框架,错误处理,内置错误码,错误码定义,业务错误码,gcode,GoFrame,框架预留,错误码使用,整型错误码] +description: 'GoFrame框架提供的常见内置错误码定义,供开发者直接使用。注意,业务使用的整型错误码应定义在大于1000,以避免与框架预留错误码冲突。提供了错误码定义文件的链接,帮助开发者更好地进行错误处理与使用。' +--- + +框架预定义了一些常见的错误码,开发者可直接使用: + +[https://github.com/gogf/gf/blob/master/errors/gcode/gcode.go](https://github.com/gogf/gf/blob/master/errors/gcode/gcode.go) + +:::warning +需要注意,如果业务使用整型错误码, `<1000` 的错误码都被框架预留,业务错误码请定义 `>1000`。 +::: \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" new file mode 100644 index 00000000000..edf158febba --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\345\256\236\347\216\260.md" @@ -0,0 +1,96 @@ +--- +slug: '/docs/core/gerror-code-custom' +title: '错误处理-错误码实现' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误码,自定义错误码,gcode,业务错误码,HttpCode,中间件,代码示例] +description: '在GoFrame框架中自定义实现业务的错误码,通过实现gcode.Code接口,可以定义复杂的错误码;并提供错误处理的代码实例,展示如何使用自定义错误码与中间件结合。' +--- + +当业务需要更复杂的错误码定义时,我们可以自定义实现业务的错误码,只需要实现 `gcode.Code` 相关的接口即可。 + +我们来看个例子。 + +## 自定义错误码 + +定义基础的业务错误码结构体,实现 `gcode.code` 接口。 + +```go +type BizCode struct { + code int + message string + detail BizCodeDetail +} +type BizCodeDetail struct { + Code string + HttpCode int +} + +func (c BizCode) BizDetail() BizCodeDetail { + return c.detail +} + +func (c BizCode) Code() int { + return c.code +} + +func (c BizCode) Message() string { + return c.message +} + +func (c BizCode) Detail() interface{} { + return c.detail +} + +func New(httpCode int, code string, message string) gcode.Code { + return BizCode{ + code: 0, + message: message, + detail: BizCodeDetail{ + Code: code, + HttpCode: httpCode, + }, + } +} +``` + +定义业务错误码 + +```go +var ( + CodeNil = New(200, "OK", "") + CodeNotFound = New(404, "Not Found", "Resource does not exist") + CodeInternal = New(500, "Internal Error", "An error occurred internally") + // ... +) +``` + +## 使用到中间件 + +```go +func ResponseHandler(r *ghttp.Request) { + r.Middleware.Next() + // There's custom buffer content, it then exits current handler. + if r.Response.BufferLength() > 0 { + return + } + var ( + err = r.GetError() + res = r.GetHandlerResponse() + code = gerror.Code(err) + ) + if code == gcode.CodeNil && err != nil { + code = CodeInternal + } else { + code = CodeNil + } + if bizCode, ok := code.(BizCode); ok { + r.Response.WriteStatus(bizCode.BizDetail().HttpCode) + } + r.Response.WriteJson(g.Map{ + `code`: gcode.CodeOK.Code(), + `message`: gcode.CodeOK.Message(), + `data`: res, + }) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" new file mode 100644 index 00000000000..6f7c34959ab --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\211\251\345\261\225.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/core/gerror-code-extension' +title: '错误处理-错误码扩展' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误码扩展,业务逻辑,中间件,自定义扩展,错误码定义,WithCode方法,gcode] +description: '在GoFrame框架中进行错误处理,尤其是错误码的扩展。通过使用错误码的Detail参数,可以根据业务需求自定义错误码。在具体场景中,我们可以通过WithCode方法灵活地创建新的错误码,并在中间件中进行应用和处理。该解决方案不仅增强了业务逻辑的灵活性,还为上层提供了详细的错误信息。' +--- + +当业务需要复杂的错误码定义时,我们推荐灵活使用错误码的 `Detail` 参数来扩展错误码功能。 + +我们来看个示例,该示例主要实现以下几个功能: +- 使用一个中间件识别路由函数的执行结果,并获取路由函数返回的错误码信息,将该错误码信息作为返回结果的一部分返回给调用端。 +- 如果产生了错误,会记录产生该错误的当前用户信息到日志中。该功能使用`Detail`扩展参数实现。 + +## 业务错误码 + +### 错误码定义 + +```go +type BizCode struct { + User User + // ... +} + +type User struct { + Id int + Name string + // ... +} +``` + +### 错误码使用 + +扩展错误码大多数场景下需要使用 `gcode.WithCode` 方法: + +```go +// WithCode creates and returns a new error code based on given Code. +// The code and message is from given `code`, but the detail if from given `detail`. +func WithCode(code Code, detail interface{}) Code +``` + +因此上面我们的自定义扩展可以这么使用: + +```go +code := gcode.WithCode(gcode.CodeNotFound, BizCode{ + User: User{ + Id: 1, + Name: "John", + }, +}) +fmt.Println(code) +``` + +即在错误码中我们可以根据业务场景注入一些自定义的错误码扩展数据,以方便上层获取错误码后做进一步处理。 + +## 改写中间件 + +我们将上面自定义的错误码应用到请求返回中间件中,顶层业务逻辑也可以获取到错误码对应的详情再进一步做相关的业务处理。中间件返回的数据结构也可以自定义重写,例如其中返回的 `code` 字段也不一定是整形数值,可以完全自定义。 + +```go +func ResponseHandler(r *ghttp.Request) { + r.Middleware.Next() + // There's custom buffer content, it then exits current handler. + if r.Response.BufferLength() > 0 { + return + } + var ( + err = r.GetError() + res = r.GetHandlerResponse() + code = gerror.Code(err) + ) + if code == gcode.CodeNil && err != nil { + code = gcode.CodeInternalError + } + if detail, ok := code.Detail().(BizCode); ok { + g.Log().Errorf(r.Context(), `error caused by user "%+v"`, detail.User) + } + r.Response.WriteJson(ghttp.DefaultHandlerResponse{ + Code: gcode.CodeOK.Code(), + Message: gcode.CodeOK.Message(), + Data: res, + }) +} +``` +:::tip +在框架 `Server` 默认的日志中会自动打印 `Detail` 数据。 +::: diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" new file mode 100644 index 00000000000..65777ee3c99 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243.md" @@ -0,0 +1,71 @@ +--- +slug: '/docs/core/gerror-code-interface' +title: '错误处理-错误码接口' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcode,错误处理,错误码接口,接口定义,高扩展性,错误码组件,自定义错误码,接口化设计] +description: 'GoFrame框架中用于错误处理的错误码接口,基本描述了错误码组件gcode的接口化设计和高扩展性。通过实现Code接口,开发者可以自定义错误码。框架提供了默认实现,开发者也可以根据需求自行扩展,实现自己的错误码逻辑。' +--- + +## 基本介绍 + +我们前面讲到的主要是`gerror`组件的使用,该组件主要用于错误管理。在后续章节中,我们主要讲解`gcode`组件的使用,该组件主要用于扩展`gerror`组件的能力,提供错误码的特性。`gcode`错误码组件使用了接口化设计,以提供高扩展性。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/errors/gcode" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/errors/gcode](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gcode) + + +## 接口定义 + +```go +// Code is universal error code interface definition. +type Code interface { + // Code returns the integer number of current error code. + Code() int + + // Message returns the brief message for current error code. + Message() string + + // Detail returns the detailed information of current error code, + // which is mainly designed as an extension field for error code. + Detail() interface{} +} +``` + +## 默认实现 + +框架提供了默认实现 `gcode.Code` 的结构体,开发者可以直接`gcode`组件的`New/WithCode`方法创建错误码: + +- 格式: + + ```go + // New creates and returns an error code. + // Note that it returns an interface object of Code. + func New(code int, message string, detail interface{}) Code + ``` + +- 示例: + + ```go + func ExampleNew() { + c := gcode.New(1, "custom error", "detailed description") + fmt.Println(c.Code()) + fmt.Println(c.Message()) + fmt.Println(c.Detail()) + + // Output: + // 1 + // custom error + // detailed description + } + ``` + + +如果开发者觉得框架默认实现 `gcode.Code` 的结构体不满足需求,可以自行定义,只需实现 `gcode.Code` 即可。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..0fee47ca995 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\346\226\271\346\263\225.md" @@ -0,0 +1,176 @@ +--- +slug: '/docs/core/gerror-code-example' +title: '错误处理-错误码使用' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误码,堆栈信息,NewCode,WrapCode,error,gerror,gcode] +description: '在GoFrame框架中使用NewCode和WrapCode方法进行错误处理,通过这些方法可以创建和包裹带有自定义错误码和堆栈信息的error对象。文档提供了基于GoFrame框架的示例代码,帮助开发者更好地理解错误码在程序中的应用。' +--- + +## 创建带错误码的 `error` + +### `NewCode/NewCodef` + +- 说明:功能同 `New/Newf` 方法,用于创建一个自定义错误信息的 `error` 对象,并包含堆栈信息,并增加错误码对象的输入。 +- 格式: + + ```go + // NewCode creates and returns an error that has error code and given text. + NewCode(code gcode.Code, text ...string) error + + // NewCodef returns an error that has error code and formats as the given format and args. + NewCodef(code gcode.Code, format string, args ...interface{}) error + ``` + +- 实例: + + ```go + func ExampleNewCode() { + err := gerror.NewCode(gcode.New(10000, "", nil), "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err)) + + // Output: + // My Error + // 10000 + } + + func ExampleNewCodef() { + err := gerror.NewCodef(gcode.New(10000, "", nil), "It's %s", "My Error") + fmt.Println(err.Error()) + fmt.Println(gerror.Code(err).Code()) + + // Output: + // It's My Error + // 10000 + } + ``` + + +### `WrapCode/WrapCodef` + +- 说明:功能同 `Wrap/Wrapf` 方法,用于包裹其他错误 `error` 对象,构造成多级的错误信息,包含堆栈信息,并增加错误码参数的输入。 +- 格式: + + ```go + // WrapCode wraps error with code and text. + // It returns nil if given err is nil. + WrapCode(code gcode.Code, err error, text ...string) error + + // WrapCodef wraps error with code and format specifier. + // It returns nil if given `err` is nil. + WrapCodef(code gcode.Code, err error, format string, args ...interface{}) error + ``` + +- 示例: + + ```go + func ExampleWrapCode() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCode(gcode.New(10000, "", nil), err1, "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // Custom Error: permission denied + // 10000 + } + + func ExampleWrapCodef() { + err1 := errors.New("permission denied") + err2 := gerror.WrapCodef(gcode.New(10000, "", nil), err1, "It's %s", "Custom Error") + fmt.Println(err2.Error()) + fmt.Println(gerror.Code(err2).Code()) + + // Output: + // It's Custom Error: permission denied + // 10000 + } + ``` + + +### `NewCodeSkip/NewCodeSkipf` + +- 说明:功能同 `NewCode/NewCodef`,用于创建一个带错误码的 `error` 对象,但忽略部分堆栈信息(按照当前调用方法位置往上忽略)。 +- 格式: + + ```go + // NewCodeSkip creates and returns an error which has error code and is formatted from given text. + // The parameter `skip` specifies the stack callers skipped amount. + func NewCodeSkip(code, skip int, text string) error + + // NewCodeSkipf returns an error that has error code and formats as the given format and args. + // The parameter `skip` specifies the stack callers skipped amount. + func NewCodeSkipf(code, skip int, format string, args ...interface{}) error + ``` + +### `WrapCodeSkip/WrapCodeSkipf` + +- 说明:功能同 `WrapCodeSkip/WrapCodeSkipf`,用于包裹一个带错误码的 `error` 对象,但忽略部分堆栈信息(按照当前调用方法位置往上忽略)。 +- 格式: + + ```go + // WrapCodeSkip wraps error with code and text. + // It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + func WrapCodeSkip(code gcode.Code, skip int, err error, text ...string) error + + // WrapCodeSkipf wraps error with code and text that is formatted with given format and args. + // It returns nil if given err is nil. + // The parameter `skip` specifies the stack callers skipped amount. + func WrapCodeSkipf(code gcode.Code, skip int, err error, format string, args ...interface{}) error + ``` + + +## `Code` + +- 说明:获取错误对象中的错误码。该方法会递归获取,如果当前错误对象不存在错误码时,该方法会追溯查找其上一级错误对象的错误码,以此类推。当给定的 `error` 参数不带有错误码信息时,该方法返回预定义的错误码 `gcode.CodeNil`。 +- 格式: + + ```go + // Code returns the error code of `current error`. + // It returns `CodeNil` if it has no error code neither it does not implement interface Code. + func Code(err error) gcode.Code + ``` + + +- 示例: + + ```go + func ExampleCode() { + err1 := gerror.NewCode(gcode.CodeInternalError, "permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.Code(err1)) + fmt.Println(gerror.Code(err2)) + + // Output: + // 50:Internal Error + // 50:Internal Error + } + ``` + +## `HasCode` + +- 说明:该方法判断给定的错误对象中是否包含指定的错误码。与`Code`方法类似,该方法也会追溯查找上一级错误的错误码。 +- 格式: + + ```go + // HasCode checks and reports whether `err` has `code` in its chaining errors. + func HasCode(err error, code gcode.Code) bool + ``` + + +- 示例: + + ```go + func ExampleHasCode() { + err1 := gerror.NewCode(gcode.CodeInternalError, "permission denied") + err2 := gerror.Wrap(err1, "operation failed") + fmt.Println(gerror.HasCode(err1, gcode.CodeOK)) + fmt.Println(gerror.HasCode(err2, gcode.CodeInternalError)) + + // Output: + // false + // true + } + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..836d2e4e223 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247/\351\224\231\350\257\257\345\244\204\347\220\206-\351\224\231\350\257\257\347\240\201\347\211\271\346\200\247.md" @@ -0,0 +1,13 @@ +--- +slug: '/docs/core/gerror-code' +title: '错误处理-错误码特性' +hide_title: true +keywords: [GoFrame,GoFrame框架,错误码特性,错误处理,Golang,编程,Web开发,软件框架,开发文档,技术指南] +description: '在GoFrame框架中进行错误处理,特别是错误码特性的使用。通过详细的示例和指南,帮助开发者轻松识别和处理错误,提高代码的可靠性和可维护性。这是了解GoFrame框架错误处理机制的重要资源。' +--- + + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..524b4d77a39 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\240\270\345\277\203\347\273\204\344\273\266/\351\224\231\350\257\257\345\244\204\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/core/gerror' +title: '错误处理' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,gerror,堆栈信息,开发者,接口文档,统一错误处理,Go,编程] +description: 'GoFrame框架提供了强大的错误处理能力,通过gerror组件实现,所有组件在返回错误时带有堆栈信息,方便开发者快速定位问题。使用该框架能有效提升编程效率及程序稳定性。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了强大、丰富、统一的错误处理能力,由 `gerror` 组件实现。该组件也是框架统一的错误处理组件,框架所有组件如果存在错误返回时,均带有堆栈信息,方便开发者快速定位问题。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/errors/gerror" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror](https://pkg.go.dev/github.com/gogf/gf/v2/errors/gerror) + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" new file mode 100644 index 00000000000..3f3dfbd6fb6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Context\345\205\261\344\272\253\345\217\230\351\207\217.md" @@ -0,0 +1,167 @@ +--- +slug: '/docs/design/context-variable' +title: 'Context共享变量' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,Context,上下文变量,共享变量,异步IO,HTTP请求,Session管理,用户信息,中间件] +description: '通过使用GoFrame框架中的Context传递和管理上下文流程中的共享变量。在Go网络应用中,尤其是HTTP/RPC服务中,Context是传递异步IO控制和上下文变量的关键工具。通过结构化对象的共享,本文展示了如何在请求过程中实现变量传递,确保请求链路中变量的一致性和灵活性。' +--- + +`Context` 指的是标准库的 `context.Context`,是一个接口对象,常用于 **异步 `IO` 控制** 以及 **上下文流程变量的传递**。本文将要介绍的,是如何使用 `Context` 传递流程间共享变量。 + +在 `Go` 的执行流程中,特别是 `HTTP/RPC` 执行流程中,不存在”全局变量”获取请求参数的方式,只有将上下文 `Context` 变量传递到后续流程的方法中,而 `Context` 上下文变量即包含了所有需要传递的共享变量。并且该 `Context` 中的共享变量应当是事先约定的,并且往往存储为对象指针形式。 + +通过 `Context` 上下文共享变量非常简单,以下我们通过一个项目中的示例来展示如何在实战化项目中传递和使用通用的共享变量。 + +## 一、结构定义 + +上下文对象中往往存储一些需要共享的变量,这些变量通常使用结构化的对象来存储,以方便维护。例如,我们在 `model` 定义一个上下文中的共享变量: + +```go +const ( + // 上下文变量存储键名,前后端系统共享 + ContextKey = "ContextKey" +) + +// 请求上下文结构 +type Context struct { + Session *ghttp.Session // 当前Session管理对象 + User *ContextUser // 上下文用户信息 + Data g.Map // 自定KV变量,业务模块根据需要设置,不固定 +} + +// 请求上下文中的用户信息 +type ContextUser struct { + Id uint // 用户ID + Passport string // 用户账号 + Nickname string // 用户名称 + Avatar string // 用户头像 +} +``` + +其中: + +1. `model.ContextKey` 常量表示存储在 `context.Context` 上下文变量中的键名,该键名用于从传递的 `context.Context` 变量中存储/获取业务自定义的共享变量。 +2. `model.Context` 结构体中的 `Session` 表示当前请求的 `Session` 对象,在 `GoFrame` 框架中每个 `HTTP` 请求对象中都会有一个空的 `Session` 对象,该对象采用了懒初始化设计,只有在真正执行读写操作时才会初始化。 +3. `model.Context` 结构体中的 `User` 表示当前登录的用户基本信息,只有在用户登录后才有数据,否则是 `nil`。 +4. `model.Context` 结构体中的 `Data` 属性用于存储自定义的 `KV` 变量,因此一般来说开发者无需再往 `context.Context` 上下文变量中增加自定义的键值对,而是直接使用 `model.` `Context` 对象的这个 `Data` 属性即可。详见后续介绍。 + +## 二、逻辑封装 + +由于该上下文对象也是和业务逻辑相关的,因此我们需要通过 `service` 对象将上下文变量封装起来以方便其他模块使用。 + +```go +// 上下文管理服务 +var Context = new(contextService) + +type contextService struct{} + +// 初始化上下文对象指针到上下文对象中,以便后续的请求流程中可以修改。 +func (s *contextService) Init(r *ghttp.Request, customCtx *model.Context) { + r.SetCtxVar(model.ContextKey, customCtx) +} + +// 获得上下文变量,如果没有设置,那么返回nil +func (s *contextService) Get(ctx context.Context) *model.Context { + value := ctx.Value(model.ContextKey) + if value == nil { + return nil + } + if localCtx, ok := value.(*model.Context); ok { + return localCtx + } + return nil +} + +// 将上下文信息设置到上下文请求中,注意是完整覆盖 +func (s *contextService) SetUser(ctx context.Context, ctxUser *model.ContextUser) { + s.Get(ctx).User = ctxUser +} +``` + +## 三、上下文变量注入 + +上下文的变量必须在请求一开始便注入到请求流程中,以便于其他方法调用。在 `HTTP` 请求中我们可以使用 `GoFrame` 的中间件来实现。在 `GRPC` 请求中我们也可以使用拦截器来实现。在 `service` 层的 `middleware` 管理对象中,我们可以这样来定义: + +```go +// 自定义上下文对象 +func (s *middlewareService) Ctx(r *ghttp.Request) { + // 初始化,务必最开始执行 + customCtx := &model.Context{ + Session: r.Session, + Data: make(g.Map), + } + service.Context.Init(r, customCtx) + if userEntity := Session.GetUser(r.Context()); userEntity != nil { + customCtx.User = &model.ContextUser{ + Id: userEntity.Id, + Passport: userEntity.Passport, + Nickname: userEntity.Nickname, + Avatar: userEntity.Avatar, + } + } + // 将自定义的上下文对象传递到模板变量中使用 + r.Assigns(g.Map{ + "Context": customCtx, + }) + // 执行下一步请求逻辑 + r.Middleware.Next() +} +``` + +该中间件初始化了用户执行流程共享的对象,并且存储到 `context.Context` 变量中的对象是指针类型 `*model.Context`。这样任何一个地方获取到这个指针,既可以获取到里面的数据,也能够直接修改里面的数据。 + +其中,如果 `Session` 中存在用户登录后的存储信息,那么也会将需要共享的用户基本信息写入到 `*model.Context` 中。 + +## 四、上下文变量使用 + +### 方法定义 + +约定俗成的,方法定义的第一个输入参数往往预留给 `context.Context` 类型参数使用,以便接受上下文变量,特别是 `service` 层的方法。例如: + +```go +// 执行用户登录 +func (s *userService) Login(ctx context.Context, loginReq *define.UserServiceLoginReq) error { + ... +} + +// 查询内容列表 +func (s *contentService) GetList(ctx context.Context, r *define.ContentServiceGetListReq) (*define.ContentServiceGetListRes, error) { + ... +} + +// 创建回复内容 +func (s *replyService) Create(ctx context.Context, r *define.ReplyServiceCreateReq) error { + ... +} + +``` + +此外,约定俗成的,方法的最后一个返回参数往往是 `error` 类型。如果您确定此方法内部永不会产生 `error`,那么可以忽略。 + +### `Context` 对象获取 + +通过 `service` 中封装的以下方法,将 `context.Context` 上下文变量传递进去即可。 `context.Context` 上下文变量在 `GoFrame` 框架的 `HTTP` 请求中可以通过 `r.Context()` 方法获取,在 `GRPC` 请求中,编译生成的 `pb` 文件中执行方法的第一个参数即固定是 `context.Context`。 + +```go +service.Context.Get(ctx) +``` + +### 自定义 `Key-Value` + +通过以下方式设置/获取自定义的 `key-value` 键值对。 + +```go +// 设置自定义键值对 +service.Context.Get(ctx).Data[key] = value + +... + +// 获取自定义键值对 +service.Context.Get(ctx).Data[key] +``` + +## 五、注意事项 + +1. 上下文变量只传递必须的链路参数数据,不要什么参数都往里面塞。特别是一些方法参数传参的数据,别往里面塞,而应当显示传递方法参数。 +2. 上下文变量仅用作运行时临时使用,不可持久化存储长期使用。例如将 `ctx` 序列化后存储到数据库,并再下一次请求中读取出来反序列化使用是错误做法。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..6e2c089cd7f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/Golang\346\236\232\344\270\276\345\200\274\347\256\241\347\220\206.md" @@ -0,0 +1,45 @@ +--- +slug: '/docs/design/enums' +title: 'Golang枚举值管理' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame框架,Go语言,枚举实现,Kubernetes枚举,Go常量,跨服务枚举,OpenAPI枚举,enumeration in Go,API开发,SDK生成] +description: '在Go语言中实现枚举值,虽然Go语言本身不支持enum定义,但是可以通过const来模拟枚举类型。这种方法在Kubernetes项目中广泛使用。此外,本文还探讨了如何在跨服务调用和前后端协作中高效维护枚举值的问题,并提供了使用OpenAPI标准协议与相关工具来实现的方法。' +--- + +## Go实现枚举值 + +`Go` 语言并没有提供 `enum` 的定义,我们可以使用 `const` 来模拟枚举类型,这也是 `Go` 语言中约定俗成的方式。 + +例如,在 `Kubernetes` 项目中,有大量的以常量形式定义的"枚举值": + +```go +// PodPhase is a label for the condition of a pod at the current time. +type PodPhase string + +// These are the valid statuses of pods. +const ( + // PodPending means the pod has been accepted by the system, but one or more of the containers + // has not been started. This includes time before being bound to a node, as well as time spent + // pulling images onto the host. + PodPending PodPhase = "Pending" + // PodRunning means the pod has been bound to a node and all of the containers have been started. + // At least one container is still running or is in the process of being restarted. + PodRunning PodPhase = "Running" + // PodSucceeded means that all containers in the pod have voluntarily terminated + // with a container exit code of 0, and the system is not going to restart any of these containers. + PodSucceeded PodPhase = "Succeeded" + // PodFailed means that all containers in the pod have terminated, and at least one container has + // terminated in a failure (exited with a non-zero exit code or was stopped by the system). + PodFailed PodPhase = "Failed" + // PodUnknown means that for some reason the state of the pod could not be obtained, typically due + // to an error in communicating with the host of the pod. + PodUnknown PodPhase = "Unknown" +) +``` + +## 如何跨服务高效维护枚举值 + +如果只是项目内部使用枚举值比较简单,定义完了内部使用即可,但涉及到跨服务之间调用,或者前后端协作时,效率就比较低了。当服务需要给外部调用者展示接口能力时,往往需要生成 `API` 接口文档(或者接口定义文件,例如 `proto`),往往也需要根据接口文档(文件)生成调用的客户端 `SDK`。 + +如果是接口定义文件,例如 `proto`,往往可以直接查看源码来解决这个问题,问题不大。我们这里主要讨论的是接口文档维护枚举值的问题,特别是前后端协作时通过 `OpenAPI` 标准协议来维护枚举值的问题。这里我们提供了专门的工具来维护这些枚举值,具体请参考章节: [枚举维护-gen enums](../开发工具/代码生成-gen/枚举维护-gen%20enums.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..62e34091ea9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\223\276\350\267\257\350\267\237\350\270\252\350\256\276\350\256\241.md" @@ -0,0 +1,50 @@ +--- +slug: '/docs/design/tracing' +title: '全链路跟踪设计' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,全链路跟踪,OpenTelemetry,可观测性,OTEL,Golang,链路传递,日志支持,框架,第三方组件] +description: '链路跟踪是可观测性的重要指标之一,GoFrame框架在支持OpenTelemetry标准方面具有优势。本文探讨项目实践中的链路跟踪痛点,以及如何通过GoFrame框架实现全链路跟踪,确保标准落地和日志支持。该框架的统一组件和规范检测工具有助于提升业务项目的稳定性。' +--- + +可观测性是很重要的,目前已经有比较不错的 `OpenTelemetry` 标准,各个第三方组件以及厂商都按照这套标准库来暴露和对接观测数据将会更加简便。我们本次聊到的,是链路跟踪,它是可观测性中很重要的一项指标。 +:::tip +`GoFrame` 框架具有很高的前瞻性,在 `OpenTelemetry` 标准草稿阶段便开始持续关注,并在发布 `OTEL` 标准发布了 `alpha` 版本时,框架便开始支持。目前 `OTEL` 的 `Golang` 实现已经稳定。 `GoFrame` 是目前众多"框架"中对 `OTEL` 标准及实现支持得最好的框架,使用 `GoFrame` 框架即隐式自带链路跟踪特性。并且可观测性也是框架未来发展的重点特性。 +::: +## 一、项目实践的痛点 + +在项目实践中,实现链路跟踪往往会遇到以下常见的痛点。 + +### 1、组件没有严格执行标准 + +虽然已经有 `OTEL` 标准,但是第三方组件没有严格执行。例如,在日志和 `ORM` 组件中,没有严格约束传递 `ctx` 上下文变量。 + +### 2、第三方组件杂乱无章 + +业务项目拼凑使用的第三方组件众多,有的组件没有实现链路跟踪支持,更别提 `OTEL` 标准。例如,命令管理、配置管理、缓存管理、数据校验、定时任务等等常用组件,表象就是没有提供 `ctx` 上下文变量传递。当使用到的组件不支持链路传递时,链路信息便丢失。 + +### 3、业务逻辑易丢失链路 + +业务项目中,链路跟踪没有检测手段,也就是写着写着,链路跟踪不小心就弄丢了。例如在链路传递中新建 `ctx` 或者传递 `nil` 的 `ctx`。当战略设计在战术实施时遭遇到了猪队友,遇到问题需要定位时就GG。特别是在 `toB` 业务中,遇到工单限时会很抓狂。 + +## 二、框架全链路跟踪 + +### 1、统一框架 + +由于 `GoFrame` 是一款工程完备的基础框架,提供了项目所需的通用核心基础组件,因此可以非常方便统一地的基础组件实现链路跟踪标准。 + +### 2、标准落地 + +在框架的核心组件中均增加了对 `ctx` 上下文变量的支持,并严格执行 `OTEL` 标准,保障标准的顺利落地。 + +### 3、日志支持 + +在工程实践中,日志对于链路跟踪来说是非常重要的组件,在大部分的业务场景中,我们需要通过链路跟踪和日志内容来排查和定位具体问题。`GoFrame` 框架的日志组件也支持并且严格执行 `OTEL` 标准,因此只要使用框架的日志组件,那么也会自动打印链路相关信息。 + +### 4、规范检测工具 + +框架通过开发工具提供工程规范检测功能,可以自动检测出业务项目中的链路丢失问题,进一步推进 `OTEL` 标准落地,保障项目质量。 + +### 5、链路传递支持 + +链路的传递也需要统一组件。目前比较常见的协议是 `HTTP/GRPC` 协议,因此框架也提供了 `HTTP Client/Server` 以及 `GRPC Client/Server` 组件来保障链路的传递。并且为提高介入性和易用性,屏蔽复杂的底层功能细节,这种链路传递是底层 **隐式实现** 的,使用者对此事完全无感知的。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..2ff3a3e6187 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\205\250\351\224\231\350\257\257\345\240\206\346\240\210\350\256\276\350\256\241.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/design/error-stack' +title: '全错误堆栈设计' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误处理,错误堆栈,统一错误处理,日志打桩,错误组件,堆栈信息,第三方组件,错误返回] +description: 'GoFrame框架在错误处理方面的策略和设计,通过统一的错误处理方案和错误组件,解决了项目中常见的错误堆栈缺失和日志冗余等问题。GoFrame框架的全组件支持错误堆栈功能,降低了错误排查和维护的成本,提高了项目的稳定性和易用性。' +--- + +## 一、项目错误处理痛点 + +我们在业务项目中,经常会遇到以下痛点。 + +### 1、缺少统一错误处理方案,代码中随处可见的日志打桩 + +为了方便接口出错时定位问题,代码中随处可见的日志打桩,并将其看做是一件理所当然的事,然而毫无章法的庞大日志量除了提高维护的工作量,却通常难以达到它该有的目的。 + +### 2、请求执行报错后缺少错误堆栈,难以快速定位问题 + +如下,当底层出现 `error` 级别的错误时,在顶层看到的就一个错误信息,请问如何排查? + +![](/markdown/d0a2ecfa83e3b3107e38a519bacf0f17.png) + +一张真实的现网案例排查截图 + +### 3、第三方组件执行返回的错误,本身不带有堆栈信息 + +不仅仅是第三方组件,连标准库所有方法返回的 `error` 都不带有堆栈,这对业务层统一错误处理造成了很大的挑战。几乎所有业务层代码调用返回的错误,都需要自行使用类似于 `Wrap` 方法再包裹一层,以便于业务层自己可以实现错误堆栈返回。这样的维护成本比较大,几乎只能靠 `CodeReview` 来人肉保障,一不小心可能会漏掉 `Wrap` 处理。 + +### 4、错误组件多样,自身项目往往还想当然再封装一层 + +错误处理的第三方组件也比较多,如何选择?甚至业务项目往往也想自己再封装一层,进一步提高错误处理组件的维护成本。 + +## 二、框架全错误堆栈设计 + +### 1、统一错误组件 + +`GoFrame` 框架提供了业内功能最强大的错误处理组件,并且该组件也是框架内部广泛使用的错误组件,降低业务团队的选择成本。 + +### 2、统一错误处理方案 + +`GoFrame` 框架提供了强大的工程设计规范,其中包含了必要的统一的错误处理方案。按照统一框架的工程设计,一些通用性的痛点已通过组件、工具的方式得以解决,使得业务团队能够将精力聚焦于业务本身,开发工作将会事半功倍。 + +在统一的错误处理方案下,项目中所有的方法调用将会以 `error` 返回值作为执行成功与否的依据。如果 `error` 不为 `nil` 时,及时返回,并将其层层往上传递,在最顶层统一做错误处理。并且,在框架的关键组件已经提供了默认的错误处理逻辑。 + +![](/markdown/0237be84e57c222bd476dad67a883253.png) + +### 3、全组件支持堆栈错误 + +🔥 `GoFrame` 框架 **所有基础组件** 的 `error` 返回对象均带有错误堆栈!🔥 + +🔥 `GoFrame` 框架 **所有基础组件** 的 `error` 返回对象均带有错误堆栈!🔥 + +🔥 `GoFrame` 框架 **所有基础组件** 的 `error` 返回对象均带有错误堆栈!🔥 + +🔥重要的事情说三遍!🔥 + +这是一件很难做到的事情,因为框架提供的组件几乎能够覆盖了大部分业务项目的所有需求,但是框架确实做到了。虽然框架在这块投入的成本比较大(单独一个版本来实现了这个特性),但却是前期一次性投入、长期收益的事情。这也就意味着,如果业务项目在使用统一的 `GoFrame` 基础框架下,错误处理将会更加简便,错误堆栈丢失的风险得到了极大的降低,项目将会更加稳健、易于快速排错。 + +### 4、关键组件支持错误堆栈打印 + +在框架的关键组件中,提供了对错误堆栈打印的 **默认处理**,以提高易用性,简化使用者负担。这些关键的组件是程序的入口,如 `HTTP/GRPC Server`、 `Command` 命令行。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" new file mode 100644 index 00000000000..5730e1069d5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\345\267\245\347\250\213\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" @@ -0,0 +1,73 @@ +--- +slug: '/docs/design/project-dao-improvement' +title: 'DAO-工程痛点及改进' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,DAO改进,数据库操作,数据模型,业务模型,ORM抽象,工程化设计,数据权限,自动化代码生成] +description: '在使用GoFrame框架进行项目开发中的DAO工程痛点及其相应的改进策略。通过自动化数据模型管理、数据与业务模型隔离、自动化DAO代码管理以及引入DO数据转换模型,提升数据操作管理效率。强调数据操作权限收口的重要性,以降低项目维护成本和数据操作风险。' +--- + +## 一、基本介绍 + +我们都知道,开发业务项目离不开数据库操作组件的使用,数据库是绝大部分业务项目的核心,这也是"CRUD工程师"戏称的由来。业务项目在进行数据库操作时,比较 `low` 的方式是直接 `Open/New` 然后各种 `SQL` 字符串操作一把梭。稍微正常一点的项目可能会考虑物色或者自己封装一层ORM抽象,提高CRUD效率,降低数据操作风险。再严谨一点的项目可能会在项目工程管理上考虑下,再进一步增加 `DAO/DTO/VO` 之类的设计模式和概念。 + +信息时代,数据是十分重要的,数据操作是十分敏感的,因此 `GoFrame` 框架对于数据操作管理的工程化思路是严谨的。我们提供了必要的ORM抽象、必要的DAO封装、必要的工程化规范约束。同时,我们并不会采用八股文设计,而是依旧保持简便、灵活、易扩展的工程设计思路。 + +## 二、工程化痛点 + +在一些严谨的业务项目中,已经有了 `ORM&DAO` 抽象、并且项目已有初步工程化设计的前提,依旧存在以下常见的痛点。 + +### 1、数据集合与代码结构不同步 + +当在代码中手动维护数据集合对应的数据结构时,这个坑就算挖好了,就看后面谁掉进去了。 + +### 2、数据模型与业务模型模糊不清 + +混淆了数据模型与业务的职责,并将数据模型与业务逻辑、接口定义形成了耦合,耦合越大,相关方法、接口维护的成本越高,对数据模型改动产生的风险也越大。常见痛点: + +- 在 `model` 中既有业务相关的数据结构(业务模型),又有数据集合对应的数据结构(数据模型),如何高效隔离和管理呢? +- 在业务流程中,将数据模型当做业务流程的 **输入参数** 使用。甚至,将数据模型直接嵌入到API接口输入数据结构定义中(总是想法设法将数据模型用到业务模型中)。 + +### 3、DAO层沉淀太多的业务逻辑封装 + +您是否有一种感觉,只要是数据操作,都有理由往 `DAO` 里面丢? + +### 4、将数据模型作为ORM/DAO操作的参数 + +您有可能认为这么做是对的,但是不明确的数据结构都意味着成本和风险。任何的操作,都应当能够明确输入/输出,否则都是不严谨的,对待数据的操作尤其应当严谨。 + +### 5、数据操作权限开放,项目任何地方都可以随意调用 + +数据的操作权限应当尽可能收口,如果过于开放那么当业务及人员复杂之后,项目的维护成本和风险都会曲线增加。 + +### 6、从顶层业务到底层数据集合操作,通篇使用同一个数据结构 + +常见的问题,是设计一个大的结构体,例如数据模型(更有甚者,将属性全部设计为指针或者 `interface{}`),从顶层业务到底层数据操作层层透传,方法逻辑根据是否输入特定的属性来判断传参。会造成什么问题呢: + +- 方法参数定义不明确,不明确的定义意味着会增加额外的协作成本,额外的不明确风险 +- 同一数据结构与多数方法形成耦合,数据结构的任一变动将会影响所有相关方法 +- 相关方法无法充分复用(特别是 `service` 层的方法) + +## 三、工程化改进 + +### 1、自动化的数据模型管理 + +通过工具自动化实现数据集合到数据模型的代码生成,避免人工维护造成的不同步。 + +### 2、数据与业务模型的隔离 + +将数据模型通过 `entity` 包维护,业务模型通过 `model` 包维护,通过不同的包职责来做区分。数据模型由工具化维护,业务模型根据业务场景由开发者定义和维护。 + +### 3、自动化的DAO代码管理 + +通过工具自动化实现数据集合的 `DAO` 代码生成,提高生产效率。 `DAO` 中只有自动化生成的基础数据操作,不封装特定业务逻辑。 + +### 4、DO数据转换模型的引入 + +避免数据模型直接被当做 `DAO` 参数使用,避免踩坑。 `GoFrame` 框架引入了 `DO` 包,在 `DAO` 操作时自动化转换为数据集合对应的数据结构,提高 `DAO` 操作的效率,降低操作风险。 + +### 5、对数据操作权限进行收口 + +由于数据操作已经由 `DAO` 包进行了统一维护,可以将 `DAO` 包迁移到了对应 `logic` 层业务模块的 `internal` 目录下,实现项目工程下仅仅只有 `logic` 层的对应业务逻辑代码可以通过 `DAO` 执行数据操作。 + +这是一个较严格的使用限制,由开发者根据需要选择性使用,框架默认的项目模板以及工具没有对数据操作权限收口。可通过框架开发工具配置来实现生成 `dao` 代码到不同的 `logic` 业务模块位置下。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" new file mode 100644 index 00000000000..ced50c8ae25 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO-\347\273\204\344\273\266\347\227\233\347\202\271\345\217\212\346\224\271\350\277\233.md" @@ -0,0 +1,136 @@ +--- +slug: '/docs/design/project-dao-pain' +title: 'DAO-组件痛点及改进' +sidebar_position: 0 +hide_title: true +keywords: [DAO,GoFrame,数据访问对象,ORM,GRPC,数据库优化,代码封装,自动映射,SQL日志,代码生成] +description: '在GoFrame框架中使用DAO设计的优点,以及现有ORM组件使用中的痛点和解决方案。通过DAO设计,极大地提高了开发和维护效率,降低了代码的耦合度,还增加了可观测性支持。文章详细描述了如何克服使用ORM时的常见问题,如字段映射、硬编码和数据结构不一致等。此外,改进方案设计还包括对DAO对象的封装和SQL日志功能的支持。' +--- + +关于 `DAO` 数据访问对象设计其实是关于 `GoFrame` 框架工程化实践中比较重要一块设计。 + +`DAO` 设计结合 `GoFrame` 的 `ORM` 组件性能和易用性都很强,可以极大提高开发和维护效率。看完本章节内容之后,小伙伴们应该能够理解并体会到使用 `DAO` 数据库访问对象设计的优点。 + +:::info +我每年都会来回重新审视这篇文章,看看是否可以删除一些地方。可是每次都倍感失望,因为这篇文章对当今现状仍旧适用。并且今年,我还新增了内容。 +::: + +## 一、现有 `ORM` 使用示例 + +### 1、需要定义模型 + +![](/markdown/77daf5d299eabade856d950ab3161f94.png)用户基础表(仅作演示,真实的表有数十个字段) + +![](/markdown/f4e8c70ee25ec329f2b64bb3a53ff503.png)医生信息表(仅作演示,真实的表有上百个字段) + +### 2、 `GRPC` 接口实现示例 + +一个简单的 `GRPC` 查询信息接口。 + +![](/markdown/b45b3af0a0bdc9ad30f739e31d0039ae.png)一个简单的 `GRPC` 数据查询接口 + +## 二、现有痛点描述 + +### 1、必须要定义 `tag` 关联表结构与 `struct` 属性,无法做到自动映射 + +表字段与实体对象属性名称之间原本就有一定的关联规则,没有必要定义和维护大量的 `tag` 定义。 + +![](/markdown/f1bb2d203d4fe4f2c44bbc7e14b7832a.png) + +大量非必要的 `tag` 定义,用于指定数据表字段到实体对象属性映射 + +### 2、不支持通过返回对象指定需要查询的字段 + +无法通过返回的对象数据结构指定查询字段,要么只能 `SELECT *` ,要么只能通过额外的方法手动录入查询字段,效率很低下。 + +![](/markdown/70e01c869632543b846b04a1696e9737.png) + +常见的 `SELECT *` 操作,无法根据接口对象指定查询字段 + +### 3、无法对输入对象属性名称进行自动字段过滤 + +定义了输入与输出数据结构,输出的数据结构已经包含我们需要查询的字段名称。开发者输入定义的返回对象,期望在查询的时候仅查询我需要的字段名称,多余的属性则不会执行查询,自动过滤掉。 + +### 4、需要创建中间查询结果对象执行赋值转换 + +查询结果不支持 `struct` 智能转换,需要额外定义一个中间 `model` 模型,再通过其他工具进行复制,效率低。 + +![](/markdown/05bf7722da09a27e7ca82bf6e0f89271.png) + +存在中间临时的模型对象,用于承接查询结果及返回结构对象赋值转换 + +### 5、需要提前初始化返回对象,不管有无查询到数据 + +这种方式不仅不优雅,对性能也有影响,还对 `GC` 不太友好。期望查询到数据时再自动创建返回对象,没有查询到数据时什么都不要做。 + +![](/markdown/239f4b75b4b77e85bca523371a7dd1b4.png) + +需要预先初始化返回对象,不管有无查询到数据 + +### 6、项目通篇使用底层裸 `DB` 对象操作,没有对象封装操作 + +大部分的 `Golang` 初学者似乎都倾向于使用一个全局的 `DB` 对象,在查询的时候通过 `DB` 对象生成特定表的 `Model` 对象再执行 `CRUD` 操作,这是一种面向过程的使用方式。这种方式并没有代码分层的设计可言, **使得数据操作和业务逻辑高度耦合**。 + +![](/markdown/d73fdaa5b76b831db0a2c1069742c218.png) + +原始数据库对象操作方式,没有 `DAO` 封装 + +### 7、随处可见的字符串硬编码,如表名和字段的硬编码 + +举个例子, `userId` 这个字段假如一不小心写成了 `UserId` 或者 `userid`,测试的时候如果没有完全覆盖到,在一定的条件下才触发查询操作,是不是会造成新的一场事故呢? + +![](/markdown/46d8aae38995327c6ce26832d21f628b.png) + +大量的字符串硬编码 + +### 8、底层ORM引起太多的指针属性定义 + +指针属性对象为业务逻辑处理埋下隐患,开发者在代码逻辑中需要在指针与属性之间来回切换,特别是一些基础类型往往需要通过重新取值的方式传递参数。如果输入参数是 `interface{}` 类型,那么更容易引起 `BUG`。 + +![](/markdown/620c8a9a4a47de0243748d588aa0bb51.png) + +`BUG` 示例,指针属性使用不当,引起地址比较逻辑错误。 + +![](/markdown/daa08ad1e9102f4ac964a8176a80e061.png) + +同时也影响了业务模型结构体定义设计,对开发者造成了错误习惯引导(上层业务模型的指针属性往往是为了迎合底层数据表实体对象,方便数据传递)。 + +![](/markdown/bba716ea66e03727826ae6401ce01b2d.png) + +值得注意一个常见错误,就是将底层数据实体模型当做顶层业务模型使用。特别是在底层数据实体对象使用指针属性的场景下,该问题十分明显。 + +### 9、可观测性的支持:Tracing、Metrics、Logging + +数据库ORM作为业务项目最关键核心的组件,可观测性的支持至关重要。 + +### 10、数据集合与代码数据实体结构不一致 + +当通过人工维护数据实体结构时,数据集合与代码数据实体结构往往会出现不一致的风险,开发和维护成本高。 + +## 三、改进方案设计 + +1、查询结果对象无需特殊标签定义,全自动关联映射 + +2、支持根据指定对象自动识别查询字段,而不是全部 `SELECT *` + +3、支持根据指定对象自动过滤不存在的字段内容 + +4、使用 `DAO` 对象封装代码设计,通过对象方式操作数据表 + +5、 `DAO` 对象将关联的表名及字段名进行封装,避免字符串硬编码 + +6、无需提前定义实体对象接受返回结果,无需创建中间实体对象用于接口返回对象的赋值转换 + +7、查询结果对象无需提前初始化,查询到数据时才会自动创建 + +8、内置支持 `OpenTelemetry` 标准,实现可观测性,极大提高维护效率、降低成本 + +9、支持 `SQL` 日志输出能力,支持开关功能 + +10、数据模型、数据操作、业务逻辑解耦,支持 `Dao` 及 `Model` 代码工具化自动生成,保证数据集合与代码数据结构一致,提高开发效率,便于规范落地 + +11、等等。 + +![](/markdown/90537635dc3b5623060fa9edfc49948a.png) + +采用 `DAO` 设计改进后的代码示例 diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..83f44a89097 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241/DAO\345\260\201\350\243\205\350\256\276\350\256\241.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/design/project-dao' +title: 'DAO封装设计' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,DAO设计,数据访问对象,设计模式,软件架构,代码封装,数据库操作,开发最佳实践,系统设计] +description: '在GoFrame框架中进行DAO封装设计,详细探讨了数据访问对象设计的各种模式和方法,帮助开发者实现高效的代码封装和数据库操作,提升软件架构的模块化和可维护性。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..75ad1aaf8a3 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\344\273\243\347\240\201\345\210\206\345\261\202\350\256\276\350\256\241.md" @@ -0,0 +1,89 @@ +--- +slug: '/docs/design/project-layer' +title: '代码分层设计' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame框架,代码分层设计,MVC设计模式,三层架构,业务逻辑层,数据访问层,表示层UI,模型定义层,软件架构,解耦] +description: 'GoFrame框架的代码分层设计,包括MVC设计模式和三层架构设计。MVC设计模式适合需要服务端渲染页面的业务场景,而三层架构设计强调高内聚低耦合的思想,通过将业务逻辑层与数据访问层进行分离,提高项目的维护性和灵活性。' +--- +:::tip +代码分层的意义在于将程序逻辑进一步解耦,将层级之间的数据流和依赖关系设计为单向链路,使得系统架构更加灵活易扩展。 +::: +## 一、基本介绍 + +`GoFrame` 作为一款工程化完备的基础开发框架,有其独特的框架设计理念,这一章节我们来介绍一下她的代码分层设计。对于服务端业务代码的分层设计模式中,我们比较常见的是 `MVC` 设计模式和三层架构设计模式( `3-Tier Architecture`)。 + +## 二、 `MVC` 设计模式 + +我们先来回顾一下经典的 `MVC` 设计模式。 + +![MVC设计模式](/markdown/d90094b0f7ec2edb2220ffc0204a1c2d.png) + +图1\. MVC设计模式 + +### 简要介绍 + +`M` 代表模型( `Model`),表示业务规则封装。在 `MVC` 的三个部件中,模型拥有最多的处理任务。被模型返回的数据是中立的,模型与数据格式无关,这样一个模型能为多个视图提供数据,由于应用于模型的代码只需写一次就可以被多个视图重用,所以减少了代码的重复性。 + +`V` 代表视图( `View`),用户看到并与之交互的界面。比如由 `HTML` 元素组成的网页界面,或者软件的客户端界面。 `MVC` 的好处之一在于它能为应用程序处理很多不同的视图。在视图中其实没有真正的处理发生,它只是作为一种输出数据并允许用户操纵的方式。 + +`C` 代表控制器( `Controller`),接受用户的输入并调用模型和视图去完成用户的需求,控制器本身不输出任何东西和做任何处理。它只是接收请求并决定调用哪个模型构件去处理请求,然后再确定用哪个视图来显示返回的数据。 + +这种设计模式比较简单,比较合适于需要服务端渲染页面的业务场景,对于 `SEO` 来说也比较友好。但随着 `MVVM` 开发模式的兴起,以及前端技术的快速发展,特别是一些前端开发框架如 `Vue`、 `React`、 `Angular` 之类的项目出现,服务端的 `MVC` 设计模式使用场景变得越来越少。 + +### 痛点描述 + +针对于业务逻辑并不是特别复杂的业务场景项目, `MVC` 还能游刃有余,但随着业务逻辑变得庞大复杂, `MVC` 设计模式的项目维护成本上升的问题变得越来越明显。特别是随着互联网项目微服务架构的发展, `MVC` 设计模式在大部分的互联网项目开发中变得越来越鸡肋。究其原因,主要的几点: + +- 视图展示与数据操作方式的进一步剥离,特别是移动端的发展,前端 `MVVM` 框架的发展,我们大多数场景下已不再需要服务端渲染 `View`。 +- `MVC` 的代码分层设计模式其实粒度较粗: + - `Model` 层级的代码既维护着数据,也封装着业务逻辑,随着业务逻辑变得越来越复杂,这一层功能逻辑会变得越来越臃肿不易维护。 + - 对于团队管理来讲, `Controller` 和 `Model` 的职责边界比较模糊,对于开发人员写好代码的要求会比较高。概念看似简单,但是却难以适应工业化的软件生产。 + +## 三、 `3-Tier Architecture` + +`GoFrame` 框架推荐的代码分层设计模式为三层架构设计( `3-Tier Architecture`),但在具体的实现中,对这种结构设计模式进行了一定的改进。三层架构设计能够很好地体现出软件设计"高内聚,低耦合"的设计思想。 + +![](/markdown/8b93ee429f05737e03dfc58bdfe04905.png) + +图2\. 三层架构设计模式 + +传统的三层架构设计如上图,将项目代码划分了三层,每一层有其独自的职责边界。但在大多数的场景中,我们常看到的是以下的分层结构,将数据结构模型再进一步地抽离了出来统一维护。 + +![三层架构设计模式](/markdown/fe9aea78ab05dc6db3b34d021a05ee76.png) + +图3\. 常见三层架构设计模型 + +### 表示层 \- `UI` + +`User Interface` 位于三层构架的最上层,与用户直接接触,主要是 `B/S` 中的 `WEB` 页面,也可以是 `API` 接口。表示层的主要功能是实现系统数据的传入与输出,在此过程中不需要借助逻辑判断操作就可以将数据传送到 `BLL` 系统中进行数据处理,处理后会将处理结果反馈到表示层中。换句话说,表示层就是实现用户界面/ `API` 接口功能,将用户的需求传达和反馈,并用 `BLL` 或者是 `Model` 进行调试,保证用户体验。 + +### 业务逻辑层 - `BLL` + +`Business Logic Layer` 的功能是对具体问题进行逻辑判断与执行操作,接收到表现层 `UI` 的用户指令后,会连接数据访问层 `DAL`,业务逻辑层在三层构架中位于表示层与数据层中间位置,同时也是表示层与数据层的桥梁,实现三层之间的数据连接和指令传达,可以对接收数据进行逻辑处理,实现数据的增删改查等功能,并将处理结果反馈到表示层 `UI` 中,实现软件功能。 + +### 数据访问层 - `DAL` + +`Data Access Layer` 是数据库的主要操控系统,实现数据的增删改查等操作,并将操作结果反馈到业务逻辑层 `BLL`。在实际运行的过程中,数据访问层没有逻辑判断能力,为了实现代码编写的严谨性,提高代码阅读程度,一般软件开发人员会在该层中实现通用数据能力进行封装(例如通过 `ORM` 组件)来保证数据访问层 `DAL` 数据处理功能。 + +### 模型定义层 - `Model` + +模型定义也常用 `Entity` 实体对象来表示,主要用于数据库表的映射对象,在信息系统软件实际开发的过程中,要建立对象实例,将关系数据库表采用对象实体化的方式表现出来,辅助软件开发中对各个系统功能的控制与操作执行。建立实体类库,进而实现各个结构层的参数传输,提高代码的阅读性。从本质上看,实体类库主要服务于表示层、业务逻辑层以及数据访问层,在三层之间进行数据参数传输,强化数据表示的简约性。 + +需要注意区分的是,这里的 `Model` 和 `MVC` 设计模式中的 `Model` 虽然都是一个名字但是差别巨大,职责完全不同。 + +### 三层架构设计与 `MVC` + +由于 `MVC` 也是三层结构,因此有的同学也会将 `MVC` 笼统地归纳于三层架构设计中,从字面意义上来讲似乎也没什么问题。不过两者还是存在一定的区别。 + +![](/markdown/2c6cfc087687cca60b1f4d23b78705c4.png) + +图4\. 三层架构设计与MVC + +可以看到,在三层架构设计中, `UI` 表示层即相当于 `MVC` 的 `View` 和 `Controller` 层,原本在 `MVC` 中这两层的逻辑应当是比较"轻量"的,因此被合并为一层进行统一管理也可以理解。比较重要的一点是,原本 `MVC` 中的 `Model` 被拆分为了 `BLL` 和 `DAL`,即将业务逻辑与数据访问进行分离,将原本臃肿的 `Model` 进行了进一步的解耦,有利于项目的更好维护。 +:::tip +软件架构的演变过程,特别是互联网软件架构的演变过程,本质也就是将业务逻辑不断解耦的过程。 +::: +## 四、更进一步了解 + +代码分层思想是最基础的工程设计入门,我们需要将分层思想进行落地,具体请参考: [工程目录设计🔥](工程目录设计.md) diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..0547d451ce2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241.md" @@ -0,0 +1,27 @@ +--- +slug: '/docs/design/project' +title: '工程开发设计(🔥重点🔥)' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,工程开发,设计实践,微服务项目,模块化设计,工具库,开箱即用,项目踩坑,开发团队] +description: 'GoFrame框架是一个模块化设计的工具库,适用于开发业务项目特别是微服务项目。本文档提供了工程开发设计和实践,通过丰富的经验帮助开发者解决常见难题,同时强调理解设计背景和思路对推动团队开发的价值。' +--- + +## 基本介绍 + +由于模块化的设计方式, `GoFrame` 框架既可以当做工具库使用,也可以直接用于完整的开发业务项目。 + +考虑到大家使用框架大部分场景下是用于开发业务项目(微服务项目),因此框架也提供了工程开发的一些设计和实践,便于大家直接开箱即用。 + +## 注意事项 + +工程开发设计是很复杂的"学问",不同的团队有不同的设计风格,甚至某些场景下还和团队 `leader` 喜好有较大关系。 + +框架官网提供的工程开发设计,是基于编写该章节的作者经验,以及通过 `Go` 实践的项目踩坑而来。她能帮助各位解决常见的工程开发难题,但我们不认为她能符合所有的开发团队的喜好。 + +查阅这里所有的设计章节,理解每一块设计本身的背景和思路,比直接使用框架组件和工具劳动更有价值。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..2082ce0e132 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\267\245\347\250\213\347\233\256\345\275\225\350\256\276\350\256\241.md" @@ -0,0 +1,189 @@ +--- +slug: '/docs/design/project-structure' +title: '工程目录设计🔥' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,工程目录设计,代码分层,框架设计,业务逻辑,目录结构,项目开发,三层架构,数据访问,模块化] +description: 'GoFrame框架中工程目录设计的方法,基于三层架构模型并结合现代工程实践进行改进,为复杂业务项目提供通用且灵活的目录结构设计。目录组件包含api、internal、dao和logic等模块,支持多种业务场景,同时鼓励开发者灵活增减目录,用于业务实现中各类具体应用。' +--- + +工程目录设计是代码分层设计的进一步落地,建议您先仔细阅读: [代码分层设计](代码分层设计.md) +:::tip +这是 `GoFrame` 框架针对 **业务项目** 的目录设计,主体的思想来源于三层架构,但在具体实现中,对其进行了一定的改进和细化使其更符合工程实践和时代进步。 +::: +## 一、工程目录结构 + +`GoFrame` 业务项目基本目录结构如下(以 `Single Repo` 为例): + +```text +/ +├── api +├── hack +├── internal +│ ├── cmd +│ ├── consts +│ ├── controller +│ ├── dao +│ ├── logic +│ ├── model +│ | ├── do +│ │ └── entity +│ └── service +├── manifest +├── resource +├── utility +├── go.mod +└── main.go +``` +:::info +**🔥 重要提示 🔥**:框架的工程目录采用了 **通用化的设计**,以满足不同复杂程度业务项目的需求,但实际项目中可以根据项目需要 **适当增减默认的目录**。例如,没有 `i18n/template/protobuf` 需求的场景, **直接删除对应目录即可**。又例如,非常简单的业务项目(如验证/演示项目),不考虑使用严谨的 `dao/logic/model` 目录及特性,那么 **直接删除对应目录即可**,可以在 `controller` 中直接实现业务逻辑。 **一切都可以由开发者灵活选择组装**! +::: +| 目录/文件名称 | 说明 | 描述 | +| ---------------- | -------- | ----------------------------------------------------------------------------------------- | +| `api` | 对外接口 | 对外提供服务的输入/输出数据结构定义。考虑到版本管理需要,往往以 `api/xxx/v1...` 存在。 | +| `hack` | 工具脚本 | 存放项目开发工具、脚本等内容。例如, `CLI` 工具的配置,各种 `shell/bat` 脚本等文件。 | +| `internal` | 内部逻辑 | 业务逻辑存放目录。通过 `Golang internal` 特性对外部隐藏可见性。 | +| `  - cmd` | 入口指令 | 命令行管理目录。可以管理维护多个命令行。 | +| `  - consts` | 常量定义 | 项目所有常量定义。 | +| `  - controller` | 接口处理 | 接收/解析用户输入参数的入口/接口层。 | +| `  - dao` | 数据访问 | 数据访问对象,这是一层抽象对象,用于和底层数据库交互,仅包含最基础的 `CRUD` 方法 | +| `  - logic` | 业务封装 | 业务逻辑封装管理,特定的业务逻辑实现和封装。往往是项目中最复杂的部分。 | +| `  - model` | 结构模型 | 数据结构管理模块,管理数据实体对象,以及输入与输出数据结构定义。 | +| `    - do` | 领域对象 | 用于 `dao` 数据操作中业务模型与实例模型转换,由工具维护,用户不能修改。 | +| `    - entity` | 数据模型 | 数据模型是模型与数据集合的一对一关系,由工具维护,用户不能修改。 | +| `  - service` | 业务接口 | 用于业务模块解耦的接口定义层。具体的接口实现在 `logic` 中进行注入。 | +| `manifest` | 交付清单 | 包含程序编译、部署、运行、配置的文件。常见内容如下: | +| `  - config` | 配置管理 | 配置文件存放目录。 | +| `  - docker` | 镜像文件 | `Docker` 镜像相关依赖文件,脚本文件等等。 | +| `  - deploy` | 部署文件 | 部署相关的文件。默认提供了 `Kubernetes` 集群化部署的 `Yaml` 模板,通过 `kustomize` 管理。 | +| `  - protobuf` | 协议文件 | `GRPC` 协议时使用的 `protobuf` 协议定义文件,协议文件编译后生成 `go` 文件到 `api` 目录。 | +| `resource` | 静态资源 | 静态资源文件。这些文件往往可以通过 资源打包/镜像编译 的形式注入到发布文件中。 | +| `go.mod` | 依赖管理 | 使用 `Go Module` 包管理的依赖描述文件。 | +| `main.go` | 入口文件 | 程序入口文件。 | + +### 对外接口 + +对外接口包含两部分:接口定义( `api`)+接口实现( `controller`)。 + +服务接口的职责类似于三层架构设计中的 `UI` 表示层,负责接收并响应客户端的输入与输出,包括对输入参数的过滤、转换、校验,对输出数据结构的维护,并调用 `service` 实现业务逻辑处理。 + +#### 接口定义 \- `api` + +`api` 包用于与客户端约定的数据结构输入输出定义,往往与具体的业务场景强绑定。 + +#### 接口实现 - `controller` + +`controller` 用于接收 `api` 的输入,可以直接在 `controller` 中实现业务逻辑,或者调用一个或多个 `service` 包实现业务逻辑,将执行结果封装为约定的 `api` 输出数据结构。 + +### 业务实现 + +业务实现包含两部分:业务接口( `service`)+业务封装( `logic`)。 + +业务实现的职责类似于三层架构设计中的 `BLL` 业务逻辑层,负责具体业务逻辑的实现以及封装。 +:::info +在后续的章节介绍中,我们会将业务实现统一称作 `service`,大家注意它其实包含两部分即可。 +::: +#### 业务接口 - `service` + +`service` 包用于解耦业务模块之间的调用。业务模块之间往往不会直接调用对应的业务模块资源来实现业务逻辑,而是通过调用 `service` 接口。 `service` 层只有接口定义,具体的接口实现注入在各个业务模块中。 + +#### 业务封装 - `logic` + +`logic` 包负责具体业务逻辑的实现以及封装。项目中各个层级代码不会直接调用 `logic` 层的业务模块,而是通过 `service` 接口层来调用。 + +### 结构模型 + +`model` 包的职责类似于三层架构中的 `Model` 模型定义层。模型定义代码层中仅包含全局的、公共的数据结构定义,供项目中所有的业务模块共同引用。。 + + +#### 数据模型 - `entity` + +与数据集合绑定的程序数据结构定义,通常和数据表一一对应。 + +#### 业务模型 - `model` + +与业务相关的通用数据结构定义,其中包含大部分的方法输入输出定义。 + +### 数据访问 - `dao` + +`dao` 包的职责类似于三层架构中的 `DAL` 数据访问层,数据访问层负责所有的数据访问收口。 + +![](/markdown/1e1bb98778823124dc5bf35c57e8f4cb.png) + +三层架构设计与框架代码分层映射关系 + +## 二、请求分层流转 + +![](/markdown/df7dd9a93cb541a8ca126b5b051002ab.png) + +### cmd + +`cmd` 层负责引导程序启动,显著的工作是初始化逻辑、注册路由对象、启动 `server` 监听、阻塞运行程序直至 `server` 退出。 + +### api + +上层 `server` 服务接收客户端请求,转换为 `api` 中定义的 `Req` 接收对象、执行请求参数到 `Req` 对象属性的类型转换、执行 `Req` 对象中绑定的基础校验并转交 `Req` 请求对象给 `controller` 层。 + +### controller + +`controller` 层负责接收 `Req` 请求对象后做一些业务逻辑校验,可以直接在 `controller` 中实现业务逻辑,或者调用一个或多个 `service` 实现业务逻辑,将执行结果封装为约定的 `Res` 数据结构对象返回。 + +### model + +`model` 层中管理了所有公共的业务模型。 + +### service + +`service` 是接口层,用于解耦业务模块, `service` 没有具体的业务逻辑实现,具体的业务实现是依靠 `logic` 层注入的。 + +### logic + +`logic` 层的业务逻辑需要通过调用 `dao` 来实现数据的操作,调用 `dao` 时需要传递 `do` 数据结构对象,用于传递查询条件、输入数据。 `dao` 执行完毕后通过 `Entity` 数据模型将数据结果返回给 `service` 层。 + +### dao + +`dao` 层通过框架的 `ORM` 抽象层组件与底层真实的数据库交互。 + +## 三、常见问题解答 + +### 框架是否支持常见的 `MVC` 开发模式 + +**当然!** + +作为一款模块化设计的基础开发框架, `GoFrame` 不会局限代码设计模式,并且框架提供了非常强大的模板引擎核心组件,可快速用于 `MVC` 模式中常见的模板渲染开发。相比较 `MVC` 开发模式,在复杂业务场景中,我们更推荐使大家用三层架构设计模式。 + +### 当 `api` 与 `model` 层存在重复数据结构时如何维护 + +在 `api` 中定义的数据结构是 **对外使用的**,与具体的业务场景绑定(如具体的页面交互逻辑、单一的接口功能),数据结构是由上层展示层前置决定的; `model` 中定义的数据结构是 **服务内部公共使用的**,并且 `model` 中的数据结构可以随意在内部修改而并不会影响对外 `api` 接口的兼容性。 + +**注意 `model` 中的数据结构不应该直接暴露给外部使用**,并且在框架的工程设计中刻意将 `model` 目录放到了 `internal` 目录下。也不应该在 `api` 层中对 `model` 中的数据结构进行 **别名类型定义** 供外部访问。一旦将 `model` 中的数据结构应用到了 `api` 层中,内部 `model` 数据结构的修改会直接影响到 `api` 接口的兼容性。 + +**如果两者出现重复的数据结构(甚至常量、枚举类型),建议将数据结构定义到 `api` 层中**。服务内部逻辑可以直接访问 `api` 层的数据结构。 `model` 层的数据结构也可以直接引用 `api` 层的数据结构,但是反之则不行。 + +我们来看一个示例,便于更好地理解: + +![](/markdown/4d95fb64e06bb72a5456fb684704b891.png)![](/markdown/36794a54e02c2be6c0edbcf07bb8821a.png) + +### 如何清晰界定和管理 `service` 和 `controller` 的分层职责 + +`controller` 层处理 `Req/Res` 外部接口请求。负责接收、校验请求参数,可以直接在 `controller` 中实现业务处理逻辑,或者调用 **一个或多个** `service` 来实现业务逻辑处理,将执行结果封装为约定的 `api` 输出数据结构返回。 `service` 层处理 `Input/Output` 内部方法调用。负责内部 **可复用** 的业务逻辑封装,封装的方法粒度往往比较细。 + +**通常来讲,开发接口时只需要编写 `controller` 层中的接口实现业务逻辑即可,当存在重复代码逻辑时,再从各个 `controller` 接口实现逻辑中抽象沉淀到 `service` 层**。如果从 `controller` 层直接透传 `Req` 对象给 `service`,同时 `service` 直接返回 `Res` 数据结构对象,该方法也就与外部接口耦合,仅面向外部接口服务,难以复用,这样会增加技术债务成本。 + +### 如何清晰界定和管理 `service` 和 `dao` 的分层职责 + +这是一个很经典的问题。 + +**痛点:** + +常见的,开发者把 **数据相关的业务逻辑实现** 封装到了 `dao` 代码层中,而 `service` 代码层只是简单的 `dao` 调用,这么做的话会使得原本负责维护数据的 `dao` 层代码越来越繁重,反而业务逻辑 `service` 层代码显得比较轻。开发者存在困惑,我写的业务逻辑代码到底应该放到 `dao` 还是 `service` 中? + +业务逻辑其实绝大部分时候都是对数据的 `CRUD` 处理,这样做会使得几乎所有的业务逻辑会逐步沉淀在 `dao` 层中,业务逻辑的改变其实会频繁对 `dao` 层的代码产生修改。例如:数据查询需求,在初期的时候可能只是简单的逻辑,目前代码放到 `dao` 好像也没问题,但是查询需求增加或变化变得复杂之后,那么必定会继续维护修改原有的 `dao` 代码,同时 `service` 代码也可能同时做更新。原本仅限于 `service` 层的业务逻辑代码职责与 `dao` 层代码职责模糊不清、耦合较重,原本只需要修改 `service` 代码的需求变成了同时修改 `service` + `dao`,使得项目中后期的开发维护成本大大增加。 + +**建议:** + +我们的建议。 `dao` 层的代码应该尽量保证通用性,并且大部分场景下不需要增加额外方法,只需要使用一些通用的链式操作方法拼凑即可满足。业务逻辑、包括看似只是简单的数据操作的逻辑都应当封装到 `service` 中。 `service` 中包含多个业务模块,每个模块独自管理自己的 `dao` 对象。理想情况下, `service` 与 `service` 之间通过相互调用方法来实现数据通信,而不是随意去调用其他 `service` 模块的 `dao` 对象。 + +### 为什么要使用 `internal` 目录包含业务代码 + +`internal` 目录是 `Golang` 语言专有的特性,防止同级目录外的其他目录引用其下面的内容。业务项目中存在该目录的目的,是避免若项目中存在多个子项目(特别是大仓管理模式时),多个项目之间无限制随意访问,造成难以避免的多项目不同包之间耦合。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" new file mode 100644 index 00000000000..737291b89ca --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\345\276\256\346\234\215\345\212\241\345\244\247\344\273\223\347\256\241\347\220\206\346\250\241\345\274\217.md" @@ -0,0 +1,147 @@ +--- +slug: '/docs/design/project-mono-repo' +title: '微服务大仓管理模式' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,微服务,单仓管理,代码开发,权限管理,服务协作,大仓管理,代码仓库,容器化支持,框架指令] +description: 'GoFrame框架对微服务-单仓管理模式的支持,详细描述如何在这种模式下进行代码开发和服务协作。讨论了单仓管理的优缺点,以及如何通过划分仓库职责、管理代码可见性、统一镜像仓库等方法优化微服务协作。同时,提供了相关的框架指令,帮助开发者更高效地管理和部署微服务项目。' +--- + +本文旨在介绍 `GoFrame` 框架对微服务-单仓管理( `mono-repo`)模式的支持,指导开发者如何在微服务-单仓管理( `mono-repo`)模式下进行代码开发和分工协作。 + +## 一、前置阅读 + +在开始本章节之前,建议先了解一下单体仓库( `monolith`)、微服务-多仓管理( `multi-repo`)、 +微服务-单仓管理( `mono-repo`)的基本概念以及各自的优缺点: +[单体仓库与多仓库都有哪些优势劣势,微服务选择哪种方案比较好?](https://johng.cn/management/monorepo-vs-multirepo) + +代码仓库的管理约束并不属于框架职责的一部分, `GoFrame` 框架的脚手架本身也支持两种仓库项目初始化的命令 \- 单仓库( `mono-repo`)、多仓库( `monolith/multi-repo`),以满足不同团队的需求。具体选择哪种代码仓库管理模式由开发团队根据自身需求、场景、习惯来自行选择。 +:::tip +为简化和清晰微服务-单仓管理( `mono-repo`)的描述,后续我们统一以 **大仓管理** 来指代微服务-单仓管理( `mono-repo`)模式。 +::: +## 二、大仓管理 + +### 1、仓库职责范围的划分 + +通过前置阅读的文章大家也知道,这个世界上没有银弹,大仓有优点也有缺点,其中最明显的缺点就是 **权限的管控** 以及 **仓库的膨胀**。为了更好地管理代码仓库,避免这两点缺陷带来更高的成本,我们建议尽可能减小大仓中的微服务规模。至于仓库中需要维护哪些微服务,需要根据服务之间的协作频率来决定。 + +#### 1)当团队内部的协作频率高于团队间协作频率时 + +- 典型的场景是针对 **非微服务化架构的产品**,可以将服务管理的权限职责按照各个业务团队进行划分。这样团队内部可以将分散的若干服务通过统一的代码仓库维护起来,充分利用大仓管理的优势,提高团队内部的开发和维护效率。 +- 另外一种场景是业务的微服务数量本身不多(例如 `50` 个以内),这个时候也可以合并成一个大仓进行管理。需要注意,大仓管理的服务数量并不是由组织架构中的人员数量来决定的。 + +#### 2)当多个团队之间的多个服务协作频率非常高时 + +当业务的微服务数量比较多,并且各个服务之间的交互协作比较频繁,那么可以考虑将这些服务合并到大仓中进行管理,可以极大提高协作效率。这种情况大部分出现在微服务在同一产品线、跨团队但不跨中心或部门的情况下。但由于涉及跨多个团队的协作,这对人员的组织架构管理有一定要求,需要由一定权限的管理者来推动。 +:::tip +微服务的管理并不仅是代码的组织管理,更是人员组织架构的管理。 +::: +### 2、大仓下的微服务间如何协作 + +#### 1)代码可见性的管理 + +服务与服务之间能够暴露的仅仅是接口,也就是 `API`。各个服务内部的逻辑应当对外不可见。在 `Golang` 里面有很好的 `internal` 特性,正好可以满足可见性管理的要求。如下图大仓代码示例,在 `app` 目录下管理了若干的服务,每一个服务暴露了自身的 `api` 目录,供其他服务直接引用(提高服务间协作效率),但内部的业务逻辑包含在了 `internal` 目录下,对其他服务不可见(也就无法引用)。 + +![](/markdown/f9028ffb7bc51e7496f1d55b79091f73.png) + +#### 2)服务间接口的调用 + +协议文件单独维护到各自服务目录下,如果涉及到协议文件编译,那么编译的文件也存放到自身的服务目录下。调用端不需要单独再对目标服务的协议文件重新编译管理。以 `HTTP API` 的接口定义为例,调用端可以直接引用目标端服务的 `API` 接口定义:(以下截图中 `khaos-shark` 为调用端, `khaos-oss` 为服务端) + +![](/markdown/b0035d25d52202b3f1b38d18980bf3ff.png) + +针对于微服务间的 `RPC` 接口调用也是同样的道理:(以下截图中 `user-api` 为调用端, `user-rpc` 为服务端) + +![](/markdown/f02efd1e4c03b3cb111cb7b9015290ee.png) + +#### 3)兼容性的严格要求 + +通过以上介绍可以发现,通过大仓的代码管理,使得大仓中所有服务的版本保持一致,每当依赖的服务 `API` 更新时,调用端服务(使用的 `SDK`)也将自动得到更新。这就要求仓库中所有服务的接口设计, **必须严格保证兼容性**,否则接口间调用将会出现问题:轻者调用端服务编译失败需要调整代码,重者编译成功但运行时报错影响业务。此外,公共引用的大仓基础组件也会受到兼容性的影响。 + +保证兼容性代码设计的几个要点: + +- **不随意删减接口参数,修改参数名称、参数类型、参数校验逻辑。** +- **当接口必须要进行非兼容更新时,应当使用接口版本号来管理(如 `v1, v2, v3...`)。** +- **公共组件尽量使用稳定成熟的外部组件,如果是必要的自定义组件,需要保证对外暴露方法的兼容性。** _举个例子:一些很基础的功能,比如说 `json.Marshal&Unmarshal`,有的人封装了一些库/函数,但是后面的人可能都不知道这个库,也不太信任这个函数,就会又重新写一个...久而久之这些库/函数却又无人维护。_ + +### 3、大仓下的微服务容器化支持 + +#### 1)镜像仓库的统一管理 + +分散的镜像仓库将会降低服务容器化的管理维护效率。为便于统一服务容器化管理,我们建议大仓下的服务使用统一的镜像仓库。镜像仓库的地址统一维护到各个服务下的工具配置文件中: + +![](/markdown/424878f3a64d0cca7899c6fd13a8b9c7.png) + +#### 2)统一编译、提交指令 + +框架提供了常用的指令来实现程序的编译、镜像的编译、镜像的提交。 + +- `make build` + +编译程序,生成二进制文件。 + +更多介绍请参考文档: [交叉编译-build](../../开发工具/交叉编译-build.md) + +- `make image` + +编译程序并编译镜像,生成 `Docker` 镜像。 + +通过 `make image TAG=xxx` 可以指定编译生成的镜像标签名称。 + +更多介绍请参考文档: [镜像编译-docker](../../开发工具/镜像编译-docker.md) + +- `make image.push` + +编译程序、编译镜像并推送镜像到已配置好的镜像仓库。 + +通过 `make image.push TAG=xxx` 可以指定编译生成的镜像标签名称。 + +#### 3)统一部署、调试指令 + +框架提供了常用的指令来实现 `Kubernetes` 集群的容器化部署,以及一体化编译部署的开发指令。 + +- `make deploy TAG=xxx` + +部署当前服务到本地 `kubeconfig` 已连接的 `kubernetes` 集群中,其中的 `TAG` 用于指定 `deploy` 目录下的 `overlays` 目录。管理部署 `yaml` 文件使用的是行业内常用的 `kustomize` 工具,具体介绍文档请参考: [https://kubernetes.io/zh-cn/docs/tasks/manage-kubernetes-objects/kustomization/](https://kubernetes.io/zh-cn/docs/tasks/manage-kubernetes-objects/kustomization/) + +![](/markdown/353b86069be6e3cb8834aab4aad32e84.png) + +- `make image.push deploy TAG=xxx` + +该命令是开发调试指令,用于一条指令 编译二进制文件、编译并推送 `Docker` 镜像、部署 `Kubernetes` 应用并重启应用。 + +### 4、大仓下的框架其他指令 + +框架针对于项目工程管理提供了丰富的工具指令支持,这些指令往往需要再特定的服务目录下执行,例如 `./app/服务名称` + +#### 1) `make cli` + +用于升级本地的框架 `CLI` 到最新稳定版本。 + +#### 2) `make up` + +用于升级本地的框架到最新社区稳定版本。 + +更多介绍请参考文档: [框架升级-up](../../开发工具/框架升级-up.md) + +#### 3) `make dao` + +用于生成 `DAO/Entity/DO` 代码文件。 + +更多介绍请参考文档: [数据规范-gen dao](../../开发工具/代码生成-gen/数据规范-gen%20dao.md) + +#### 4) `make service` + +用于解析 `logic` 目录并自动生成内调用接口。该指令在 `Goland IDE` 下往往使用自动化的 `Watcher` 文件变动来自动生成,具体请参考官方文档。 + +更多介绍请参考文档: [模块规范-gen service](../../开发工具/代码生成-gen/模块规范-gen%20service.md) + +#### 5) `make enums` + +用于解析指定代码目录(默认为 `api` 目录)并自动生成 `enums` 加载代码。 + +更多介绍请参考文档: [枚举维护-gen enums](../../开发工具/代码生成-gen/枚举维护-gen%20enums.md) + +#### 6)更多指令 + +更多的指令支持请参考框架官网工具介绍章节: [开发工具](../../开发工具/开发工具.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" new file mode 100644 index 00000000000..4fa63859c1d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\346\225\260\346\215\256\346\250\241\345\236\213\344\270\216\344\270\232\345\212\241\346\250\241\345\236\213.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/design/project-models' +title: '数据模型与业务模型' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,数据模型,业务模型,接口输入输出模型,业务输入输出模型,ORM组件,DAO操作,CLI工具,实体模型,模块调用] +description: 'GoFrame框架中的数据模型与业务模型,包括MySQL、Redis等数据库的数据模型,以及用于接口交互的输入/输出模型。详细讲述业务输入/输出模型的定义和使用,特别介绍特殊的业务模型DO与ORM组件集成,简化DAO数据访问操作。' +--- + +在之前的章节 [代码分层设计](代码分层设计.md) 有提及到" **模型**"的概念。 + +这一章节,我们集中介绍一下在 `GoFrame` 中关于模型的定义以及对应的管理。 + +## 一、数据模型 + +**数据模型** 又叫做 **实体模型**,主要是来自于底层持久化数据库的数据结构,例如: `MySQL`、 `Redis`、 `MongoDB`、 `Kafka` 等等。这部分数据结构是由第三方系统维护的,可以通过工具对其集合数据结构进行识别,并自动生化成对应的程序数据模型代码。这部分数据模型的代码位于 `/internal/model/entity` 目录下。开发者不需要手动在程序中维护数据模型,在 `GoFrame` 框架规范中,数据模型统一使用 `CLI` 工具统一维护,代码自动生成。 + +![](/markdown/0126798ec8cb70d798fc2260afb2f9a9.png) + +数据模型示例 + +## 二、业务模型 + +业务模型主要包含两类: **接口输入/输出模型** 与 **业务输入/输出模型**。 + +### 接口输入/输出模型 + +接口输入/输出模型用于系统/服务间的接口交互,定义在 `api` 接口层中,供工程项目所有的层级调用,例如 `controller, logic, model` 中均可以调用 `api` 层的输入输出模型,但是 `api` 层仅用于与外部服务的接口交互,该模型中不能调用或者引用内部的模型如 `model` 模型。在 `GoFrame` 框架规范中,这部分输出输出模型名称以 `XxxReq` 和 `XxxRes` 格式命名。 + +![](/markdown/8c037d2e08ddf5b8cb758cefd706b5ea.png) + +接口输入模型示例 + +### 业务输入/输出模型 + +**业务输入/输出模型** 用于服务 **内部** 模块/组件之间的方法调用交互,特别是 `controller->service` 或者 `service->service` 之间的调用。这部分模型定义在 `model` 模型层中。在 `GoFrame` 框架规范中,这部分输入输出模型名称通常以 `XxxInput` 和 `XxxOutput` 格式命名。 + +![](/markdown/b23a0dab9a4f4ac63c51c166248d9779.png) + +业务输入模型与业务输出模型示例 + +### 特殊的业务模型 `DO` + +在 `GoFrame` 有一类特殊的业务模型 `DO`,介于业务模型与数据模型之间,主要用于结合框架强大的 `ORM` 组件大大简便 `DAO` 数据访问操作。 + +![](/markdown/d08e7808de1c18c306e05157dd899992.png) + +DO主要用于DAO数据访问操作 + +## 三、其他模型 + +还有内部私有的模型,用于模块内部调用,例如在 `logic` 各个业务模块内部定义的模型,用于内部逻辑,不对外公开。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..b85486700cc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\345\267\245\347\250\213\345\274\200\345\217\221\350\256\276\350\256\241/\347\273\223\346\236\204\345\214\226\347\274\226\347\250\213\350\256\276\350\256\241.md" @@ -0,0 +1,69 @@ +--- +slug: '/docs/design/project-struct-parameter' +title: '结构化编程设计' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame框架,结构化编程,参数管理,代码设计,接口优化,自动化文档生成,代码维护,参数校验,生产力提升,错误机制] +description: '在GoFrame框架下如何通过结构化编程改善代码设计,详细分析了在controller和service层出现的非结构化问题,并提供了使用结构体管理参数的优点和示例。通过结构化管理接口输入和输出,不仅简化了参数接收、校验和转换的过程,还提高了生产力,降低了维护成本,实现了更便捷的接口文档生成和更规范的错误处理机制。' +--- + +## 一、基本介绍 + +结构化编程,简单来理解,就是通过定义结构体传递和返回参数。 + +我们建议在必要的场景下使用结构化定义来管理输入/输出,尤其是在 `controller` 和 `service` 两层的代码设计中。 + +### 1、 `controller` 非结构化痛点 + +![](/markdown/e76d9687eb2d840494ce98a644e05d95.png) + +- 难以确定接口输入/输出数据结构,大多数的场景是在代码中硬编码参数接收名称,易把名称写错造成不可预料的问题 +- 接口参数往往只定义一个 `HttpRequest/HttpContext` 对象指针,执行结果直接写入到对象,难以确定接口是否成功/失败 +- 参数接收、校验、转换处理工作繁琐 +- 接口文档生成以及维护极为困难 + +### 2、 `service` 非结构化痛点 + +![](/markdown/f8434f1243e4d9dace23021f0f2132a4.png) + +- 当方法参数较多的时,定义丑陋,使用别扭 +- 当方法参数数量、类型不太确定时,任意的参数变化都是非兼容的(例如新增参数),会引起较高的修改成本 +- 方法参数注释不简便,以至于绝大部分业务项目都不会有方法参数注释 + +## 二、结构化编程 + +### 1、 `controller` 结构化改进 + +**结构化优点:** + +- 通过结构化管理接口输入/输出参数,参数接收不再需要硬编码参数名称,降低维护成本,避免参数名称硬编码错误问题 +- 可以做到自动化的参数接收、转换、校验,提高生产力 +- 可以规范化接口编写 +- 使得接口管理能够像普通的函数管理那么方便,通过返回 `error` 来判断接口处理结果,并可以规范化统一错误机制 +- 使得自动化的接口文档生成变为了可能,并保障了接口结构定义和接口文档同步维护 + +**结构化示例:** + +结构定义: + +![](/markdown/686ee75e775a1076387154615c40e868.png) + +方法使用: + +![](/markdown/6f0cd9333bb1c514a1047c0e17024997.png) + +### 2、 `service` 结构化改进 + +**结构化优点:** + +- 当方法参数较多的时,通过结构体优雅管理参数 +- 当方法参数数量、类型不太确定时,参数的增加对方法调用来说都是兼容性的 +- 对结构体属性的注释描述更加便捷,提高代码维护质量 + +**结构化示例:** + +![](/markdown/37a0eecf7f1c45bf99bdd98ec205eea0.png) + +## 三、注意事项 + +- `service` 层的方法在使用结构化管理输入/输出参数时,结构体中任意参数都将会被看做非必需参数。因此需要根据业务场景合理评估可行性。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..f00b20aad92 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\216\245\345\217\243\345\214\226\344\270\216\346\263\233\345\236\213\350\256\276\350\256\241.md" @@ -0,0 +1,52 @@ +--- +slug: '/docs/design/interface-generic' +title: '接口化与泛型设计' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,接口化,泛型设计,框架组件,灵活性,扩展性,Adapter,gsession,Storage实现,数据类型转换] +description: 'GoFrame框架中接口化与泛型设计的基本概念及应用,强调接口化设计带来的灵活性和扩展性,通过使用泛型提高参数的灵活性,同时简化使用复杂度。在实际应用中,通过Adapter实现灵活的组件接口层设计,并提供多个默认实现供选择。' +--- + +## 一、基本介绍 + +接口化是更高层次的抽象。框架组件的设计尽可能使用了接口化,而不是尽可能提供具体实现。接口化设计的最大的好处,是允许使用者自定义实现,来替换组件底层的接口层,以实现很强的灵活性和扩展性。 + +## 二、组件接口化 + +`GoFrame` 框架的核心组件均采用了接口化设计,举个例子,如下图展示了部分组件的接口化实现概览: + +![](/markdown/f7c64eb343963d83adee0800a7774045.png) + +大部分的组件,使用了 `Adapter` 作为自身接口层的名字,通过 `SetAdapter` 方法来设置当前的接口实现,通过 `GetAdapter` 方法获取当前组件的接口实现对象。此外,为了提高易用性,组件都会提供一些默认的 `Adapter` 实现,可供使用者选择。拿 `gsession` 组件举例: + +![](/markdown/5b6e3ff29277e5e5bd32707d9a29bf4c.png) + +底层接口采用了 `Storage` 进行定义,并提供了 `File/Memory/Redis/RedisHashTable` 四种实现供选择,默认实现为 `File`。 + +## 三、接口化与泛型 + +组件的接口化设计可扩展性是很高的,但是在具体落地的时候需要结合泛型来实现更灵活的使用。同样拿 `gsession` 组件举例,参数的返回均采用了泛型,在业务使用时根据需要再转换为对应的数据类型。 + +### 提高参数灵活性、简化使用复杂度 + +在不使用泛型的情况下,我们的接口要么提供各种类型的方法、要么使用 `interface{}` 类型返回,使用的复杂度都比较高。统一通过泛型的数据类型返回,使得参数类型更加灵活,极大地降低了使用复杂度。 + +![](/markdown/b8a2950b795cf7cb987cbfa7a305ff72.png) + +泛型支持转换为各种类型: + +![](/markdown/76fcb2211bfb4d98a88bdb9d1288b574.png) + +根据业务场景需要转换为对应的数据类型。类型转换使用了框架统一的类型转换组件,底层会优先使用断言进行类型识别,以保证转换的效率。 + +![](/markdown/4605ec6822024dd52fe79ea75d6497d9.png) + +### 统一使用方式、屏蔽底层影响 + +针对于一些复杂类型的接口化场景,接口的底层实现上可能会存在外部存储的情况、更会产生序列化/反序列化操作,可能会改变/丢失数据类型。使用泛型将能够通过统一的使用方式屏蔽底层实现的影响。例如以下示例,无论底层 `Session` 实现如何变化,上层使用均通过泛型的 `Scan` 方法转换为目标对象。 + +![](/markdown/de4f942e624d5e30a40f7d9d087c35fc.png) + +## 四、注意事项 + +虽然框架提供了泛型设计,但是并不推荐在业务中广泛使用泛型。业务层的数据结构设计,包括接口和业务模型数据结构,应当是准确的、确定的。 diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..5785e9636f0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\241\206\346\236\266\350\256\276\350\256\241.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/design' +title: '框架设计' +sidebar_position: 99 +hide_title: true +keywords: [GoFrame,GoFrame框架,框架设计,设计思想,内功心法,组件使用,独特设计,技巧性框架,理解设计,使用指南] +description: 'GoFrame框架的设计思想是其灵魂所在,是使用者不可或缺的内功心法。相比于简单的技巧性框架和组件使用,GoFrame更注重指导使用者理解设计理念。掌握了GoFrame的独特设计思想,就等于掌握了整个框架的精髓。' +--- + + +设计思想是 `GoFrame` 框架的灵魂,同时对于使用者来讲,是不可或缺的内功心法。 + +授人予鱼不如授人以渔,比起技巧性的框架、组件使用,我们想尽可能地告诉大家为什么这样、为什么那样。 + +`GoFrame` 有其独特的设计思想,理解了 `GoFrame` 的设计思想,您就理解了 `GoFrame` 的全部。 + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..fa6ed01990c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\346\250\241\345\235\227\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,188 @@ +--- +slug: '/docs/design/modular' +title: '模块化设计' +sidebar_position: 0 +hide_title: true +keywords: [模块化设计,软件解耦,代码复用,GoFrame框架,Golang模块,软件开发原则,模块复用,编译型语言,单仓库包设计,模块聚合] +description: '软件模块化设计及其复用原则,详细讲解模块如复用/发布等同原则、共同闭包原则和共同复用原则。通过GoFrame框架的模块化设计实例分析,强调可维护性的重要性以及模块设计的平衡策略。模块化设计有助于提高开发效率和代码质量,确保软件的稳定性和易维护性。' +--- + +本章节我们先讲一讲在软件设计中,模块化的一些设计和复用原则,然后再介绍 `GoFrame` 框架的模块化设计,以便于大家更好地了解 `GoFrame` 框架模块化设计的思想。 + +## 一、什么是模块 + +**模块** 也称作 **组件**,是软件系统中可复用的功能逻辑封装单位。在不同的软件架构层次,模块的概念会有些不太一样。在开发框架层面,模块是某一类功能逻辑的最小封装单位。在 `Golang` 代码层面中,我们也可以将 `package` 称作模块。 + +## 二、模块化的目标 + +软件进行模块化设计的目的,是为了使得软件功能逻辑尽可能的 **解耦** 和 **复用**,终极目标也是为了保证软件开发维护的效率和质量。 + +## 三、模块复用原则 + +### `REP` 复用/发布等同原则 + +**复用/发布等同原则**( `Reuse/Release Equivalency Principle`):软件复用的最小粒度应等同于其发布的最小粒度。 + +直白地说,就是要复用一段代码就把它抽成模块。 + +### `CCP` 共同闭包原则 + +**共同闭包原则**( `Common Closure Principle`):为了相同目的而同时修改的类,应该放在同一个模块中。 + +对大部分应用程序而言, **可维护性** 的重要性远远大于 **可复用性**,由同一个原因引起的代码修改,最好在同一个模块中,如果分散在多个模块中,那么开发、提交、部署的成本都会上升。 + +### `CRP` 共同复用原则 + +**共同复用原则**( `Common Reuse Principle`):不要强迫一个模块依赖它不需要的东西。 + +相信你一定有这种经历,集成了模块A,但模块A依赖了模块B、C。即使模块B、C 你完全用不到,也不得不集成进来。这是因为你只用到了模块A的部分能力,模块A中额外的能力带来了额外的依赖。如果遵循共同复用原则,你需要把A拆分,只保留你要用的部分。 + +### 复用原则竞争关系 + +`REP`、 `CCP`、 `CRP` 三个原则之间存在 **彼此竞争** 的关系。 `REP` 和 `CCP` 是 **黏合性原则**,它们会让模块变得更大,而 `CRP` 原则是 **排除性原则**,它会让模块变小。遵守 `REP`、 `CCP` 而忽略 `CRP` ,就会依赖了太多没有用到的模块和类,而这些模块或类的变动会导致你自己的模块进行太多不必要的发布;遵守 `REP` 、 `CRP` 而忽略 `CCP`,因为模块拆分的太细了,一个需求变更可能要改 `n` 个模块,带来的成本也是巨大的。 + +![](/markdown/bcfbb9253aefc770b284cc0c67ae68b8.png) + +图2\. 模块复用原则竞争关系张力图 + +优秀的架构师应该能在上述三角形张力区域中定位一个最适合目前研发团队状态的位置,例如在项目早期, `CCP` 比 `REP` 更重要,随着项目的发展,这个最合适的位置也要不停调整。 + +## 四、框架模块设计 + +经过前面关于模块设计原则和复用原则的介绍,我们应该对模块开发和管理这块的原则有了大概的了解,那么我们接着介绍框架的模块化设计就比较容易理解了。 + +### 单仓库包设计 + +根据 `REP` 原则我们了解到,一个可复用的模块是支持独立版本管理的,单仓库包设计也正是如此。 `Golang` 中很多这样的单仓库包,一个包就是一个独立的模块。单仓库包根据 `CRP` 原则可以再进一步的细化解耦拆分。我们来举个例子,在开发复杂的业务项目场景下,常见的包依赖情况,类似于这样的: + +```go +module business + +go 1.16 + +require ( + business.com/golang/strings v1.0.0 + business.com/golang/config v1.15.0 + business.com/golang/container v1.1.0 + business.com/golang/encoding v1.2.0 + business.com/golang/files v1.2.1 + business.com/golang/cache v1.7.3 + business.com/framework/utils v1.30.1 + github.com/pkg/errors v0.9.0 + github.com/goorm/orm v1.2.1 + github.com/goredis/redis v1.7.4 + github.com/gokafka/kafka v0.1.0 + github.com/gometrics/metrics v0.3.5 + github.com/gotracing/tracing v0.8.2 + github.com/gohttp/http v1.18.1 + github.com/google/grpc v1.16.1 + github.com/smith/env v1.0.2 + github.com/htbj/command v1.1.1 + github.com/kmlevel1/pool v1.1.4 + github.com/anolog/logging v1.16.2 + github.com/bgses123/session v1.5.1 + github.com/gomytmp/template v1.3.4 + github.com/govalidation/validate v1.19.2 + github.com/yetme1/goi18n v0.10.0 + github.com/convman/convert v1.20.0 + github.com/google/uuid v1.1.2 + // ... +) +``` + +示例中的模块依赖,都是一些通用模块,大部分业务项目都会涉及到。模块地址是便于演示而写的随意地址,并不一定真实存在。 + +使用 `Golang` 开发过复杂一点的业务项目的小伙伴们,对于这样的场景大家一定不会陌生。一个正常的软件企业,往往至少有数百个这样的项目,真实的模块依赖关系比这里的例子更加复杂。在 `Golang` 项目开发中,对于模块依赖的维护性挑战是比较大的,我们往往会遇到一些痛点,主要的几点: + +- 实现相同功能逻辑的模块较多,选择成本增加 +- 项目依赖的模块过多,项目整体的稳定性会受到影响 +- 项目依赖的模块过多,项目无从下手是否应当升级这些模块版本 +- 模块分散设计,不成体系,难以统一。具体请参考章节: [统一框架设计](统一框架设计.md) + +现身说法举例。 + +本厂的自研模块有数十个,这些模块已经被频繁使用遍布到数百个业务项目中。有一次,我们提交了对几个模块的 `bug fix`,其中有两个还是比较重要的 `bug`,紧接着,我们要求所有业务项目全部升级一下对应模块的版本号,并且这些版本号填写得务必小心。当然,这肯定也不是唯一的一次,随后相同的场景各位同学可以自行脑补。 + +我们也可以选择,不去主动推进所有业务项目升级模块,只要项目还没有触发这些 `bug`,那么就等着业务项目踩到了坑再由项目组自行去升级。领导如果听到这种解决方案......各位同学再自行脑补一下和谐的场景。 + +其实这种问题主要的原因,还是来源于模块的不稳定,模块也是需要不停迭代改进的。项目使用到这些模块,那么就与这些模块建立了 **耦合关系**,耦合模块的变化,必然会影响到依赖的相关项目。越底层的基础模块,顶层模块则对其依赖的越多,影响面也就越大。那是不是只要模块稳定了,就不会存在这样的问题了呢?风险依旧是存在的。 `Golang` 标准库大家觉得算稳定吧,但是它也是在不断的迭代改进过程中,也是不断有 `bug` 出现,只是大家幸运没踩上去而已,风险相对较低。 + +好的软件设计,并不是一成不变,而是能够做到快速响应变化,根据变化快速改进完善。模块的设计和管理,亦是如此。寻求能够快速改进模块逻辑、有效维护模块依赖的方案,比编写更加稳定的功能模块,更加高效和务实。 + +### 模块聚合设计 + +`GoFrame` 的模块化管理思想更偏重于 `CCP` 原则,看重 **可维护性** 比 **可复用性** 更多。由于 `GoFrame` 是基于 **开发框架** 层面的出发点考虑,因此整体框架的设计不是单点设计的,而是自顶向下设计的。前面有提到,越底层的基础模块,顶层模块则对其依赖的越多,影响面也就越大。因此,框架将一些 **通用性的核心模块** 进行统一维护,这样做的目的是使得这些模块共同形成闭包,保证基础模块的稳定性,并通过统一的版本管理,提高开发效率和可维护性,降低接入和维护成本。 + +站在 `GoFrame` 框架模块化设计的角度,前面例子中的依赖情况应当变成以下的样子: + +```go +module business + +go 1.16 + +require ( + github.com/gogf/gf v1.16.0 + github.com/goorm/orm v1.15.1 + github.com/goredis/redis v1.7.4 + github.com/gokafka/kafka v0.1.0 + github.com/google/grpc v1.16.1 + // ... +) +``` + +`GoFrame` 只维护一些通用性的核心模块,其他非通用核心模块或者稳定性较高的模块,依旧建议使用单仓库包的形式进行依赖引入,正如 `REP` 和 `CRP` 模块复用原则倡导的那样。在这种设计模式下: + +- 框架核心维护较全面的通用基础模块,降低基础模块选择成本 +- 我们只需要维护一个统一的框架版本,而不是数十个模块版本 +- 我们只需要了解一个框架的内容变化,而不是数十个模块的内容变化 +- 升级的时候只需要升级一个框架版本,而不是数十个模块版本的升级 +- 减轻开发人员的心智负担,提高模块可维护性,更容易保证各业务项目的模块版本一致性 + +## 五、常见问题解答 + +### 1、虽然每一个模块都按照低耦合设计,模块虽然可以选择性引入,但在使用时也得全量下载完整框架代码 + +这对编译型开发语言来说并不是问题, **文件层面的源文件下载与模块之间的逻辑耦合没有直接关系**。这个问题的根因,主要是来源于我们对 **编译型语言** 和 **解释型语言** 理解的思路不一样。在互联网时代,解释型语言大行其道,而Golang语言在这个时代广受欢迎的同时却又独树旗帜。 + +![](/markdown/7b9d58a737b0340d95454312801c3c0e.png) + +- **编译型语言**:(以静态编译为例)往往以 `main` 包为入口,编译器会自动分析源码并将所有逻辑依赖模块中对应的资源进行编译处理,最终生成为静态二进制文件进行发布,自身源文件以及依赖模块(逻辑依赖)的源文件只在编译阶段使用,源码文件并不会直接用于发布,如:C/C++、Golang、Rust等。 +- **解释型语言**:往往会将自身源文件(或中间码)以及依赖模块的源文件(或中间码)全部进行打包发布,例如:PHP、Java、NodeJS、Python等。这个时候,依赖模块的源码大小对于项目发布来说影响会比较大。并且,打包时候的模块依赖处理并不会检查"逻辑依赖",只要依赖配置文件中存在指定模块,那么该模块都会被共同打包发布。假如模块中有10万个函数,即使其中只有一个函数被使用到,该模块所有函数将被共同打包发布。因为解释型语言在代码发布前并没有"编译-汇编-链接"等阶段,只能在运行时对源码及模块依赖做完整解析处理。特别是PHP/Java转Go的同学,这一块的思维需要转变适应。 + +### 2、框架中任一模块的版本变更都会引起框架版本的发布,框架的发布频次是否会变高? + +当然,框架的模块设计也会充分考虑稳定性因素,仅会将一些 **通用性的核心模块** 按照 `CCP` 进行管理,并不会包含特定业务的逻辑封装,因为涉及到特定业务的功能逻辑实现将会为框架模块带来更多的不稳定变化。 + +在保证一定的稳定性前提下,模块的版本发布按照框架统一的迭代开发计划进行,除了必要的 `hot fix` 之外,版本发布设置有固定的时间窗口,以保证框架核心的稳定性。因此,框架通过模块聚合的方式进行版本管理,不仅没有增加框架的版本发布频次,反而降低了框架的版本发布频次,使得框架中的模块版本更加稳定。 + +### 3、框架聚合并维护通用性的核心模块,通用性的核心模块定义是什么? + +首先,它们是 **基础模块**,往往位于模块依赖链的最底层,这部分的模块变化对项目稳定性影响最大。 + +其次,绝大部分项目(二八定律)都会依赖的通用性基础模块,可以称作核心模块。 + +最后,这部分模块不包含具体的 **业务封装实现**。反例如:微信公众号/小程序、CMS/CRM、区块链等相关模块都是具体业务实现封装。 +:::tip +关于模块通用性的评估无法完全准确,框架为保证核心精简会尽可能持保守态度,并且会根据实际情况在未来的迭代中逐步做调整。 +::: +以下为可供参考的模块分层: + +![](/markdown/f48e08aa60bb126bb41953bcbe98b438.png) + +模块分层参考 + +**业务实现模块**:特定业务项目逻辑实现,这里包含业务项目进一步的代码分层。 + +**通用业务模块**:可复用的业务逻辑封装,例如微信公众号/小程序、CMS/CRM、区块链等相关业务逻辑封装模块。 + +**通用基础模块**:标准库不提供或者基于标准库封装扩展的基础模块,例如:配置、校验、缓存、ORM、I18N等等。 + +**标准基础模块**:Golang标准库。 + +### 4、框架中包含的模块多,人的精力有限,我觉得每个模块肯定没有github上其他单包的项目更好 + +一件事情做得少并不就能做得"好",这两个逻辑之间没有必然关系。 + +### 5、框架中包含的模块多,我觉得每个模块的性能往往就不会高 + +哈哈。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..6240a97c947 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\347\273\237\344\270\200\346\241\206\346\236\266\350\256\276\350\256\241.md" @@ -0,0 +1,102 @@ +--- +slug: '/docs/design/unified' +title: '统一框架设计' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,开发框架,软件架构,技术体系化,开发规范化,组件统一化,版本一致性,解决方案沉淀,资源浪费避免] +description: '统一开发框架是软件架构和代码开发的关键,通过技术体系化、开发规范化、组件统一化以及版本一致性,实现框架的高度协调和可维护性,避免资源浪费,帮助开发团队聚焦于业务本身。这种框架提供了高效的错误堆栈追踪能力,并具备强大的战斗力和凝聚力,为企业与社区的良性循环提供了基础。GoFrame框架实现了这些特性,是现代软件开发的重要工具。' +--- + +软件行业和建筑行业比较像,如果说我们的产品是一栋高楼大厦,那么程序代码就是建筑高楼的砖坯(我们每天的工作就像是在不停"搬砖")。如果说软件架构是高屋建瓴,那么程序代码是软件架构能够准确落地的关键构成。 + +![](/markdown/8f8075c3f449ab501c9d25ce5050db52.png) + +程序代码如此重要,那么开发框架的重要性不言而喻。开发框架着力于代码层面,解决通用性的技术问题,目的是使得开发人员能够将精力关注于业务本身、辅助软件架构能够快速响应业务变化、提高软件的整体开发和维护效率。 + +本章节我们主要介绍统一开发框架建设的意义和必要性。 + +> 在开始本章节之前,建议您先了解一下框架的: [模块化设计](模块化设计.md) ,部分灵感和同感来源于: [小马的经验分享](../../community/社区投稿/Golang框架选型比较_goframe_beego_iris和gin.md) + +## 一、技术体系化 + +![](/markdown/2b04e46ddf26d0d9233f84c9ba69c6f3.png) + +体系化更关注的是框架整体战斗力,而不是每个模块本身 + +框架体系化设计的显著特点: + +- 体系完善、组件丰富 +- 规范统一、风格一致 +- 统一抽象、设计严密 +- 执行高效,无重复逻辑 + +我们这里的体系化是指微观层面的代码开发框架自顶向下统一设计,使得整个框架的设计思想是一体的,而不是分散的。从技术上,解决一个具体的问题比较简单,开发一个特定的模块也比较容易,但是如何将共性的问题进行抽象沉淀、将各个独立的模块按照统一的设计思想组织协调,并产生强大的综合战斗力却不是一件简单的事情。这要求框架的设计师具备一定的技术底蕴、经验沉淀、格局和前瞻性,而不是眼界只关注于单个模块本身。 + +例如,我们即使没有开发过框架,但是应该或多或少使用过,知道一个框架至少会包含哪些模块。当我们需要写日志的时候,我们知道这种组件框架必定提供,那么会去框架中寻找并从官网获得使用帮助。当我们需要WebServer、数据库访问、模板引擎等等能力的时候,我们也可以预料得到,这种组件框架必定会有提供,那么也会去框架中寻找并从官网获得使用帮助。 + +再例如,我们在使用框架中各种模块的时候,虽然各个模块都是低耦合设计,按需引入使用,但是发现她们的配置管理方式都是一致的,都是结构化的配置管理对象,通过相同的配置管理模块,通过固定的配置项到配置对象属性映射规则,通过 `Get`/ `Set` 开头的方法进行读取和设置(所有组件的参数获取和设置也是 `Get`/ `Set` 开头的方法),全局的环境变量/启动参数设置也是类同的。这使得开发者能够快速认知到框架行为,做到快速接入、降低学习成本的目的。 + +再例如,在框架层面有一个非常棒的特性 \- 全错误堆栈特性,框架所有组件的 `error` 返回均带有错误堆栈,便于顶层业务在出错时通过错误堆栈快速定位问题。目前 `Golang` 开发语言下仅 `GoFrame` 框架有此能力。 + +以上只是举的几点简单示例,如果您感兴趣,可以从框架中发现更多有趣的点。 + +最后,我们可以想一想,为什么我们潜意识中能够认知到框架的行为、框架能够提供极高的便捷性和极低的接入成本、框架模块在"高内聚,低耦合"的设计思想下却整体有着非常高的组织协调性。为什么会造成这样的现象?其实这就是框架采用体系化设计,还是"东拼西凑"封装 的差异。 +:::tip +举个比较贴切的比喻: `GoFrame` 是一支纪律性、凝聚力和战斗力极强的"正规军",而不是"东拼西凑"的"散兵游勇"。 +::: +## 二、开发规范化 + +![](/markdown/5f76d7bd6d1a06dce9641fec0c497b77.png) + +代码层面也是需要有一系列的开发规范,如基本的 代码结构、分层模型、封装设计等,具体请参考: [工程开发设计(🔥重点🔥)](工程开发设计/工程开发设计.md)。统一的框架设计,将会使得所有的业务项目按照统一的代码设计进行编码,形成统一的开发规范。此外,框架的开发工具链也会使得开发规范更容易快速推广和落地实施: [开发工具](../开发工具/开发工具.md) 。 + +## 三、组件统一化 + +![](/markdown/19cac91617dc457b461391e208b675b3.png) + +> 这里的统一化有两层概念: +> +> - 多个相同功能组件统一为一个组件。 +> - 多个不同功能组件统一到框架管理。 + +另一个痛点就是开发组件的百花齐放: + +- 实现相同功能逻辑的模块较多,选择成本增加 +- 项目依赖的模块过多,项目整体的稳定性会受到影响 +- 项目依赖的模块过多,项目无从下手是否应当升级这些模块版本 +- 项目依赖的模块过多,容易出现不同模块依赖同一第三方模块不同版本,引发版本兼容问题 +- 各个模块孤立设计,单独看待每个模块可替换性很高,开发体系难以建立,开发规范难以统一 + +![](/markdown/1c16c5ec1bae23caaf9509673f782d0a.png) + +统一的开发框架才能将各个模块" **各自为政**"的状态收归到" **统一管理**": + +- 框架自顶向下设计,形成体系化、统一化的模块设计,更有利于开发规范化的实施 +- 框架核心维护较全面的通用基础模块,降低基础模块选择成本 +- 我们只需要维护一个统一的框架版本,而不是数十个模块版本 +- 我们只需要了解一个框架的内容变化,而不是数十个模块的内容变化 +- 升级的时候只需要升级一个框架版本,而不是数十个模块版本的升级 +- 统一的模块化设计可以减少不必要的逻辑实现,提高模块性能及易用性 +- 减轻开发人员的心智负担,提高模块可维护性,更容易保证各业务项目的模块版本一致性 + +## 四、版本一致性 + +版本一致性问题主要来源于项目依赖的模块过多、版本过多,造成版本很难以统一维护和升级。开发框架将模块统一化管理后,更容易保证项目模块的版本一致性。但是需要注意的是,这种一致性不是强一致性,只是降低了模块及版本的维护复杂度,但是不一致性的问题依然存在。关于痛点和改进我们在之前的章节也有介绍过,同时也可以参考章节: [模块化设计](模块化设计.md) 。这里不再赘述。 + +> 行业内也有一些 **版本强一致性** 的代码管理方案,例如采用 `Monorepo` **大仓** 的代码管理方式,却各有利弊,可自行了解,这里不赘述。 + +## 五、解决方案沉淀 + +![](/markdown/642e90cfc4809a4f237073c7e80f25d5.png) + +基于统一的开发框架,更容易形成解决方案沉淀,企业与社区形成良性循环。解决方案的沉淀优先采用工具以及代码形式,而不是文档。 + +## 六、避免资源浪费 + +当每个团队都在试图自己创造轮子时,不仅无法形成统一的开发规范,而且会出现非常多的资源浪费。 + +> 这在 `Golang` 语言流行的初期,或者一个初创公司技术体系不完善的初期,现象比较明显。 + +![](/markdown/fb5e4135a82ff9ca41c79db9a4c6b89c.jpeg) + +让项目组把精力更多的投入到业务中,相信这是大多数技术公司的共识。使用统一的开发架构,可以把共性的技术问题提炼出来,并形成通用的解决方案。避免每个项目都独自去解决遇到的各种各样的技术难题,有效的把精力释放出来。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 00000000000..c3a72ff3f10 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\346\241\206\346\236\266\350\256\276\350\256\241/\351\232\220\345\274\217\344\270\216\346\230\276\345\274\217\345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/design/initialization' +title: '隐式与显式初始化' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,隐式初始化,显式初始化,Golang,服务器配置,数据库配置,模块初始化,错误定位,初始化逻辑] +description: '程序启动时需要执行的初始化操作,包括GoFrame框架中的隐式初始化和显式初始化。隐式初始化通过包的init方法实现,但可能导致程序启动失败,建议显式调用进行复杂初始化。显式初始化在业务开发中更为可取,以保证程序的可维护性。' +--- + +我们知道在程序启动时会需要执行一些"初始化"的逻辑操作,例如: `Server` 配置、各种数据库( `MySQL`、 `Redis`、 `Kafka` 等)配置、业务对象配置等等。绝大多数场景下,我们有两种初始化方式:隐式初始化和显式初始化。 + +## 一、隐式初始化 +:::warning +特别注意:在 `Golang v1.21` 版本后, `init` 的初始化执行顺序发生了变化,并且可能会引起依赖 `init` 来执行初始化逻辑的包的问题。因此,不建议在 `init` 中执行 **复杂的初始化逻辑**, **建议是通过显式调用方式来实现模块复杂的初始化逻辑**。 +::: +隐式初始化一般通过包初始化方法 `init` 执行初始化。需要注意的是,如果初始化逻辑存在错误的可能,由于 `init` 方法的错误无法被上层捕获,初始化出错时往往直接终止程序启动。例如: + +![](/markdown/9190e5a8e2acf34a70442c6814a52327.png) + +隐式初始化出错时往往直接终止程序启动 + +隐式初始化的好处是不需要手动调用初始化方法,对于开发者隐藏了初始化细节,因此开发者没有心智负担。但是缺点也同样如此,开发者不知道初始化细节,一旦出现错误时,很难快速定位错误原因。因此使用隐式初始化时,往往要求在初始化出错时将详细的错误以及堆栈信息打印出来便于错误定位。 + +`GoFrame` 框架的很多模块都采用了隐式初始化,隐藏模块的初始化细节,减少开发者的心智负担。例如: + +![](/markdown/d019031d40a93f6318a933271d63c503.png) + +`GoFrame` 中的模块普遍存在隐式初始化设计 + +![](/markdown/b0b839a86595ee57f2c5a1b39c559df0.png) + +使用 `GoFrame` 框架的 `main` 包隐式 `imports` + +关于包 `init` 方法的初始化流程: + +![](/markdown/40b3b7c2b75dcb36be348c840ca0eb3e.png) + +## 二、显式初始化 + +显式初始化要求开发在程序启动时,如在 `main` 或者 `boot` 模块中,调用特定的方法来执行初始化操作。一般来说,基础组件的初始化往往采用隐式初始化多一些,因为对于使用者来讲并不关心底层基础模块的初始化逻辑,而业务模块的初始化大多数会采用显式初始化。例如: + +![](/markdown/0124c249f03cd1f9fd78fe0970ffbda6.png)在 `boot` 包中按照顺序执行显式初始化 + +![](/markdown/8417caae0e203d44d43c6bca369b3023.png)在 `main` 包调用 `boot.Boot()` 方法执行初始化 + +## 三、如何选择 + +在业务场景下,非特殊必要,我们建议大家采用 **显式初始化** 的方式,以保证更好的可维护性。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..898d2740d59 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/I18N\347\273\204\344\273\266.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/i18n' +title: 'I18N组件' +sidebar_position: 10 +hide_title: true +keywords: [I18N,国际化,本地化,翻译,语言支持,多语言,GoFrame,GoFrame框架,开源框架,可扩展组件] +description: 'I18N组件是GoFrame框架中的重要模块,提供国际化和本地化支持,帮助开发者实现多语言网站或应用程序。通过I18N组件,用户可以在不同语言环境中更流畅地体验软件功能,提升全球用户的使用体验。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" new file mode 100644 index 00000000000..261c2fad236 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/I18N\347\273\204\344\273\266/\345\233\275\351\231\205\345\214\226-gi18n.md" @@ -0,0 +1,11 @@ +--- +slug: '/docs/components/i18n-gi18n' +title: '国际化-gi18n' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,国际化,i18n,gi18n,多语言支持,Web开发,框架,软件本地化,开源] +description: '在使用GoFrame框架构建网站时,我们提供了强大的国际化支持模块gi18n,旨在简化Web应用程序的多语言实现。通过这种方式,开发者能够更高效地满足全球用户的语言需求,提高用户体验和产品的可接受度。' +--- + +详情请参考章节: [I18N国际化](../../核心组件/I18N国际化/I18N国际化.md) + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" new file mode 100644 index 00000000000..4fa8cc36469 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/NoSQL Redis.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/contrib-nosql-redis' +title: 'NoSQL Redis' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame框架,gredis组件,Redis客户端,连接池设计,命令通道,Redis操作,GoFrame文档,接口化设计,NoSQL Redis,社区组件] +description: 'NoSQL Redis客户端在GoFrame框架中的实现,主要通过gredis组件进行Redis操作,采用连接池设计和命令通道方式,保证了组件的通用性和扩展性。本文提供安装和引用指南,强调gredis的显著特性,并链接至相关接口文档。开发者可通过社区组件实现100多项常用方法,并支持包括集群化操作在内的多种高级功能。' +--- + +## 基本介绍 + +`Redis` 客户端由 `gredis` 组件实现,底层采用了链接池设计。 +:::tip +为了保证通用性和扩展性, `gredis` 组件采用了 **命令通道** 的方式执行 `Redis` 操作。当您不知道命令通道的参数如何传递时,可以参考终端命令行的参数传递。也就是说,所有的操作都和命令行的参数传递保持一致。 +::: +**使用方式**: + +安装: + +```bash +go get -u github.com/gogf/gf/contrib/nosql/redis/v2 +``` + +引用: + +```go +import ( + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + // other imported packages. +) +``` + +**接口文档**: + +- [https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis](https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis) +- [https://github.com/gogf/gf/tree/master/contrib/nosql/redis](https://github.com/gogf/gf/tree/master/contrib/nosql/redis) + +**简要介绍:** + +`gredis` 使用了连接池来进行 `Redis` 连接管理,通过 `Config` 配置对象或者 `Set*` 方法可以对连接池的属性进行管理,通过 `Stats` 方法可以获取连接池的统计信息。 `gredis` 使用接口化的设计来解耦对 `redis` 的底层依赖,通过社区组件的方式实现了 `100+` 项常用方法,并且提供了分组的方式来管理接口。 +:::warning +`gredis.Redis` 客户端对象提供了一个 `Close` 方法,该方法用于关闭 `Redis` 客户端(同时关闭客户端的连接池),而不是连接对象,开发者基本不会用到,非高级玩家请不要使用。 +::: + +## 组件特性 + +`gredis` 具有以下显著特性: + +- 使用简便,功能强大 +- 统一配置组件进行配置 +- 提供 `100+` 项常用方法社区组件实现 +- 支持单实例及集群化操作 +- 支持 `Redis` 服务所有特性 +- 支持 `OpenTelemetry` 可观测性 +- 支持单例对象、也支持动态创建对象 +- 接口化设计,很高的灵活性和扩展性 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..29837884b03 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-Conn\345\257\271\350\261\241.md" @@ -0,0 +1,67 @@ +--- +slug: '/docs/components/contrib-nosql-redis-conn' +title: 'Redis-Conn对象' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,Redis,Conn对象,订阅发布,连接池,长链接,连接超时,订阅模式,发布模式] +description: '在GoFrame框架中使用Redis的Conn对象进行长链接操作,如订阅发布等功能。通过使用连接池获取连接对象进行操作,同时注意连接对象超时问题以及使用后的关闭操作。示例代码展示了通过Conn实现订阅发布模式,程序将在终端打印从Redis Server获取的数据。' +--- + +## `Conn` 对象 + +如果需要实现长链接性的 `Redis` 操作(例如订阅发布),那么我们可以使用 `Conn` 方法从连接池中获取一个连接对象,随后使用该连接对象进行操作。但需要注意的是,该连接对象不再使用时,应当显式调用 `Close` 方法进行关闭(丢回连接池)。 +:::warning +由于该 `Conn` 是个连接对象,注意该对象存在连接超时的限制,超时和服务端配置有关。 +::: +## 订阅/发布 + +我们可以通过 `Redis` 的 `Conn` 实现订阅/发布模式。 + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + channel = "channel" + ) + conn, _ := g.Redis().Conn(ctx) + defer conn.Close(ctx) + _, err := conn.Subscribe(ctx, channel) + if err != nil { + g.Log().Fatal(ctx, err) + } + for { + msg, err := conn.ReceiveMessage(ctx) + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(msg.Payload) + } +} +``` + +执行后,程序将阻塞等待获取数据。 + +另外打开一个终端通过 `redis-cli` 命令进入 `Redis Server`,发布一条消息: + +```bash +$ redis-cli +127.0.0.1:6379> publish channel test +(integer) 1 +127.0.0.1:6379> +``` + +随后程序终端立即打印出从 `Redis Server` 获取的数据: + +```test +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" new file mode 100644 index 00000000000..0647c0f21db --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\344\275\277\347\224\250\347\244\272\344\276\213.md" @@ -0,0 +1,196 @@ +--- +slug: '/docs/components/contrib-nosql-redis-example' +title: 'Redis-使用示例' +sidebar_position: 1 +hide_title: true +keywords: [Redis示例,GoFrame框架,NoSQL数据库,Set操作,Get操作,SetEx操作,HSet操作,HMSet操作,HGetAll操作,HMGet操作] +description: '使用GoFrame框架在Redis中执行基本操作的示例,包括Set/Get、SetEx、HSet/HGetAll和HMSet/HMGet操作。这些代码示例展示了如何通过GoFrame框架的Redis模块进行数据存储和检索,适用于初学者学习如何在GoFrame框架环境中实现Redis功能。本示例同时提醒用户在Redis版本4.0.0及以上中HMSET已弃用,应使用HSET。' +--- + +## `Set/Get` 操作 + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + _, err := g.Redis().Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +执行后,终端输出: + +```value +``` + +## `SetEx` 操作 + +```go +package main + +import ( + "fmt" + "time" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + err := g.Redis().SetEX(ctx, "key", "value", 1) + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.IsNil()) + fmt.Println(value.String()) + + time.Sleep(time.Second) + + value, err = g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.IsNil()) + fmt.Println(value.Val()) +} +``` + +执行后,终端输出: + +```false +value +true + +``` + +## `HSet/HGetAll` 操作 + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key = "key" + ) + _, err := g.Redis().HSet(ctx, key, g.Map{ + "id": 1, + "name": "john", + "score": 100, + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // retrieve hash map + value, err := g.Redis().HGetAll(ctx, key) + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.Map()) + + // scan to struct + type User struct { + Id uint64 + Name string + Score float64 + } + var user *User + if err = value.Scan(&user); err != nil { + g.Log().Fatal(ctx, err) + } + g.Dump(user) +} +``` + +执行后,终端输出: + +``` +map[id:1 name:john score:100] +{ + Id: 1, + Name: "john", + Score: 100, +} +``` + +## `HMSet/HMGet` 操作 + +```go +package main + +import ( + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + key = "key" + ) + err := g.Redis().HMSet(ctx, key, g.Map{ + "id": 1, + "name": "john", + "score": 100, + }) + if err != nil { + g.Log().Fatal(ctx, err) + } + + // retrieve hash map + values, err := g.Redis().HMGet(ctx, key, "id", "name") + if err != nil { + g.Log().Fatal(ctx, err) + } + g.Dump(values.Strings()) +} +``` + +执行后,终端输出: + +``` +[ + "1", + "john", +] +``` + +`As per Redis 4.0.0, HMSET is considered deprecated. Please use HSET in new code. ` + +根据 `Redis 4.0.0`, `HMSET` 被视为已弃用。请在新代码中使用 `HSET`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" new file mode 100644 index 00000000000..340257596ae --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\345\221\275\344\273\244\344\272\244\344\272\222.md" @@ -0,0 +1,115 @@ +--- +slug: '/docs/components/contrib-nosql-redis-do-and-serialization' +title: 'Redis-命令交互' +sidebar_position: 2 +hide_title: true +keywords: [Redis命令交互,Do方法,自动序列化与反序列化,GoFrame框架,结构体存取,map存取,gredis库,json序列化,Redis API,Go语言] +description: '在使用GoFrame框架构建的应用中,通过Redis命令交互以及自动化的方式来序列化和反序列化数据。首先,我们讲解了Do方法的强大扩展性,它允许任何Redis命令的执行。随后我们展示了如何使用map和struct来存取数据,并利用json序列化简化编程。通过GoFrame框架与Redis的结合,开发者可以更加高效地进行数据管理。' +--- + +## `Do` 方法 + +`Do` 是通用的命令交互方法,执行同步指令,通过向 `Redis Server` 发送对应的 `Redis API` 命令,来使用 `Redis Server` 的服务。 `Do` 方法最大的特点是使用 `Redis` 命令与服务端交互,因此扩展很强,没有提供 `Redis` 操作方法的其他命令都可以通过 `Do` 方法来实现。使用示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + ) + v, _ := g.Redis().Do(ctx, "SET", "k", "v") + fmt.Println(v.String()) +} +``` + +## 自动序列化/反序列化 + +当给定的参数为 `map`, `slice`, `struct` 时, `gredis` 内部支持自动对其使用 `json` 序列化,并且读取数据时可使用 `gvar.Var` 的转换功能实现反序列化。 + +### `map` 存取 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ( + ctx = gctx.New() + err error + result *gvar.Var + key = "user" + data = g.Map{ + "id": 10000, + "name": "john", + } + ) + _, err = g.Redis().Do(ctx, "SET", key, data) + if err != nil { + panic(err) + } + result, err = g.Redis().Do(ctx,"GET", key) + if err != nil { + panic(err) + } + fmt.Println(result.Map()) +} +``` + +### `struct` 存取 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + type User struct { + Id int + Name string + } + + var ( + ctx = gctx.New() + err error + result *gvar.Var + key = "user" + user = g.Map{ + "id": 10000, + "name": "john", + } + ) + + _, err = g.Redis().Do(ctx, "SET", key, user) + if err != nil { + panic(err) + } + result, err = g.Redis().Do(ctx, "GET", key) + if err != nil { + panic(err) + } + + var user2 *User + if err = result.Struct(&user2); err != nil { + panic(err) + } + fmt.Println(user2.Id, user2.Name) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" new file mode 100644 index 00000000000..54d4cd7fa2f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\346\216\245\345\217\243\345\214\226\350\256\276\350\256\241.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/components/contrib-nosql-redis-interface' +title: 'Redis-接口化设计' +sidebar_position: 4 +hide_title: true +keywords: [Redis,接口化设计,GoFrame,GoFrame框架,gredis,自定义Redis Adapter,扩展Redis方法,Redis社区组件,SetAdapter方法,GetAdapter方法] +description: '使用GoFrame框架中的gredis实现一个接口化设计的Redis组件,具备强大灵活性和扩展性。通过实现自定义Redis Adapter,可以轻松覆盖默认实现的方法。文中提供了详细示例,展示了如何在自定义Do方法中实现日志打印,并在业务中使用。' +--- + +`gredis` 采用接口化设计,具有强大的灵活性和扩展性。 + +## 接口定义 + +[https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis#Adapter](https://pkg.go.dev/github.com/gogf/gf/v2/database/gredis#Adapter) + +## 相关方法 + +```go +// SetAdapter sets custom adapter for current redis client. +func (r *Redis) SetAdapter(adapter Adapter) + +// GetAdapter returns the adapter that is set in current redis client. +func (r *Redis) GetAdapter() Adapter +``` + +## 自实现Redis Adapter + +框架社区组件提供了 `Redis Adapter` 的默认实现,如果开发者需要自实现 `Redis Adapter` 或者想要覆盖其中的某一些方法,可以基于该实现来扩展。 + +我们来看一个例子,在该例子中,我们实现一个自定义的 `Redis Adapter`,并且覆盖它的 `Do` 底层方法。为简化示例,我们这里在自实现的 `Do` 方法中打印一条日志即可,后续逻辑仍然走社区 `Redis Adapter` 的实现。 + +```go +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/container/gvar" + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + ctx = gctx.New() + group = "cache" + config = gredis.Config{ + Address: "127.0.0.1:6379", + Db: 1, + } +) + +// MyRedis description +type MyRedis struct { + *redis.Redis +} + +// Do implements and overwrites the underlying function Do from Adapter. +func (r *MyRedis) Do(ctx context.Context, command string, args ...interface{}) (*gvar.Var, error) { + fmt.Println("MyRedis Do:", command, args) + return r.Redis.Do(ctx, command, args...) +} + +func main() { + gredis.RegisterAdapterFunc(func(config *gredis.Config) gredis.Adapter { + r := &MyRedis{redis.New(config)} + r.AdapterOperation = r // This is necessary. + return r + }) + gredis.SetConfig(&config, group) + + _, err := g.Redis(group).Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis(group).Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +执行后,终端输出: + +``` +MyRedis Do: Set [key value] +MyRedis Do: Get [key] +value +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..74395157c37 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/NoSQL Redis/Redis-\351\205\215\347\275\256\347\256\241\347\220\206.md" @@ -0,0 +1,169 @@ +--- +slug: '/docs/components/contrib-nosql-redis-config' +title: 'Redis-配置管理' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gredis,Redis配置,配置管理,配置文件,单例对象,集群配置,模块化,配置方法] +description: '在GoFrame框架中使用gredis组件进行Redis的配置管理。我们推荐通过配置文件来管理Redis配置,支持单实例和集群化配置。此外,还详细说明了各种配置项的使用方法,并提供了相关的代码示例供参考。' +--- + +`gredis` 组件支持两种方式来管理 `redis` 配置和获取 `redis` 对象,一种是通过 **配置组件+单例对象** 的方式;一种是模块化通过 **配置管理方法** 及对象创建方法。 + +## 配置文件(推荐) + +绝大部分情况下推荐使用 `g.Redis` 单例方式来操作 `redis`。因此同样推荐使用配置文件来管理 `Redis` 配置,在 `config.yaml` 中的配置示例如下: + +### 单实例配置 + +``` +# Redis 配置示例 +redis: + # 单实例配置示例1 + default: + address: 127.0.0.1:6379 + db: 1 + + # 单实例配置示例2 + cache: + address: 127.0.0.1:6379 + db: 1 + pass: 123456 + idleTimeout: 600 +``` + +其中的 `default` 和 `cache` 分别表示配置分组名称,我们在程序中可以通过该名称获取对应配置的 `redis` 单例对象。不传递分组名称时,默认使用 `redis.default` 配置分组项)来获取对应配置的 `redis` 客户端单例对象。 + +### 集群化配置 + +``` +# Redis 配置示例 +redis: + # 集群模式配置方法 + default: + address: 127.0.0.1:6379,127.0.0.1:6370 + db: 1 +``` + +### 配置项说明 + +| 配置项名称 | 是否必须 | 默认值 | 说明 | +| --- | --- | --- | --- | +| `address` | 是 | - | 格式: `地址:端口`
    支持 `Redis` 单实例模式和集群模式配置,使用 `,` 分割多个地址。例如:
    `192.168.1.1:6379, 192.168.1.2:6379` | +| `db` | 否 | `0` | 数据库索引 | +| `user` | 否 | `-` | 访问授权用户 | +| `pass` | 否 | `-` | 访问授权密码 | +| `minIdle` | 否 | `0` | 允许闲置的最小连接数 | +| `maxIdle` | 否 | `10` | 允许闲置的最大连接数( `0` 表示不限制) | +| `maxActive` | 否 | `100` | 最大连接数量限制( `0` 表示不限制) | +| `idleTimeout` | 否 | `10` | 连接最大空闲时间,使用时间字符串例如 `30s/1m/1d` | +| `maxConnLifetime` | 否 | `30` | 连接最长存活时间,使用时间字符串例如 `30s/1m/1d` | +| `waitTimeout` | 否 | `0` | 等待连接池连接的超时时间,使用时间字符串例如 `30s/1m/1d` | +| `dialTimeout` | 否 | `0` | `TCP` 连接的超时时间,使用时间字符串例如 `30s/1m/1d` | +| `readTimeout` | 否 | `0` | `TCP` 的 `Read` 操作超时时间,使用时间字符串例如 `30s/1m/1d` | +| `writeTimeout` | 否 | `0` | `TCP` 的 `Write` 操作超时时间,使用时间字符串例如 `30s/1m/1d` | +| `masterName` | 否 | `-` | 哨兵模式下使用, 设置 `MasterName` | +| `tls` | 否 | `false` | 是否使用 `TLS` 认证 | +| `tlsSkipVerify` | 否 | `false` | 通过 `TLS` 连接时,是否禁用服务器名称验证 | +| `cluster` | 否 | `false` | 是否强制设置为集群工作模式。当 `address` 是单个endpoint的集群时,系统会自动判定为单实例模式,这时需要设置此项为 `true`。 | +| `protocol` | 否 | `3` | 设置与 `Redis Server` 通信的 `RESP` 协议版本。 | +| `sentinelUsername` | 否 | | `Sentinel` 模式下的账号 | +| `sentinelPassword` | 否 | | `Sentinel` 模式下的密码 | + +使用示例: + +`config.yaml` + +``` +# Redis 配置示例 +redis: + # 单实例配置示例1 + default: + address: 127.0.0.1:6379 + db: 1 + pass: "password" # 在此配置密码, 没有可去掉 +``` + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + var ctx = gctx.New() + _, err := g.Redis().Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis().Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` + +执行后,输出结果为: + +```value +``` + +## 配置方法(高级) + +由于 `GoFrame` 是模块化的框架,除了可以通过耦合且便捷的 `g` 模块来自动解析配置文件并获得单例对象之外,也支持有能力的开发者模块化使用 `gredis` 包。 + +`gredis` 提供了全局的分组配置功能,相关配置管理方法如下: + +```go +func SetConfig(config Config, name ...string) +func SetConfigByMap(m map[string]interface{}, name ...string) error +func GetConfig(name ...string) (config Config, ok bool) +func RemoveConfig(name ...string) +func ClearConfig() +``` + +使用示例: + +```go +package main + +import ( + "fmt" + + _ "github.com/gogf/gf/contrib/nosql/redis/v2" + + "github.com/gogf/gf/v2/database/gredis" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +var ( + config = gredis.Config{ + Address: "127.0.0.1:6379", + Db: 1, + Pass: "password", + } + group = "cache" + ctx = gctx.New() +) + +func main() { + gredis.SetConfig(&config, group) + + _, err := g.Redis(group).Set(ctx, "key", "value") + if err != nil { + g.Log().Fatal(ctx, err) + } + value, err := g.Redis(group).Get(ctx, "key") + if err != nil { + g.Log().Fatal(ctx, err) + } + fmt.Println(value.String()) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" new file mode 100644 index 00000000000..83a5dc1193c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\345\212\237\350\203\275\350\260\203\350\257\225.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/debug' +title: '功能调试' +sidebar_position: 9 +hide_title: true +keywords: [功能调试,调试工具,代码调试,错误排查,GoFrame,GoFrame框架,开发者工具,调试技术,性能优化,问题解决] +description: '使用GoFrame框架进行功能调试。通过提供有效的调试工具和方法,帮助开发者快速识别和解决代码中的错误问题,提升开发效率和性能。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" new file mode 100644 index 00000000000..996baea400b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\237\350\203\275\350\260\203\350\257\225/\350\260\203\350\257\225\345\212\237\350\203\275-gdebug.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/components/debug-gdebug' +title: '调试功能-gdebug' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,调试功能,gdebug,GoFrame调试,gdebug组件,堆栈分析,调用链信息,性能优化,接口文档] +description: 'GoFrame框架通过gdebug组件提供了丰富的调试功能,适用于开发环境中堆栈和调用链的分析。虽然调试方法与性能效率相关性不强,但可以帮助开发者更好地了解代码执行路径和调用信息。' +--- + +`goframe` 框架提供了丰富的调试功能,由 `gdebug` 组件实现。 +:::warning +所谓的“调试”方法大多数和开发环境有一定关系,包含堆栈和调用链信息分析,并且性能往往不是特别高。 +::: +**使用方式:** + +```go +import "github.com/gogf/gf/v2/debug/gdebug" +``` + +**接口文档:** + +[https://pkg.go.dev/github.com/gogf/gf/v2/debug/gdebug](https://pkg.go.dev/github.com/gogf/gf/v2/debug/gdebug) + +**方法列表:** + +```go +func BinVersion() string +func BinVersionMd5() string +func Caller(skip ...int) (function string, path string, line int) +func CallerDirectory() string +func CallerFileLine() string +func CallerFileLineShort() string +func CallerFilePath() string +func CallerFunction() string +func CallerPackage() string +func CallerWithFilter(filter string, skip ...int) (function string, path string, line int) +func FuncName(f interface{}) string +func FuncPath(f interface{}) string +func GoroutineId() int +func PrintStack(skip ...int) +func Stack(skip ...int) string +func StackWithFilter(filter string, skip ...int) string +func StackWithFilters(filters []string, skip ...int) string +func TestDataPath(names ...string) string +``` + +> 熟悉 `PHP` 的同学可能比较好理解,这里某些方法其实和 `PHP` 的部分 [魔术常量](https://www.php.net/manual/en/language.constants.predefined.php) 功能一致。 `CallerDirectory` 对应 `__DIR__`, `CallerFilePath` 对应 `__FILE__`, `CallerFunction` 对应 `__FUNCTION__`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" new file mode 100644 index 00000000000..bb876680561 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/AES\347\256\227\346\263\225-gaes.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/crypto-gaes' +title: 'AES算法-gaes' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,AES算法,加解密,gaes,Go语言,数据编码,base64,加密解密指南,GoFrame教程] +description: '在GoFrame框架中使用AES算法进行数据加解密。通过引入go包以及调用相关功能函数,用户可以实现安全的数据传输和存储。特别注意在加解密过程中如果数据经过其他编码如base64,则需要准确解码和编码,以确保数据的完整性和安全性。' +--- + +AES算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/crypto/gaes" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gaes](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gaes) + +**温馨提示:** + +如果待解密数据经过其它编码,则要先解码再解密,如base64.decode + +反过来也一样 + +如果希望加密完的数据编码,则将结果编码即可,如base64.encode \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" new file mode 100644 index 00000000000..aec7e6ae10a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/CRC32\347\256\227\346\263\225-gcrc32.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gcrc32' +title: 'CRC32算法-gcrc32' +sidebar_position: 4 +hide_title: true +keywords: [CRC32,gcrc32,GoFrame,GoFrame框架,加密算法,goframe crypto,Go语言,Checksum,数据校验,编码] +description: '在GoFrame框架中使用CRC32算法进行数据校验与加密,包括导入库的方式和相关接口文档链接,帮助开发者有效利用gcrc32模块进行数据完整性验证。' +--- + +CRC32算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/crypto/gcrc32" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gcrc32](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gcrc32) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" new file mode 100644 index 00000000000..7c2b46e2663 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/DES\347\256\227\346\263\225-gdes.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/crypto-gdes' +title: 'DES算法-gdes' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,DES算法,gdes,crypto,encryption,PKCS5PADDING,NOPADDING,三倍长DES,密钥] +description: '在GoFrame框架中使用DES算法的方式,展示了如何通过gdes包实现加密操作。通过链接至官方接口文档以便开发者获取更多技术细节。在包中支持两种补位方式,并对三倍长DES算法的密钥使用进行了特殊说明,以确保数据安全性。' +--- + +DES算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/crypto/gdes" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gdes](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gdes) + +**关于 `gdes` 包中的补位说明:** + +**`gdes` 包中补位方式支持: `PKCS5PADDING`、 `NOPADDING` 两种方式,当使用 `NOPADDING` 方式时需要自定义补位方法。** + +**关于gdes包中的密钥的说明:** + +**当使用三倍长的DES算法时,密钥为16字节时,key3等于key1。** \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" new file mode 100644 index 00000000000..e89e0199d4e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/MD5\347\256\227\346\263\225-gmd5.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gmd5' +title: 'MD5算法-gmd5' +sidebar_position: 0 +hide_title: true +keywords: [MD5算法,GoFrame,gmd5,密码学,加密算法,哈希函数,数据安全,GoFrame框架,GoFrame加密,Go开发] +description: '在GoFrame框架中使用MD5算法,通过导入gmd5库进行数据加密。MD5是一种常用的哈希函数,用于保证数据的完整性和安全性。在GoFrame中可以方便地对数据进行MD5加密,实现简单且高效的数据安全保护。' +--- + +MD5算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/crypto/gmd5" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gmd5](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gmd5) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" new file mode 100644 index 00000000000..5baaf6441fb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/SHA1\347\256\227\346\263\225-gsha1.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/crypto-gsha1' +title: 'SHA1算法-gsha1' +sidebar_position: 3 +hide_title: true +keywords: [SHA1,gsha1,GoFrame,GoFrame框架,crypto,算法,Go语言,加密,数据安全,gogf] +description: 'SHA1算法在GoFrame框架中的使用,提供了具体的导入包方法和相关接口文档链接,帮助用户在使用Go语言进行加密和数据安全时有效应用SHA1算法。' +--- + +SHA1算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/crypto/gsha1" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gsha1](https://pkg.go.dev/github.com/gogf/gf/v2/crypto/gsha1) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" new file mode 100644 index 00000000000..0f8fb03d526 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\212\240\345\257\206\350\247\243\345\257\206/\345\212\240\345\257\206\350\247\243\345\257\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/crypto' +title: '加密解密' +sidebar_position: 5 +hide_title: true +keywords: [加密,解密,GoFrame,加密技术,解密技术,数据安全,GoFrame框架,信息保护,安全组件,数据加密] +description: '使用GoFrame框架进行加密解密的常规方法,包括如何在GoFrame中实现数据的安全性和保护。通过有效的加密技术,确保信息的机密性和完整性,为您的应用程序提供坚实的数据安全保障。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" new file mode 100644 index 00000000000..9d5e775adc1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225-gtest.md" @@ -0,0 +1,103 @@ +--- +slug: '/docs/components/test-gtest' +title: '单元测试-gtest' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,单元测试,gtest,测试断言,GoFrame框架,测试框架,测试用例,Assert,goconvey,testify] +description: 'gtest模块在GoFrame框架下的使用,提供简便和轻量级的单元测试方法。gtest基于标准库testing进行功能扩展,增加了多个测试特性,如测试用例隔离和常用断言方法。适用于大部分的单元测试场景,并在需要更复杂测试时,可结合第三方测试框架如testify和goconvey使用。' +--- + +## 基本介绍 +`gtest` 模块提供了简便化的、轻量级的、常用的单元测试方法。是基于标准库 `testing` 的功能扩展封装,主要增加实现了以下特性: + +- 单元测试用例多测试项的隔离。 +- 增加常用的一系列测试断言方法。 +- 断言方法支持多种常见格式断言。提高易用性。 +- 测试失败时的错误信息格式统一。 +:::tip +`gtest` 设计为比较简便易用,可以满足绝大部分的单元测试场景,如果涉及更复杂的测试场景,可以考虑第三方的 `testify`、 `goconvey` 等测试框架。 +::: +**使用方式**: + +```go +import "github.com/gogf/gf/v2/test/gtest" +``` + +**接口文档**: +本章节更新可能不及时,更全面的接口介绍请参考接口文档。 +[https://pkg.go.dev/github.com/gogf/gf/v2/test/gtest](https://pkg.go.dev/github.com/gogf/gf/v2/test/gtest) + +```go +func C(t *testing.T, f func(t *T)) +func Assert(value, expect interface{}) +func AssertEQ(value, expect interface{}) +func AssertGE(value, expect interface{}) +func AssertGT(value, expect interface{}) +func AssertIN(value, expect interface{}) +func AssertLE(value, expect interface{}) +func AssertLT(value, expect interface{}) +func AssertNE(value, expect interface{}) +func AssertNI(value, expect interface{}) +func Error(message ...interface{}) +func Fatal(message ...interface{}) +``` + +**简要说明**: + +1. 使用 `C` 方法创建一个 `Case`,表示一个单元测试用例。一个单元测试方法可以包含多个 `C`,每一个 `C` 包含的用例往往表示该方法的其中一种可能性测试。 +2. 断言方法 `Assert` 支持任意类型的变量比较。 `AssertEQ` 进行断言比较时,会同时比较类型,即严格断言。 +3. 使用大小比较断言方法如 `AssertGE` 时,参数支持字符串及数字比较,其中字符串比较为大小写敏感。 +4. 包含断言方法 `AssertIN` 及 `AssertNI` 支持 `slice` 类型参数,暂不支持 `map` 类型参数。 + +用于单元测试的包名既可以使用 `包名_test`,也可直接使用 `包名`(即与测试包同名)。两种使用方式都比较常见,且在 `Go` 官方标准库中也均有涉及。但需要注意的是,当需要测试包的私有方法/私有变量时,必须使用 `包名` 命名形式。且在使用 `包名` 命名方式时,注意仅用于单元测试的相关方法(非 `Test*` 测试方法)一般定义为私有,不要公开。 + +## 使用示例 + +例如 `gstr` 模块其中一个单元测试用例: + +```go +package gstr_test + +import ( + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" + "testing" +) + +func Test_Trim(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + t.Assert(gstr.Trim(" 123456\n "), "123456") + t.Assert(gstr.Trim("#123456#;", "#;"), "123456") + }) +} +``` + +也可以这样使用: + +```go +package gstr_test + +import ( + . "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/text/gstr" + "testing" +) + +func Test_Trim(t *testing.T) { + C(t, func() { + Assert(gstr.Trim(" 123456\n "), "123456") + Assert(gstr.Trim("#123456#;", "#;"), "123456") + }) +} +``` + +一个单元测试用例可以包含多个 `C`,一个 `C` 也可以执行多个断言。 断言成功时直接PASS,但是如果断言失败,会输出如下类似的错误信息,并终止当前单元测试用例的继续执行(不会终止后续的其他单元测试用例)。 + +```text +=== RUN Test_Trim +[ASSERT] EXPECT 123456#; == 123456 +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/text/gstr/gstr_z_unit_trim_test.go:20 +2. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/text/gstr/gstr_z_unit_trim_test.go:18 +--- FAIL: Test_Trim (0.00s) +FAIL +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..618b7a509f8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\215\225\345\205\203\346\265\213\350\257\225/\345\215\225\345\205\203\346\265\213\350\257\225.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/test' +title: '单元测试' +sidebar_position: 7 +hide_title: true +keywords: [单元测试,GoFrame,GoFrame框架,测试框架,自动化测试,代码测试,软件开发,开发工具,性能优化,错误检测] +description: '使用GoFrame框架进行单元测试,涵盖测试框架的基本概念和实践方法。通过使用自动化测试工具和技术,开发者可以有效地提高代码质量和软件性能,并在软件开发过程中及时检测和纠正错误。本文档旨在帮助开发者充分利用GoFrame框架进行高效的单元测试。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" new file mode 100644 index 00000000000..3d89c0e7eba --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\205\203\346\225\260\346\215\256-gmeta.md" @@ -0,0 +1,98 @@ +--- +slug: '/docs/components/util-gmeta' +title: '元数据-gmeta' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gmeta,元数据,标签,动态获取,结构体,接口文档,数据方法,获取方法] +description: '在GoFrame框架中使用gmeta包来为用户自定义的结构体添加元数据标签,并通过特定的方法在运行时动态获取这些标签内容,包括如何使用Data方法和Get方法获取指定对象的元数据标签信息。' +--- + +## 基本介绍 + +主要用于嵌入到用户自定义的结构体中,并且通过标签的形式给 `gmeta` 包的结构体打上自定义的标签内容(元数据),并在运行时可以特定方法动态获取这些自定义的标签内容。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/util/gmeta" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gmeta](https://pkg.go.dev/github.com/gogf/gf/v2/util/gmeta) + +**方法列表:** + +```go +func Data(object interface{}) map[string]interface{} +func Get(object interface{}, key string) *gvar.Var +``` + +## 使用示例 + +### `Data` 方法 + +`Data` 方法用于获取指定 `struct` 对象的元数据标签,构成 `map` 返回。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gmeta" +) + +func main() { + type User struct { + g.Meta `orm:"user" db:"mysql"` + Id int + Name string + } + g.Dump(gmeta.Data(User{})) +} +``` + +:::tip +大部分时候,在结构体定义中,我们使用的`gmeta.Meta`的别名`g.Meta`。 +::: + +执行后,终端输出: + +```json +{ + "db": "mysql", + "orm": "user" +} +``` + +### `Get` 方法 + +`Get` 方法用于获取指定 `struct` 对象中指定名称的元数据标签信息。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/gmeta" +) + +func main() { + type User struct { + g.Meta `orm:"user" db:"mysql"` + Id int + Name string + } + user := User{} + fmt.Println(gmeta.Get(user, "orm").String()) + fmt.Println(gmeta.Get(user, "db").String()) +} +``` + +执行后,终端输出: + +```text +user +mysql +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" new file mode 100644 index 00000000000..1d61d6aa16a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\210\206\351\241\265\347\256\241\347\220\206-gpage.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gpage' +title: '分页管理-gpage' +sidebar_position: 5 +hide_title: true +description: '应用GoFrame框架的gpage模块来实现高效的分页管理功能。通过阅读此文档,开发者可以掌握在WEB服务开发中利用gpage模块进行分页的具体操作步骤和优化技巧。' +keywords: [GoFrame,GoFrame框架,gpage模块,分页管理,WEB服务开发,gpage功能,编程指南,模块使用,开发者工具,代码优化] +--- + +分页管理由 `gpage` 模块实现,具体请参考 [分页管理](../../WEB服务开发/分页管理/分页管理.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" new file mode 100644 index 00000000000..7f0dbf3787d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\224\257\344\270\200\346\225\260-guid.md" @@ -0,0 +1,120 @@ +--- +slug: '/docs/components/util-guid' +title: '唯一数-guid' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,GUID,唯一识别,性能高效,简便使用,全局唯一数,唯一数生成,Go语言,开发工具] +description: '使用GoFrame框架的GUID模块生成高性能且简便使用的全局唯一数。GUID生成的字符串由数字及小写英文字符组成,长度固定为32字节。文档详细说明了GUID的生成机制、使用方式及其在各种场景下的优势。' +--- + +`guid` 提供了更简便更高性能的全局唯一数生成功能。生成的 `uid` 字符串仅包含 **数字及小写英文字符**。 + +- **优点**:性能高效、使用简便。 +- **缺点**:字符范围有限、长度固定 `32` 字节。 + +> `guid` 模块的设计目的在于提供一种使用更简便、性能更高效且能满足绝大多数业务场景的唯一数生成。 `guid` 的设计比较简单,详情可参考实现源码。 + +**字符列表**: + +``` +字符类型 字符列表 +数字字符 0123456789 +英文字符 abcdefghijklmnopqrstuvwxyz +``` + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/util/guid" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/guid](https://pkg.go.dev/github.com/gogf/gf/v2/util/guid) + +### 基本介绍 + +`guid` 通过 `S` 方法生成 `32` 字节的唯一数,该方法定义如下: + +```go +func S(data ...[]byte) string +``` + +1. 通过不带任何参数的方式使用,该方法生成的唯一数将会有以下方式构成: + +`MACHash(7) + PID(4) + TimestampNano(12) + Sequence(3) + RandomString(6)` + +其中: + + - `MAC` 表示当前机器的 `MAC` 地址哈希值,由 `7` 个字节构成; + - `PID` 表示当前机器的进程ID,由 `4` 个字节构成; + - `TimestampNano` 表示当前的纳秒时间戳,由 `12` 个字节构成; + - `Sequence` 表示当前进程并发安全的序列号,由 `3` 个字节构成; + - `RandomString` 表示随机数,由 `6` 个字节构成; +2. 通过自定义任何参数的方式使用,该方法生成的唯一数将会有以下方式构成: + +`DataHash(7/14) + TimestampNano(12) + Sequence(3) + RandomString(3/10)` + +主要说明: + + - `Data` 表示自定义的参数,参数类型为 `[]byte`,最多支持 `2` 个参数输入,由 `7` 或 `14` 个字节构成; + - 需要注意的是,输入的自定义参数需要在业务上具有一定的唯一识别性,使得生成的唯一数更有价值; + - 不管每一个 `[]byte` 参数长度为多少,最终都将通过哈希方式生成 `7` 个字节的哈希值。 + - `TimestampNano` 表示当前的纳秒时间戳,由 `12` 个字节构成; + - `Sequence` 表示当前进程并发安全的序列号,由 `3` 个字节构成; + - `RandomString` 表示随机数,由 `3` 或者 `10` 个字节构成,即: + - 如果给定 `1` 个自定义参数,那么剩余的字节将会使用随机数占位,长度为 `10` 个字节; + - 如果给定 `2` 个自定义参数,那么剩余的字节将会使用随机数占位,长度为 `3` 个字节; + +### 基准测试 + +``` +goos: darwin +goarch: amd64 +pkg: github.com/gogf/gf/v2/util/guid +cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz +Benchmark_S +Benchmark_S-12 2665587 423.8 ns/op +Benchmark_S_Data_1 +Benchmark_S_Data_1-12 2027568 568.2 ns/op +Benchmark_S_Data_2 +Benchmark_S_Data_2-12 4352824 275.5 ns/op +PASS +``` + +### 示例1,基本使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/util/guid" +) + +func main() { + fmt.Printf("TraceId: %s", guid.S()) +} +``` + +执行后,输出结果为: + +``` +TraceId: oa9sdw03dk0c35q9bdwcnz42p00trwfr +``` + +### 示例2,自定义参数 + +我们的 `SessionId` 生成需要具有比较好的唯一性,且需要防止轻易的碰撞,因此可以使用以下方式: + +```go +func CreateSessionId(r *ghttp.Request) string { + var ( + address = request.RemoteAddr + header = fmt.Sprintf("%v", request.Header) + ) + return guid.S([]byte(address), []byte(header)) +} +``` + +可以看到, `SessionId` 需要依靠自定义的两个输入参数 `RemoteAddr`, `Header` 来生成,这两个参数在业务上具有一定的唯一识别性,且通过 `guid.S` 方法的设计构成,生成的唯一数将会非常随机且唯一,既满足了业务需要也保证了安全。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" new file mode 100644 index 00000000000..db52405b299 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\256\236\347\224\250\345\267\245\345\205\267.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/util' +title: '实用工具' +sidebar_position: 6 +hide_title: true +keywords: [实用工具,GoFrame,GoFrame框架,组件,开发,效率,文档,工具库,Web框架,开源项目] +description: '该页面介绍了GoFrame框架中的实用工具组件,通过这些工具开发者可以提升开发效率。GoFrame框架提供了一系列高效的实用工具来帮助开发者更快、更轻松地完成项目,为开发者提供便捷的文档开发体验。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" new file mode 100644 index 00000000000..7e2a3332738 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\345\267\245\345\205\267\346\226\271\346\263\225-gutil.md" @@ -0,0 +1,250 @@ +--- +slug: '/docs/components/util-gutil' +title: '工具方法-gutil' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,gutil,工具方法,Go语言,开发组件,数据格式化,接口文档,Dump函数,DumpWithType] +description: 'gutil组件是GoFrame框架中用于封装常用开发工具方法的模块,提供了一系列便利的函数,支持数据结构的友好输出,如Dump和DumpWithType。开发者可以通过github库引入gutil组件,以提高Go语言项目开发效率。' +--- + +## 基本介绍 + +`gutil` 组件封装了一些开发中常用的工具方法。 + +使用方式: + +```go +import "github.com/gogf/gf/v2/util/gutil" +``` + +接口文档: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/gutil](https://pkg.go.dev/github.com/gogf/gf/v2/util/gutil) + +## 常用方法 + +### `Dump` + +- 说明: `Dump` 将 `values` 以更好的可读性的方式输出在标准输出中。 + +- 格式: + +```go +Dump(values ...interface{}) +``` + +- 示例: + +```go +type User struct { + Name string + Age int +} + +type Location struct { + Province string + City string +} + +type UserInfo struct { + U User + L Location +} + +func main() { + userList := make([]UserInfo, 0) + userList = append(userList, UserInfo{ + U: User{ + Name: "郭强", + Age: 18, + }, + L: Location{ + Province: "四川", + City: "成都", + }, + }) + userList = append(userList, UserInfo{ + U: User{ + Name: "黄骞", + Age: 18, + }, + L: Location{ + Province: "江苏", + City: "南京", + }, + }) + + gutil.Dump(userList) +} + +// Output: +[ + { + U: { + Name: "郭强", + Age: 18, + }, + L: { + Province: "四川", + City: "成都", + }, + }, + { + U: { + Name: "黄骞", + Age: 18, + }, + L: { + Province: "江苏", + City: "南京", + }, + }, +] +``` + + +### `DumpWithType` + +- 说明: `DumpWithType` 和 `Dump` 类似,但是多了类型信息。 + +- 格式: + +```go +DumpWithType(values ...interface{}) +``` + +- 示例: + +```go +type User struct { + Name string + Age int +} + +type Location struct { + Province string + City string +} + +type UserInfo struct { + U User + L Location +} + +func main() { + userList := make([]UserInfo, 0) + userList = append(userList, UserInfo{ + U: User{ + Name: "郭强", + Age: 18, + }, + L: Location{ + Province: "四川", + City: "成都", + }, + }) + userList = append(userList, UserInfo{ + U: User{ + Name: "黄骞", + Age: 18, + }, + L: Location{ + Province: "江苏", + City: "南京", + }, + }) + + gutil.DumpWithType(userList) +} + +// Output: +[]main.UserInfo(2) [ + main.UserInfo(2) { + U: main.User(2) { + Name: string(6) "郭强", + Age: int(18), + }, + L: main.Location(2) { + Province: string(6) "四川", + City: string(6) "成都", + }, + }, + main.UserInfo(2) { + U: main.User(2) { + Name: string(6) "黄骞", + Age: int(18), + }, + L: main.Location(2) { + Province: string(6) "江苏", + City: string(6) "南京", + }, + }, +] +``` + + +### `DumpTo` + +- 说明: `DumpTo` 将 `value` 以自定义的输出形式写入到 `write` 中。 + +- 格式: + +```go +DumpTo(writer io.Writer, value interface{}, option DumpOption) +``` + +- 示例: +```go +package main + +import ( + "bytes" + "fmt" + "github.com/gogf/gf/v2/util/gutil" + "io" +) + +type UserInfo struct { + Name string + Age int + Province string + City string +} + +type DumpWriter struct { + Content string +} + +func (d *DumpWriter) Write(p []byte) (n int, err error) { + buffer := bytes.NewBuffer(nil) + buffer.WriteString("I'm Start!\n") + buffer.WriteString(string(p)) + buffer.WriteString("\nI'm End!\n") + + d.Content = buffer.String() + + return buffer.Len(), nil +} + +func main() { + u := UserInfo{ + "a", 18, "b", "c", + } + + var dw io.Writer = &DumpWriter{} + + gutil.DumpTo(dw, u, gutil.DumpOption{}) + + fmt.Println(dw.(*DumpWriter).Content) +} + +// Output: +I'm Start! +{ + Name: "a", + Age: 18, + Province: "b", + City: "c", +} +I'm End! +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" new file mode 100644 index 00000000000..d2311262742 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\346\225\260\346\215\256\346\240\241\351\252\214-gvalid.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gvalid' +title: '数据校验-gvalid' +sidebar_position: 4 +hide_title: true +description: 'GoFrame框架中的gvalid模块,该模块是实现数据和表单校验的核心组件。gvalid在GoFrame中扮演着重要角色,提供了强大的数据验证功能,适用于多种应用场景。' +keywords: [GoFrame,GoFrame框架,数据校验,gvalid模块,数据验证,表单校验,核心组件,gvalid使用,GoFrame数据校验,表单验证工具] +--- + +数据/表单校验由 `gvalid` 模块实现,具体请参考 [数据校验](../../核心组件/数据校验/数据校验.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" new file mode 100644 index 00000000000..a282765a3b5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\347\261\273\345\236\213\350\275\254\346\215\242-gconv.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/util-gconv' +title: '类型转换-gconv' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gconv模块,类型转换,GoFrame类型转换,gconv类型转换,GoFrame gconv,GoFrame文档,GoFrame组件,核心功能] +description: 'GoFrame框架中的类型转换功能,该功能由gconv模块实现。通过引用具体章节,用户可以深入了解如何在GoFrame中进行类型转换,以提升开发效率和代码可靠性。' +--- + +类型转换功能由 `gconv` 模块实现,具体请参考 [类型转换](../../核心组件/类型转换/类型转换.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" new file mode 100644 index 00000000000..7223b8b32a2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\345\256\236\347\224\250\345\267\245\345\205\267/\351\232\217\346\234\272\346\225\260-grand.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/components/util-grand' +title: '随机数-grand' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,随机数,grand模块,随机生成,性能优化,接口文档,字符列表,概率计算,随机字符串] +description: 'GoFrame框架中的grand模块,提供了对随机数操作的优化封装,具备高性能和多样性的随机生成方法,包含整数、字符串及概率性计算。通过实用的方法如Intn、Str等,您可以轻松生成各种类型的随机数据,满足不同的开发需求。' +--- + +`grand` 模块实现了对随机数操作的封装和改进,实现了极高的随机数生成性能,提供了丰富的随机数相关操作方法。 + +使用方式: + +```go +import "github.com/gogf/gf/v2/util/grand" +``` + +接口文档: + +[https://pkg.go.dev/github.com/gogf/gf/v2/util/grand](https://pkg.go.dev/github.com/gogf/gf/v2/util/grand) + +常用方法: + +```go +func N(min, max int) int +func B(n int) []byte +func S(n int, symbols ...bool) string +func Str(s string, n int) string +func Intn(max int) int +func Digits(n int) string +func Letters(n int) string +func Meet(num, total int) bool +func MeetProb(prob float32) bool +func Perm(n int) []int +func Symbols(n int) string +``` + +### 字符列表 + +``` +字符类型 字符列表 +数字字符 0123456789 +英文字符 abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ +特殊字符 !\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ +``` + +### 随机整数 + +1. `Intn` 方法返回大于等 `0` 且不大于 `max` 的随机整数,即: `[0, max)`。 +2. `N` 方法返回 `min` 到 `max` 之间的随机整数,支持负数,包含边界,即: `[min, max]`。 + +### 随机字符串 + +1. `B` 方法用于返回指定长度的二进制 `[]byte` 数据。 +2. `S` 方法用于返回指定长度的数字、字符,第二个参数 `symbols` 用于指定知否返回的随机字符串中包含特殊字符。 +3. `Str` 方法是一个比较高级的方法,用于从给定的字符列表中选择指定长度的随机字符串返回,并且支持 `unicode` 字符,例如中文。例如, `Str("中文123abc", 3)` 将可能会返回 `1a文` 的随机字符串。 +4. `Digits` 方法用于返回指定长度的随机数字字符串。 +5. `Letters` 方法用于返回指定长度的随机英文字符串。 +6. `Symbols` 方法用于返回指定长度的随机特殊字符串。 + +### 概率性计算 + +1. `Meet` 用于指定一个数 `num` 和总数 `total`,往往 `num<=total`,并随机计算是否满足 `num/total` 的概率。例如, `Meet(1, 100)` 将会随机计算是否满足百分之一的概率。 +2. `MeetProb` 用于给定一个概率浮点数 `prob`,往往 `prob<=1.0`,并随机计算是否满足该概率。例如, `MeetProb(0.005)` 将会随机计算是否满足千分之五的概率。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" new file mode 100644 index 00000000000..2bba29fac70 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/Redis\345\256\242\346\210\267\347\253\257-gredis.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/database-gredis' +title: 'Redis客户端-gredis' +sidebar_position: 1 +hide_title: true +description: 'Redis客户端-gredis模块旨在通过GoFrame框架提供高效的数据库缓存操作,用户可以探索如何在GoFrame框架下优化Redis相关应用,实现高性能的Redis功能。' +keywords: [Redis客户端,gredis,GoFrame,数据库缓存,NoSQL Redis,Redis功能,缓存操作,高效,模块,应用优化] +--- + +`Redis` 的功能由 `gredis` 模块实现,具体请参考 [NoSQL Redis](../NoSQL%20Redis/NoSQL%20Redis.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" new file mode 100644 index 00000000000..e678b5a1d79 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\345\272\223ORM-gdb.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/database-gdb' +title: '数据库ORM-gdb' +sidebar_position: 0 +hide_title: true +description: 'GoFrame框架中的gdb模块,该模块是实现数据库ORM功能的核心组件,负责高效的数据操作与管理。在GoFrame框架中,gdb扮演着至关重要的角色,有助于简化数据库的交互和管理。' +keywords: [GoFrame,GoFrame框架,数据库,ORM,gdb模块,数据交互,数据管理,数据库操作,核心组件,gdb] +--- + +数据库 `ORM` 功能由 `gdb` 模块实现,具体请参考 [数据库ORM🔥](../../核心组件/数据库ORM/数据库ORM.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..84f9236400a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\256\241\347\220\206/\346\225\260\346\215\256\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/database' +title: '数据管理' +sidebar_position: 4 +hide_title: true +keywords: [数据管理,数据库组件,数据库操作,数据存储,数据查询,数据分析,数据同步,GoFrame,GoFrame框架,数据安全] +description: '在GoFrame框架中进行数据管理,通过数据库组件实现数据的存储、查询和分析,并确保数据的安全性和可靠性。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" new file mode 100644 index 00000000000..54a8f9afa30 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-gmap.md" @@ -0,0 +1,40 @@ +--- +slug: '/docs/components/container-gmap' +title: '字典类型-gmap' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gmap,map容器,HashMap,TreeMap,ListMap,并发安全,数据结构,关联数组] +description: '在GoFrame框架中使用gmap字典类型的基本方法和注意事项。gmap模块提供了多种并发安全的map数据结构选项,包括HashMap、TreeMap和ListMap。适用于在Go应用程序中任何涉及并发访问和哈希表操作的场景,并详细描述了每种类型的性能和特性。' +--- + +## 基本介绍 + +支持并发安全开关选项的 `map` 容器,最常用的数据结构。该模块包含多个数据结构的 `map` 容器: `HashMap`、 `TreeMap` 和 `ListMap`。 + +| 类型 | 数据结构 | 平均复杂度 | 支持排序 | 有序遍历 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `HashMap` | 哈希表 | `O(1)` | 否 | 否 | 高性能读写操作,内存占用较高,随机遍历 | +| `ListMap` | 哈希表+双向链表 | `O(2)` | 否 | 是 | 支持按照写入顺序遍历,内存占用较高 | +| `TreeMap` | 红黑树 | `O(log N)` | 是 | 是 | 内存占用紧凑,支持键名排序及有序遍历 | +:::tip +此外, `gmap` 模块支持多种以哈希表为基础数据结构的常见类型 `map` 定义: `IntIntMap`、 `IntStrMap`、 `IntAnyMap`、 `StrIntMap`、 `StrStrMap`、 `StrAnyMap`。 +::: +**使用场景**: + +任何 `map`/哈希表/关联数组使用场景,尤其是并发安全场景中。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gmap" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..d2fac81aad0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,392 @@ +--- +slug: '/docs/components/container-gmap-example' +title: '字典类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gmap,并发安全,数据结构,键值对操作,有序遍历,JSON序列化,字典类型,开关参数] +description: 'GoFrame框架下gmap模块的基本使用方法,包括并发安全特性的开关操作,键值对的设置、查询和删除,以及数据结构的有序遍历、序列化与反序列化等内容,并提供了详细的代码示例与执行结果。' +--- + +## 并发安全 + +`gmap` 支持并发安全选项开关,在默认情况下是 `非并发安全` 的,开发者可以选择开启 `gmap` 的并发安全特性(传递初始化开关参数 `safe` 参数值为 `true`, 必须在初始化时设定,不能运行时动态设定)。如: + +```go +m := gmap.New(true) +``` + +不仅仅是 `gmap` 模块, `goframe` 框架的其他并发安全数据结构也支持并发安全特性开关。 + +## 使用示例 + +### 基本使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + // 创建一个默认的gmap对象, + // 默认情况下该gmap对象不支持并发安全特性, + // 初始化时可以给定true参数开启并发安全特性。 + m := gmap.New() + + // 设置键值对 + for i := 0; i < 10; i++ { + m.Set(i, i) + } + // 查询大小 + fmt.Println(m.Size()) + // 批量设置键值对(不同的数据类型对象参数不同) + m.Sets(map[interface{}]interface{}{ + 10 : 10, + 11 : 11, + }) + fmt.Println(m.Size()) + + // 查询是否存在 + fmt.Println(m.Contains(1)) + + // 查询键值 + fmt.Println(m.Get(1)) + + // 删除数据项 + m.Remove(9) + fmt.Println(m.Size()) + + // 批量删除 + m.Removes([]interface{}{10, 11}) + fmt.Println(m.Size()) + + // 当前键名列表(随机排序) + fmt.Println(m.Keys()) + // 当前键值列表(随机排序) + fmt.Println(m.Values()) + + // 查询键名,当键值不存在时,写入给定的默认值 + fmt.Println(m.GetOrSet(100, 100)) + + // 删除键值对,并返回对应的键值 + fmt.Println(m.Remove(100)) + + // 遍历map + m.Iterator(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + + // 自定义写锁操作 + m.LockFunc(func(m map[interface{}]interface{}) { + m[99] = 99 + }) + + // 自定义读锁操作 + m.RLockFunc(func(m map[interface{}]interface{}) { + fmt.Println(m[99]) + }) + + // 清空map + m.Clear() + + // 判断map是否为空 + fmt.Println(m.IsEmpty()) +} +``` + +执行后,输出结果为: + +```10 +12 +true +1 +11 +9 +[0 1 2 4 6 7 3 5 8] +[3 5 8 0 1 2 4 6 7] +100 +100 +3:3 5:5 8:8 7:7 0:0 1:1 2:2 4:4 6:6 99 +true +``` + +### 有序遍历 + +我们来看一下三种不同类型 `map` 的有序性遍历示例。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + array := g.Slice{2, 3, 1, 5, 4, 6, 8, 7, 9} + hashMap := gmap.New(true) + listMap := gmap.NewListMap(true) + treeMap := gmap.NewTreeMap(gutil.ComparatorInt, true) + for _, v := range array { + hashMap.Set(v, v) + } + for _, v := range array { + listMap.Set(v, v) + } + for _, v := range array { + treeMap.Set(v, v) + } + fmt.Println("HashMap Keys:", hashMap.Keys()) + fmt.Println("HashMap Values:", hashMap.Values()) + fmt.Println("ListMap Keys:", listMap.Keys()) + fmt.Println("ListMap Values:", listMap.Values()) + fmt.Println("TreeMap Keys:", treeMap.Keys()) + fmt.Println("TreeMap Values:", treeMap.Values()) +} +``` + +执行后,输出结果为: + +``` +HashMap Keys: [4 6 8 7 9 2 3 1 5] +HashMap Values: [6 8 4 3 1 5 7 9 2] +ListMap Keys: [2 3 1 5 4 6 8 7 9] +ListMap Values: [2 3 1 5 4 6 8 7 9] +TreeMap Keys: [1 2 3 4 5 6 7 8 9] +TreeMap Values: [1 2 3 4 5 6 7 8 9] +``` + +### `FilterEmpty/FilterNil` 空值过滤 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + m1 := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m2 := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m1.FilterEmpty() + m2.FilterNil() + fmt.Println(m1.Map()) + fmt.Println(m2.Map()) + + // Output: + // map[k4:1] + // map[k1: k3:0 k4:1] +} +``` + +### `Flip` 键值对反转 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + }) + m.Flip() + fmt.Println(m.Map()) + + // May Output: + // map[v1:k1 v2:k2] +} +``` + +### `Keys/Values` 键名/数值列表 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Keys()) + fmt.Println(m.Values()) + + // May Output: + // [k1 k2 k3 k4] + // [v2 v3 v4 v1] +} +``` + +### `Pop/Pops` 随机出栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pop()) + fmt.Println(m.Pops(2)) + fmt.Println(m.Size()) + + // May Output: + // k1 v1 + // map[k2:v2 k4:v4] + // 1 +} +``` + +### `SetIfNotExist*` 判断性写入 + +判断性写入是指当指定的键名不存在时则写入并且方法返回 `true`,否则忽略写入并且方法返回 `false`。相关方法如下: + +- `SetIfNotExist` +- `SetIfNotExistFunc` +- `SetIfNotExistFuncLock` + +方法具体描述请查看接口文档或源码注释。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + var m gmap.Map + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + +### `Merge` 字典表合并 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + var m1, m2 gmap.Map + m1.Set("key1", "val1") + m2.Set("key2", "val2") + m1.Merge(&m2) + fmt.Println(m1.Map()) + + // May Output: + // map[key1:val1 key2:val2] +} +``` + +### `JSON` 序列化/反序列 + +`gmap` 模块下的所有容器类型均实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +1\. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + m := gmap.New() + m.Sets(g.MapAnyAny{ + "name": "john", + "score": 100, + }) + b, _ := json.Marshal(m) + fmt.Println(string(b)) +} +``` + +执行后,输出结果: + +``` +{"name":"john","score":100} +``` + +2. `Unmarshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gmap" +) + +func main() { + m := gmap.Map{} + s := []byte(`{"name":"john","score":100}`) + json.Unmarshal(s, &m) + fmt.Println(m.Map()) +} +``` + +执行后,输出结果: + +``` +map[name:john score:100] +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..d3562e66578 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,88 @@ +--- +slug: '/docs/components/container-gmap-benchmark' +title: '字典类型-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,字典类型,性能测试,并发安全,非并发安全,map性能,gmap,sync.Map,基准测试] +description: '针对字典类型的性能进行详细测试和分析。通过对GoFrame框架中的gmap与标准库sync.Map的性能比较,揭示在并发安全与非并发安全不同场景下的效率表现。包括不同类型map的性能基准测试,如HashMap、ListMap和TreeMap,提供开发者优化应用程序的实时参考。' +--- + +## 性能测试 + +### 并发安全 + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_safe\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_safe_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_IntIntMap_Set-4 10000000 202 ns/op 15 B/op 0 allocs/op +Benchmark_IntAnyMap_Set-4 10000000 262 ns/op 29 B/op 1 allocs/op +Benchmark_IntStrMap_Set-4 10000000 241 ns/op 22 B/op 0 allocs/op +Benchmark_AnyAnyMap_Set-4 5000000 359 ns/op 40 B/op 2 allocs/op +Benchmark_StrIntMap_Set-4 5000000 305 ns/op 26 B/op 1 allocs/op +Benchmark_StrAnyMap_Set-4 5000000 354 ns/op 40 B/op 2 allocs/op +Benchmark_StrStrMap_Set-4 5000000 338 ns/op 32 B/op 1 allocs/op +Benchmark_IntIntMap_Get-4 20000000 86.6 ns/op 0 B/op 0 allocs/op +Benchmark_IntAnyMap_Get-4 30000000 69.7 ns/op 0 B/op 0 allocs/op +Benchmark_IntStrMap_Get-4 30000000 69.6 ns/op 0 B/op 0 allocs/op +Benchmark_AnyAnyMap_Get-4 20000000 74.4 ns/op 0 B/op 0 allocs/op +Benchmark_StrIntMap_Get-4 20000000 116 ns/op 7 B/op 0 allocs/op +Benchmark_StrAnyMap_Get-4 20000000 92.3 ns/op 7 B/op 0 allocs/op +Benchmark_StrStrMap_Get-4 20000000 91.9 ns/op 7 B/op 0 allocs/op +``` + +### 非并发安全 + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_unsafe\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_unsafe_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_Unsafe_IntIntMap_Set-4 10000000 318 ns/op 62 B/op 0 allocs/op +Benchmark_Unsafe_IntAnyMap_Set-4 5000000 282 ns/op 57 B/op 1 allocs/op +Benchmark_Unsafe_IntStrMap_Set-4 5000000 332 ns/op 82 B/op 1 allocs/op +Benchmark_Unsafe_AnyAnyMap_Set-4 3000000 471 ns/op 73 B/op 2 allocs/op +Benchmark_Unsafe_StrIntMap_Set-4 5000000 429 ns/op 82 B/op 1 allocs/op +Benchmark_Unsafe_StrAnyMap_Set-4 3000000 424 ns/op 73 B/op 2 allocs/op +Benchmark_Unsafe_StrStrMap_Set-4 2000000 515 ns/op 96 B/op 2 allocs/op +Benchmark_Unsafe_IntIntMap_Get-4 10000000 133 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntAnyMap_Get-4 20000000 134 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntStrMap_Get-4 10000000 126 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnyAnyMap_Get-4 10000000 166 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_StrIntMap_Get-4 5000000 246 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrAnyMap_Get-4 10000000 238 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrStrMap_Get-4 5000000 229 ns/op 7 B/op 0 allocs/op +``` + +### 不同类型map性能 + +[https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_maps\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_maps_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_HashMap_Set-4 5000000 349 ns/op 40 B/op 2 allocs/op +Benchmark_ListMap_Set-4 3000000 455 ns/op 87 B/op 3 allocs/op +Benchmark_TreeMap_Set-4 3000000 481 ns/op 28 B/op 2 allocs/op +Benchmark_HashMap_Get-4 30000000 67.8 ns/op 0 B/op 0 allocs/op +Benchmark_ListMap_Get-4 20000000 74.5 ns/op 0 B/op 0 allocs/op +Benchmark_TreeMap_Get-4 20000000 189 ns/op 8 B/op 1 allocs/op +``` + +### `gmap` 与 `sync.Map` 性能比较 + +go语言从 `1.9` 版本开始引入了并发安全的 `sync.Map`,但 `gmap` 比较于标准库的 `sync.Map` 性能更加优异,并且功能更加丰富。 + +我们来看看基准测试对比结果: [https://github.com/gogf/gf/blob/master/container/gmap/gmap\_z\_bench\_syncmap\_test.go](https://github.com/gogf/gf/blob/master/container/gmap/gmap_z_bench_syncmap_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_GMapSet-4 10000000 209 ns/op 15 B/op 0 allocs/op +Benchmark_SyncMapSet-4 3000000 451 ns/op 67 B/op 3 allocs/op +Benchmark_GMapGet-4 30000000 66.4 ns/op 0 B/op 0 allocs/op +Benchmark_SyncMapGet-4 30000000 36.0 ns/op 0 B/op 0 allocs/op +Benchmark_GMapRemove-4 10000000 207 ns/op 0 B/op 0 allocs/op +Benchmark_SyncMapRmove-4 30000000 42.4 ns/op 0 B/op 0 allocs/op +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..3919569ac49 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\255\227\345\205\270\347\261\273\345\236\213-gmap/\345\255\227\345\205\270\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1370 @@ +--- +slug: '/docs/components/container-gmap-funcs' +title: '字典类型-方法介绍' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,AnyAnyMap,gmap,map操作,并发安全,map方法,GoFrame文档,map迭代,map复制] +description: 'GoFrame框架中AnyAnyMap的各种方法,包括创建、克隆、迭代、设置、删除和合并等操作。同时,还提供了代码示例以帮助理解这些方法的使用方式确保代码的并发安全性,更详细内容可参考GoFrame框架文档。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) +::: +## `New` + +- 说明: `New` 创建并返回一个空的 `AnyAnyMap`。参数 `safe` 用于指定是否使用并发安全的 `map`,默认情况下为 `false`。 + +- 格式: + +```go +New(safe ...bool) *Map +``` + +- 示例: + +```go +func ExampleNew() { + m := gmap.New() + + // Add data. + m.Set("key1", "val1") + + // Print size. + fmt.Println(m.Size()) + + addMap := make(map[interface{}]interface{}) + addMap["key2"] = "val2" + addMap["key3"] = "val3" + addMap[1] = 1 + + fmt.Println(m.Values()) + + // Batch add data. + m.Sets(addMap) + + // Gets the value of the corresponding key. + fmt.Println(m.Get("key3")) + + // Get the value by key, or set it with given key-value if not exist. + fmt.Println(m.GetOrSet("key4", "val4")) + + // Set key-value if the key does not exist, then return true; or else return false. + fmt.Println(m.SetIfNotExist("key3", "val3")) + + // Remove key + m.Remove("key2") + fmt.Println(m.Keys()) + + // Batch remove keys. + m.Removes([]interface{}{"key1", 1}) + fmt.Println(m.Keys()) + + // Contains checks whether a key exists. + fmt.Println(m.Contains("key3")) + + // Flip exchanges key-value of the map, it will change key-value to value-key. + m.Flip() + fmt.Println(m.Map()) + + // Clear deletes all data of the map. + m.Clear() + + fmt.Println(m.Size()) + + // May Output: + // 1 + // [val1] + // val3 + // val4 + // false + // [key4 key1 key3 1] + // [key4 key3] + // true + // map[val3:key3 val4:key4] + // 0 +} +``` + + +## `NewFrom` + +- 说明: `NewFrom` 使用给定 `map` 的数据创建并返回 `AnyAnyMap`。 + +- 注意:入参 `map` 将被设置为底层数据映射(无深度拷贝),在外部更改 `map` 时,可能会同时出现一些安全问题。可选参数 `safe` 指定是否在并发安全中使用此结构,默认情况下为 `false`。 + +- 格式: + +```go +NewFrom(data map[interface{}]interface{}, safe ...bool) *Map +``` + +- 示例: + +```go +func ExampleNewFrom() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + n := gmap.NewFrom(m.MapCopy(), true) + fmt.Println(n) + + // Output: + // {"key1":"val1"} + // {"key1":"val1"} +} +``` + + +## `Iterator` + +- 说明: `Iterator` 使用自定义回调函数 `f` 以只读方式迭代 `hashmap`。如果 `f` 返回 `true`,则继续迭代,返回 `false` 则停止。 + +- 格式: + +```go +Iterator(f func(k interface{}, v interface{}) bool) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Iterator() { + m := gmap.New() + for i := 0; i < 10; i++ { + m.Set(i, i*2) + } + + var totalKey, totalValue int + m.Iterator(func(k interface{}, v interface{}) bool { + totalKey += k.(int) + totalValue += v.(int) + + return totalKey < 10 + }) + + fmt.Println("totalKey:", totalKey) + fmt.Println("totalValue:", totalValue) + + // May Output: + // totalKey: 11 + // totalValue: 22 +} +``` + + +## `Clone` + +- 说明: `Clone` 返回一个新的 `AnyAnyMap`,其中包含当前 `map` 数据的副本。 + +- 格式: + +```go +Clone(safe ...bool) *AnyAnyMap +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Clone() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + n := m.Clone() + fmt.Println(n) + + // Output: + // {"key1":"val1"} + // {"key1":"val1"} +} +``` + + +## `Map` + +- 说明: `Map` 返回底层数据 `map`。 + +- 注意:如果在并发安全中,它将返回底层数据的副本,否则返回指向底层数据的指针。 + +- 格式: + +```go +Map() map[interface{}]interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Map() { + // non concurrent-safety, a pointer to the underlying data + m1 := gmap.New() + m1.Set("key1", "val1") + fmt.Println("m1:", m1) + + n1 := m1.Map() + fmt.Println("before n1:", n1) + m1.Set("key1", "val2") + fmt.Println("after n1:", n1) + + // concurrent-safety, copy of underlying data + m2 := gmap.New(true) + m2.Set("key1", "val1") + fmt.Println("m1:", m2) + + n2 := m2.Map() + fmt.Println("before n2:", n2) + m2.Set("key1", "val2") + fmt.Println("after n2:", n2) + + // Output: + // m1: {"key1":"val1"} + // before n1: map[key1:val1] + // after n1: map[key1:val2] + // m1: {"key1":"val1"} + // before n2: map[key1:val1] + // after n2: map[key1:val1] +} +``` + + +## `MapCopy` + +- 说明: `MapCopy` 返回 `map` 的数据的副本。 + +- 格式: + +```go +MapCopy() map[interface{}]interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_MapCopy() { + m := gmap.New() + + m.Set("key1", "val1") + m.Set("key2", "val2") + fmt.Println(m) + + n := m.MapCopy() + fmt.Println(n) + + // Output: + // {"key1":"val1","key2":"val2"} + // map[key1:val1 key2:val2] +} +``` + + +## MapStrAny + +- 说明: `MapStrAny` 以 `map[string]interface{}` 的形式返回 `map` 的数据的副本。 + +- 格式: + +```go +MapStrAny() map[string]interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_MapStrAny() { + m := gmap.New() + m.Set(1001, "val1") + m.Set(1002, "val2") + + n := m.MapStrAny() + fmt.Println(n) + + // Output: + // map[1001:val1 1002:val2] +} +``` + + +## `FilterEmpty` + +- 说明: `FilterEmpty` 删除值为空的所有键值对。如: `0`, `nil`, `false`, `""`, `len(slice/map/chan) == 0` 这样的值被认为是空的。 + +- 格式: + +```go +FilterEmpty() +``` + +- 示例: + +```go +func ExampleAnyAnyMap_FilterEmpty() { + m := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m.FilterEmpty() + fmt.Println(m.Map()) + + // Output: + // map[k4:1] +} +``` + + +## FilterNil + +- 说明: `FilterNil` 删除其值为 `nil` 的所有键值对。 + +- 格式: + +```go +FilterNil() +``` + +- 示例: + +```go +func ExampleAnyAnyMap_FilterNil() { + m := gmap.NewFrom(g.MapAnyAny{ + "k1": "", + "k2": nil, + "k3": 0, + "k4": 1, + }) + m.FilterNil() + fmt.Println(m.Map()) + + // May Output: + // map[k1: k3:0 k4:1] +} +``` + + +## Set + +- 说明: `Set` 为 `map` 设置 `key/value`。 + +- 格式: + +```go +Set(key interface{}, value interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Set() { + m := gmap.New() + + m.Set("key1", "val1") + fmt.Println(m) + + // Output: + // {"key1":"val1"} +} +``` + + +## Sets + +- 说明: `Sets` 为 `map` 批量设置 `key/` `value`。 + +- 格式: + +```go +Sets(data map[interface{}]interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Sets() { + m := gmap.New() + + addMap := make(map[interface{}]interface{}) + addMap["key1"] = "val1" + addMap["key2"] = "val2" + addMap["key3"] = "val3" + + m.Sets(addMap) + fmt.Println(m) + + // Output: + // {"key1":"val1","key2":"val2","key3":"val3"} +} +``` + + +## `Search` + +- 说明: `Search` 使用参数 `key` 搜索 `map`。如果找到 `key`,则返回其对应的键值,并且返回参数 `found` 为 `true`,否则为 `false`。 + +- 格式: + +```go +Search(key interface{}) (value interface{}, found bool) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Search() { + m := gmap.New() + + m.Set("key1", "val1") + + value, found := m.Search("key1") + if found { + fmt.Println("find key1 value:", value) + } + + value, found = m.Search("key2") + if !found { + fmt.Println("key2 not find") + } + + // Output: + // find key1 value: val1 + // key2 not find +} + + +``` + + +## `Get` + +- 说明: `Get` 返回参数 `key` 对应的值 `value`,如 `key` 不存在,则返回 `Nil`。 + +- 格式: + +```go +Get(key interface{}) (value interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Get() { + m := gmap.New() + + m.Set("key1", "val1") + + fmt.Println("key1 value:", m.Get("key1")) + fmt.Println("key2 value:", m.Get("key2")) + + // Output: + // key1 value: val1 + // key2 value: +} +``` + + +## `Pop` + +- 说明: `Pop` 从 `map` 中随机取出返回一个键值对,并在内部删除该键值对。 + +- 格式: + +```go +Pop() (key, value interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Pop() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + fmt.Println(m.Pop()) + + // May Output: + // k1 v1 +} +``` + + +## Pops + +- 说明: `Pops` 从 `map` 中随机取出并删除 `size` 个键值对。如果 `size == -1`,则删除并返回所有键值对。 + +- 格式: + +```go +Pops(size int) map[interface{}]interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Pops() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pops(-1)) + fmt.Println("size:", m.Size()) + + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Pops(2)) + fmt.Println("size:", m.Size()) + + // May Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] + // size: 0 + // map[k1:v1 k2:v2] + // size: 2 +} +``` + + +## GetOrSet + +- 说明: `GetOrSet` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `value` 给 `map` 设置键值,然后返回该值。 + +- 格式: + +```go +GetOrSet(key interface{}, value interface{}) interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetOrSet() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSet("key1", "NotExistValue")) + fmt.Println(m.GetOrSet("key2", "val2")) + + // Output: + // val1 + // val2 +} +``` + + +## `GetOrSetFunc` + +- 说明: `GetOrSetFunc` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `func f` 的返回值 给 `map` 设置键值,然后返回该值。 + +- 格式: + +```go +GetOrSetFunc(key interface{}, f func() interface{}) interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetOrSetFunc() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSetFunc("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetOrSetFunc("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetOrSetFuncLock + +- 说明: `GetOrSetFunc` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `func f` 的返回值 给 `map` 设置键值,然后返回该值。 + +- 注意: `GetOrSetFuncLock` 与 `GetOrSetFunc` 函数的不同之处在于它在写锁中执行函数 `f`。 + +- 格式: + +```go +GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetOrSetFuncLock() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetOrSetFuncLock("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetOrSetFuncLock("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetVar + +- 说明: `GetVar` 根据键名 `key` 查询并返回对应的键值,键值使用泛型类型 `*gvar.Var` 返回。 + +- 格式: + +```go +GetVar(key interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetVar() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVar("key1")) + fmt.Println(m.GetVar("key2").IsNil()) + + // Output: + // val1 + // true +} +``` + + +## `GetVarOrSet` + +- 说明: `GetVarOrSet` 根据键名 `key` 查询并返回对应的键值。当对应的键值不存在时,使用 `value` 设置该键值,并返回查询/设置的键值。键值使用泛型类型 `*gvar.Var` 返回。 + +- 格式: + +```go +GetVarOrSet(key interface{}, value interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetVarOrSet() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSet("key1", "NotExistValue")) + fmt.Println(m.GetVarOrSet("key2", "val2")) + + // Output: + // val1 + // val2 +} +``` + + +## GetVarOrSetFunc + +- 说明: `GetVarOrSetFunc` 根据键名 `key` 查询并返回对应的键值。当对应的键值不存在时,使用 `func f` 的返回值设置该键值,并返回查询/设置的键值。键值使用泛型类型 `*gvar.Var` 返回。 + +- 格式: + +```go +GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetVarOrSetFunc() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSetFunc("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetVarOrSetFunc("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## GetVarOrSetFuncLock + +- 说明: `GetVarOrSetFuncLock` 根据键名 `key` 查询并返回对应的键值。当对应的键值不存在时,使用 `func f` 的返回值设置该键值,并返回查询/设置的键值。键值使用泛型类型 `*gvar.Var` 返回。 + +- 注意: `GetVarOrSetFuncLock` 与 `GetVarOrSetFunc` 函数的不同之处在于它在写锁中执行函数 `f`。即当有多个 `goroutine` 同时调用该方法时,函数 `f` 将会在执行之前被阻塞。 +- 格式: + +```go +GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleAnyAnyMap_GetVarOrSetFuncLock() { + m := gmap.New() + m.Set("key1", "val1") + + fmt.Println(m.GetVarOrSetFuncLock("key1", func() interface{} { + return "NotExistValue" + })) + fmt.Println(m.GetVarOrSetFuncLock("key2", func() interface{} { + return "NotExistValue" + })) + + // Output: + // val1 + // NotExistValue +} +``` + + +## `SetIfNotExist` + +- 说明:如果 `key` 不存在,则 `SetIfNotExist` 为 `map` 设置值键值对 `key/value`,并且返回 `true`。如果 `key` 存在,则返回 `false`,而 `value` 将被忽略。 + +- 格式: + +```go +SetIfNotExist(key interface{}, value interface{}) bool +``` + +- 示例: + +```go +func ExampleAnyAnyMap_SetIfNotExist() { + var m gmap.Map + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.SetIfNotExist("k1", "v1")) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## SetIfNotExistFunc + +- 说明:如果 `key` 不存在,则 `SetIfNotExistFunc` 为 `map` 设置值为函数 `f` 的返回值,并且返回 `true`。如果 `key` 存在,则返回 `false`,并且 `value` 将被忽略。 + +- 格式: + +```go +SetIfNotExistFunc(key interface{}, f func() interface{}) bool +``` + +- 示例: + +```go +func ExampleAnyAnyMap_SetIfNotExistFunc() { + var m gmap.Map + fmt.Println(m.SetIfNotExistFunc("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.SetIfNotExistFunc("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## `SetIfNotExistFuncLock` + +- 说明:如果 `key` 不存在,则 `SetIfNotExistFunc` 为 `map` 设置值为 `func c` 的返回值,然后返回 `true`。如果 `key` 存在,则返回 `false`,而 `value` 将被忽略。 + +- 注意: `SetIfNotExistFuncLock` 与 `SetIfNotExistFunc` 函数的不同之处在于它在 `mutex.Lock` 中执行函数 `f`。 + +- 格式: + +```go +SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool +``` + +- 示例: + +```go +func ExampleAnyAnyMap_SetIfNotExistFuncLock() { + var m gmap.Map + fmt.Println(m.SetIfNotExistFuncLock("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.SetIfNotExistFuncLock("k1", func() interface{} { + return "v1" + })) + fmt.Println(m.Map()) + + // Output: + // true + // false + // map[k1:v1] +} +``` + + +## Remove + +- 说明:按给定的 `key` 从 `map` 中删除 `value`,并返回此删除的 `value`。 + +- 格式: + +```go +Remove(key interface{}) (value interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Remove() { + var m gmap.Map + m.Set("k1", "v1") + + fmt.Println(m.Remove("k1")) + fmt.Println(m.Remove("k2")) + + // Output: + // v1 + // +} +``` + + +## Removes + +- 说明: `Removes` 按给定的 `key` 批量删除 `map` 的 `value`。 + +- 格式: + +```go +Removes(keys []interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Removes() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + removeList := make([]interface{}, 2) + removeList = append(removeList, "k1") + removeList = append(removeList, "k2") + + m.Removes(removeList) + + fmt.Println(m.Map()) + + // Output: + // map[k3:v3 k4:v4] +} +``` + + +## Keys + +- 说明: `Keys` 将 `map` 的所有 `key` 作为 `slice` 返回。 + +- 格式: + +```go +Keys() []interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Keys() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Keys()) + + // Output: + // [k1 k2 k3 k4] +} +``` + + +## Values + +- 说明: `Values` 将 `map` 的所有 `value` 作为 `slice` 返回。 + +- 格式: + +```go +Values() []interface{} +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Values() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Values()) + + // May Output: + // [v1 v2 v3 v4] +} +``` + + +## Contains + +- 说明: `Contains` 检查 `key` 是否存在。如果 `key` 存在,则返回 `true`,否则返回 `false`。 + +- 注意:键名类型为 `interface{}`,因此匹配判断需要保证类型和数值一致。 +- 格式: + +```go +Contains(key interface{}) bool +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Contains() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + fmt.Println(m.Contains("k1")) + fmt.Println(m.Contains("k5")) + + // Output: + // true + // false +} +``` + + +## `Size` + +- 说明: `Size` 返回 `map` 的大小。 + +- 格式: + +```go +Size() int +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Size() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + fmt.Println(m.Size()) + + // Output: + // 4 +} +``` + + +## `IsEmpty` + +- 说明: `IsEmpty` 检查 `map` 是否为空。如果 `map` 为空,则返回 `true`,否则返回 `false`。 + +- 格式: + +```go +IsEmpty() bool +``` + +- 示例: + +```go +func ExampleAnyAnyMap_IsEmpty() { + var m gmap.Map + fmt.Println(m.IsEmpty()) + + m.Set("k1", "v1") + fmt.Println(m.IsEmpty()) + + // Output: + // true + // false +} +``` + + +## Clear + +- 说明: `Clear` 删除 `map` 的所有数据。 + +- 格式: + +```go +Clear() +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Clear() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + m.Clear() + + fmt.Println(m.Map()) + + // Output: + // map[] +} +``` + + +## `Replace` + +- 说明: `Replace` 用给定的 `data` 完整替换 `map` 的 `value`。 + +- 格式: + +```go +Replace(data map[interface{}]interface{}) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Replace() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + + var n gmap.Map + n.Sets(g.MapAnyAny{ + "k2": "v2", + }) + + fmt.Println(m.Map()) + + m.Replace(n.Map()) + fmt.Println(m.Map()) + + n.Set("k2", "v1") + fmt.Println(m.Map()) + + // Output: + // map[k1:v1] + // map[k2:v2] + // map[k2:v1] +} +``` + + +## `LockFunc` + +- 说明: `LockFunc` 在写锁中执行函数 `f`。 + +- 格式: + +```go +LockFunc(f func(m map[interface{}]interface{})) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_LockFunc() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": 1, + "k2": 2, + "k3": 3, + "k4": 4, + }) + + m.LockFunc(func(m map[interface{}]interface{}) { + totalValue := 0 + for _, v := range m { + totalValue += v.(int) + } + fmt.Println("totalValue:", totalValue) + }) + + // Output: + // totalValue: 10 +} +``` + + +## `RLockFunc` + +- 说明: `RLockFunc` 在读锁中执行函数 `f`。 + +- 格式: + +```go +RLockFunc(f func(m map[interface{}]interface{})) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_RLockFunc() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": 1, + "k2": 2, + "k3": 3, + "k4": 4, + }) + + m.RLockFunc(func(m map[interface{}]interface{}) { + totalValue := 0 + for _, v := range m { + totalValue += v.(int) + } + fmt.Println("totalValue:", totalValue) + }) + + // Output: + // totalValue: 10 +} +``` + + +## `Flip` + +- 说明: `Flip` 将 `map` 的 `key` 与 `value` 进行交换。 + +- 格式: + +```go +Flip() +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Flip() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + m.Flip() + fmt.Println(m.Map()) + + // Output: + // map[v1:k1] +} +``` + + +## `Merge` + +- 说明: `Merge` 合并两个AnyAnyMap。入参 `map` 将合并到原 `map` 中。 + +- 格式: + +```go +Merge(other *AnyAnyMap) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_Merge() { + var m1, m2 gmap.Map + m1.Set("key1", "val1") + m2.Set("key2", "val2") + m1.Merge(&m2) + fmt.Println(m1.Map()) + + // May Output: + // map[key1:val1 key2:val2] +} +``` + + +## `String` + +- 说明: `String` 以字符串形式返回 `map`。 + +- 格式: + +```go +String() string +``` + +- 示例: + +```go +func ExampleAnyAnyMap_String() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + }) + + fmt.Println(m.String()) + + // Output: + // {"k1":"v1"} +} +``` + + +## `MarshalJSON` + +- 说明: `MarshalJSON` 实现 `json.Marshal` 的接口。 + +- 格式: + +```go +MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_MarshalJSON() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + bytes, err := m.MarshalJSON() + if err == nil { + fmt.Println(gconv.String(bytes)) + } + + // Output: + // {"k1":"v1","k2":"v2","k3":"v3","k4":"v4"} +} +``` + + +## `UnmarshalJSON` + +- 说明: `UnmarshalJSON` 实现了 `json.Unmarshal` 的接口。 + +- 格式: + +```go +UnmarshalJSON(b []byte) error +``` + +- 示例: + +```go +func ExampleAnyAnyMap_UnmarshalJSON() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + var n gmap.Map + + err := n.UnmarshalJSON(gconv.Bytes(m.String())) + if err == nil { + fmt.Println(n.Map()) + } + + // Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] +} +``` + + +## `UnmarshalValue` + +- 说明: `UnmarshalValue` 是一个接口实现,它通过任意类型的变量初始化当前 `map`。 + +- 格式: + +```go +UnmarshalValue(value interface{}) (err error) +``` + +- 示例: + +```go +func ExampleAnyAnyMap_UnmarshalValue() { + var m gmap.Map + m.Sets(g.MapAnyAny{ + "k1": "v1", + "k2": "v2", + "k3": "v3", + "k4": "v4", + }) + + var n gmap.Map + err := n.UnmarshalValue(m.String()) + if err == nil { + fmt.Println(n.Map()) + } + // Output: + // map[k1:v1 k2:v2 k3:v3 k4:v4] +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" new file mode 100644 index 00000000000..5298e55ed21 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-gtype.md" @@ -0,0 +1,36 @@ +--- +slug: '/docs/components/container-gtype' +title: '安全类型-gtype' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,安全类型,并发编程,并发安全,gtype,基本类型,atomic操作,性能优化,数据类型,锁机制] +description: 'GoFrame框架中的安全类型gtype,适用于任何需要并发安全的场景。通过提供对最常用基本数据类型的并发安全支持,gtype具备比互斥锁更高的性能,使用atomic原子操作简化并发控制,方便开发者在复杂场景下进行高效的并发编程。' +--- + +## 基本介绍 + +并发安全基本类型。 + +**使用场景**: + +`gtype` 使用得非常频繁,任何需要并发安全的场景下都适用。 + +在普通的并发安全场景中,一个基本类型的变量,特别是一个 `struct` 含有若干的属性,往往使用互斥(读写)锁或者多把(读写)锁来进行安全管理。 但这样的使用中, `变量/struct/属性` 的操作性能 **十分低下**,且由于互斥锁机制的存在往往使得操作变得相当复杂,必须小心翼翼地维护好 `变量/属性` 的并发安全控制(特别是 `(RW)Mutex`)。 + +`gtype` 针对于最常用的基本数据类型,提供了对应的并发安全数据类型,便于在并发安全场景下更好地维护变量/属性,开发者无需在 `struct` 中再创建和维护繁琐的 `(RW)Mutex`。由于 `gtype` 维护的是基本类型的并发安全,因此内部基本都使用了 `atomic` 原子操作来维护并发安全性,因此效率往往会比 `(RW)Mutex` 互斥锁高出数十倍。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gtype" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gtype](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtype) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..35ee296bdec --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/components/container-gtype-example' +title: '安全类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gtype,并发安全,JSON序列化,Go语言,基本使用,线程安全,容器类型,数据操作] +description: '在GoFrame框架中使用gtype模块实现并发安全的基本类型操作。通过示例代码展示了如何创建和操作线程安全的基本类型,如整数类型的增减操作,以及gtype容器类型的JSON序列化和反序列化功能,帮助开发者便捷地管理数据。' +--- + +`gtype` 并发安全基本类型的使用非常简单,往往就类似以下几个方法(以 `gtype.Int` 类型举例): + +```go +func NewInt(value ...int) *Int +func (v *Int) Add(delta int) (new int) +func (v *Int) Cas(old, new int) bool +func (v *Int) Clone() *Int +func (v *Int) Set(value int) (old int) +func (v *Int) String() string +func (v *Int) Val() int +``` + +## 基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gtype" + "fmt" +) + +func main() { + // 创建一个Int型的并发安全基本类型对象 + i := gtype.NewInt() + + // 设置值 + fmt.Println(i.Set(10)) + + // 获取值 + fmt.Println(i.Val()) + + // 数值-1,并返回修改之后的数值 + fmt.Println(i.Add(-1)) +} +``` + +执行后,输出结果为: + +```0 +10 +9 +``` + +## `JSON` 序列化/反序列 + +`gtype` 模块下的所有容器类型均实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +1、 `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gtype" +) + +func main() { + type Student struct { + Id *gtype.Int + Name *gtype.String + Scores *gtype.Interface + } + s := Student{ + Id: gtype.NewInt(1), + Name: gtype.NewString("john"), + Scores: gtype.NewInterface([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +执行后,输出结果: + +``` +{"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2、 `Unmarshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gtype" +) + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id *gtype.Int + Name *gtype.String + Scores *gtype.Interface + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +执行后,输出结果: + +``` +{1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..8a2c40e6e1d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\256\211\345\205\250\347\261\273\345\236\213-gtype/\345\256\211\345\205\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,47 @@ +--- +slug: '/docs/components/container-gtype-benchmark' +title: '安全类型-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,基准测试,性能测试,安全类型,并发编程,数据类型,Go语言,代码优化,测试结果] +description: '展示了GoFrame框架中gtype包的安全类型性能基准测试结果,通过详细测试数据对比,分析不同数据类型的方法执行效率,为Go语言的开发者提供参考,提高并发编程中的数据处理能力。' +--- + +基准测试结果如下: + +``` +john@john-B85M:~/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/container/gtype$ go test -bench=".*" -benchmem +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/gtype +BenchmarkInt_Set-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkInt_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkInt_Add-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Set-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Val-4 2000000000 0.47 ns/op 0 B/op 0 allocs/op +BenchmarkInt32_Add-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Set-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkInt64_Add-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Set-4 300000000 5.88 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkUint_Add-4 300000000 5.87 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Set-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Val-4 2000000000 0.50 ns/op 0 B/op 0 allocs/op +BenchmarkUint32_Add-4 200000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Set-4 300000000 5.86 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Val-4 2000000000 0.47 ns/op 0 B/op 0 allocs/op +BenchmarkUint64_Add-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkBool_Set-4 300000000 5.85 ns/op 0 B/op 0 allocs/op +BenchmarkBool_Val-4 2000000000 0.46 ns/op 0 B/op 0 allocs/op +BenchmarkString_Set-4 20000000 90.1 ns/op 23 B/op 1 allocs/op +BenchmarkString_Val-4 2000000000 1.58 ns/op 0 B/op 0 allocs/op +BenchmarkBytes_Set-4 20000000 76.2 ns/op 35 B/op 2 allocs/op +BenchmarkBytes_Val-4 2000000000 1.58 ns/op 0 B/op 0 allocs/op +BenchmarkInterface_Set-4 50000000 30.7 ns/op 8 B/op 0 allocs/op +BenchmarkInterface_Val-4 2000000000 0.74 ns/op 0 B/op 0 allocs/op +BenchmarkAtomicValue_Store-4 50000000 27.3 ns/op 8 B/op 0 allocs/op +BenchmarkAtomicValue_Load-4 2000000000 0.73 ns/op 0 B/op 0 allocs/op +PASS +ok github.com/gogf/gf/v2/container/gtype 49.454s +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" new file mode 100644 index 00000000000..85463f07c87 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-gpool.md" @@ -0,0 +1,43 @@ +--- +slug: '/docs/components/container-gpool' +title: '对象复用-gpool' +sidebar_position: 8 +hide_title: true +keywords: [对象复用,GoFrame框架,gpool,过期时间,创建方法,销毁方法,并发安全,sync.Pool,GC压力,缓存复用] +description: 'GoFrame框架中的对象复用池gpool的基本功能和使用场景,包括提供对象缓存复用、过期时间、创建和销毁方法的定义。gpool设计与sync.Pool的区别在于对过期时间的支持和GC压力缓解功能的不同,是并发安全的。' +--- + +## 基本介绍 + +对象复用池(并发安全)。将对象进行缓存复用,支持 `过期时间`、 `创建方法` 及 `销毁方法` 定义。 + +**使用场景**: + +任何需要支持定时过期的对象复用场景。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gpool" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gpool](https://pkg.go.dev/github.com/gogf/gf/v2/container/gpool) + +需要注意两点: + +1. `New` 方法的过期时间类型为 `time.Duration`。 +2. 对象 `创建方法`( `newFunc NewFunc`)返回值包含一个 `error` 返回,当对象创建失败时可由该返回值反馈原因。 +3. 对象 `销毁方法`( `expireFunc...ExpireFunc`)为可选参数,用以当对象超时/池关闭时,自动调用自定义的方法销毁对象。 + +## `gpool` 与 `sync.Pool` + +`gpool` 与 `sync.Pool` 都可以达到对象复用的作用,但是两者的设计初衷和使用场景不太一样。 + +`sync.Pool` 的对象生命周期不支持自定义过期时间,究其原因, `sync.Pool` 并不是一个 `Cache`; `sync.Pool` 设计初衷是为了缓解GC `压力`, `sync.Pool` 中的对象会在 `GC` 开始前全部清除;并且 `sync.Pool` 不支持对象创建方法及销毁方法。 + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..07d2f11c5d1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\345\257\271\350\261\241\345\244\215\347\224\250-gpool/\345\257\271\350\261\241\345\244\215\347\224\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,91 @@ +--- +slug: '/docs/components/container-gpool-example' +title: '对象复用-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [对象复用,GoFrame,GoFrame框架,gpool,对象池,Go语言,golang,网络连接,资源管理,编程教程] +description: '在GoFrame框架中使用gpool进行对象复用。在示例中,我们创建和操作了一个对象池,展示了对象的获取、返回及过期处理方法。此方法对于管理短生命周期的资源非常有效,可以显著提高程序性能和资源利用率。' +--- + +## 基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gpool" + "fmt" + "time" +) + +func main () { + // 创建一个对象池,过期时间为1秒 + p := gpool.New(time.Second, nil) + + // 从池中取一个对象,返回nil及错误信息 + fmt.Println(p.Get()) + + // 丢一个对象到池中 + p.Put(1) + + // 重新从池中取一个对象,返回1 + fmt.Println(p.Get()) + + // 等待2秒后重试,发现对象已过期,返回nil及错误信息 + time.Sleep(2*time.Second) + fmt.Println(p.Get()) +} +``` + +## 创建及销毁方法 + +我们可以给定动态创建及销毁方法。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gpool" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "time" +) + +func main() { + // 创建对象复用池,对象过期时间为3秒,并给定创建及销毁方法 + p := gpool.New(3*time.Second, func() (interface{}, error) { + return gtcp.NewConn("www.baidu.com:80") + }, func(i interface{}) { + glog.Println("expired") + i.(*gtcp.Conn).Close() + }) + conn, err := p.Get() + if err != nil { + panic(err) + } + result, err := conn.(*gtcp.Conn).SendRecv([]byte("HEAD / HTTP/1.1\n\n"), -1) + if err != nil { + panic(err) + } + fmt.Println(string(result)) + // 丢回池中以便重复使用 + p.Put(conn) + // 等待一定时间观察过期方法调用 + time.Sleep(4*time.Second) +} +``` + +执行后,终端输出结果: + +``` +HTTP/1.1 302 Found +Connection: Keep-Alive +Content-Length: 17931 +Content-Type: text/html +Date: Wed, 29 May 2019 11:23:20 GMT +Etag: "54d9749e-460b" +Server: bfe/1.0.8.18 + +2019-05-29 19:23:24.732 expired +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..a0fd0dfa547 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/container' +title: '数据结构' +sidebar_position: 0 +hide_title: true +keywords: [数据结构,GoFrame,GoFrame框架,容器组件,网页组件,开发指南,项目结构,代码结构,编程框架,技术文档] +description: '在GoFrame框架中如何创建和管理数据结构。通过容器组件,用户可以高效地组织和优化项目的代码结构。同时,我们提供了详尽的开发指南,帮助开发者更好地理解和使用GoFrame框架的强大功能。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" new file mode 100644 index 00000000000..bba62f81da1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-garray.md" @@ -0,0 +1,41 @@ +--- +slug: '/docs/components/container-garray' +title: '数组类型-garray' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,garray,数组容器,并发安全,排序数组,接口文档,数据类型,工具包,数据项唯一性] +description: 'GoFrame框架中的数组类型garray及其基本功能。通过garray模块,用户能够使用并发安全的数组容器,支持普通数组与排序数组,提供对数据项唯一性矫正、int/string/interface{}数据类型的支持及详细的接口文档等功能。' +--- + +## 基本介绍 + +数组容器,提供普通数组,及排序数组,支持数据项唯一性矫正,支持并发安全开关控制。 + +**使用场景**: + +数组操作。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/garray" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) + +简要说明: + +1. `garray` 模块下的对象及方法较多,建议仔细看看接口文档。 +2. `garray` 支持 `int`/ `string`/ `interface{}` 三种常用的数据类型。 +3. `garray` 支持普通数组和排序数组,普通数组的结构体名称定义为 `*Array` 格式,排序数组的结构体名称定义为 `Sorted*Array` 格式,如下: + - `Array`, `intArray`, `StrArray` + - `SortedArray`, `SortedIntArray`, `SortedStrArray` + - 其中排序数组 `SortedArray`,需要给定排序比较方法,在工具包 `gutil` 中也定义了很多 `Comparator*` 比较方法 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..8c6dbc61c38 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,489 @@ +--- +slug: '/docs/components/container-garray-example' +title: '数组类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,数组,Go语言,并发安全,排序数组,数组遍历,出栈,随机获取,包含判断,空值过滤] +description: '在 GoFrame 框架中使用数组类型,包括创建并发安全的数组、排序数组、数组遍历与修改以及数组的随机获取和出栈操作。通过示例代码演示了基本的数组操作,提供了数组的排序、过滤及翻转等高级用法,帮助开发者更好地理解和掌握 GoFrame 框架中的数组处理能力。' +--- + +### 普通数组 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main () { + // 创建并发安全的int类型数组 + a := garray.NewIntArray(true) + + // 添加数据项 + for i := 0; i < 10; i++ { + a.Append(i) + } + + // 获取当前数组长度 + fmt.Println(a.Len()) + + // 获取当前数据项列表 + fmt.Println(a.Slice()) + + // 获取指定索引项 + fmt.Println(a.Get(6)) + + // 在指定索引后插入数据项 + a.InsertAfter(9, 11) + // 在指定索引前插入数据项 + a.InsertBefore(10, 10) + fmt.Println(a.Slice()) + + // 修改指定索引的数据项 + a.Set(0, 100) + fmt.Println(a.Slice()) + + // 搜索数据项,返回搜索到的索引位置 + fmt.Println(a.Search(5)) + + // 删除指定索引的数据项 + a.Remove(0) + fmt.Println(a.Slice()) + + // 并发安全,写锁操作 + a.LockFunc(func(array []int) { + // 将末尾项改为100 + array[len(array) - 1] = 100 + }) + + // 并发安全,读锁操作 + a.RLockFunc(func(array []int) { + fmt.Println(array[len(array) - 1]) + }) + + // 清空数组 + fmt.Println(a.Slice()) + a.Clear() + fmt.Println(a.Slice()) +} +``` + +执行后,输出结果为: + +```10 +[0 1 2 3 4 5 6 7 8 9] +6 true +[0 1 2 3 4 5 6 7 8 9 10 11] +[100 1 2 3 4 5 6 7 8 9 10 11] +5 +[1 2 3 4 5 6 7 8 9 10 11] +100 +[1 2 3 4 5 6 7 8 9 10 100] +[] +``` + +### 排序数组 + +排序数组的方法与普通数组类似,但是带有自动排序功能及唯一性过滤功能。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main () { + // 自定义排序数组,降序排序(SortedIntArray管理的数据是升序) + a := garray.NewSortedArray(func(v1, v2 interface{}) int { + if v1.(int) < v2.(int) { + return 1 + } + if v1.(int) > v2.(int) { + return -1 + } + return 0 + }) + + // 添加数据 + a.Add(2) + a.Add(3) + a.Add(1) + fmt.Println(a.Slice()) + + // 添加重复数据 + a.Add(3) + fmt.Println(a.Slice()) + + // 检索数据,返回最后对比的索引位置,检索结果 + // 检索结果:0: 匹配; <0:参数小于对比值; >0:参数大于对比值 + fmt.Println(a.Search(1)) + + // 设置不可重复 + a.SetUnique(true) + fmt.Println(a.Slice()) + a.Add(1) + fmt.Println(a.Slice()) +} +``` + +执行后,输出结果: + +``` +[3 2 1] +[3 3 2 1] +3 0 +[3 2 1] +[3 2 1] +``` + +### `Iterate*` 数组遍历 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + // Iterator is alias of IteratorAsc, which iterates the array readonly in ascending order + // with given callback function . + // If returns true, then it continues iterating; or false to stop. + array.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + // IteratorDesc iterates the array readonly in descending order with given callback function . + // If returns true, then it continues iterating; or false to stop. + array.IteratorDesc(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c + // 2 c + // 1 b + // 0 a +} +``` + +### `Pop*` 数组项出栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + array := garray.NewFrom([]interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Any Pop* functions pick, delete and return the item from array. + + fmt.Println(array.PopLeft()) + fmt.Println(array.PopLefts(2)) + fmt.Println(array.PopRight()) + fmt.Println(array.PopRights(2)) + + // Output: + // 1 true + // [2 3] + // 9 true + // [7 8] +} +``` + +### `Rand/PopRand` 数组项随机获取/出栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Randomly retrieve and return 2 items from the array. + // It does not delete the items from array. + fmt.Println(array.Rands(2)) + + // Randomly pick and return one item from the array. + // It deletes the picked up item from array. + fmt.Println(array.PopRand()) +} +``` + +### `Contains/ContainsI` 包含判断 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + var array garray.StrArray + array.Append("a") + fmt.Println(array.Contains("a")) + fmt.Println(array.Contains("A")) + fmt.Println(array.ContainsI("A")) + + // Output: + // true + // false + // true +} +``` + +### `FilterEmpty/FilterNil` 空值过滤 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array1 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"}) + array2 := garray.NewFrom(g.Slice{0, 1, 2, nil, "", g.Slice{}, "john"}) + fmt.Printf("%#v\n", array1.FilterNil().Slice()) + fmt.Printf("%#v\n", array2.FilterEmpty().Slice()) + + // Output: + // []interface {}{0, 1, 2, "", []interface {}{}, "john"} + // []interface {}{1, 2, "john"} +} +``` + +### `Reverse` 数组反转 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 5, 3}) + + // Reverse makes array with elements reverse. + fmt.Println(array.Reverse().Slice()) + + // Output: + // [3 5 1] +} +``` + +### `Shuffle` 随机排序 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Shuffle randomly shuffles the array. + fmt.Println(array.Shuffle().Slice()) +} +``` + +### `Walk` 遍历修改 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var array garray.StrArray + tables := g.SliceStr{"user", "user_detail"} + prefix := "gf_" + array.Append(tables...) + // Add prefix for given table names. + array.Walk(func(value string) string { + return prefix + value + }) + fmt.Println(array.Slice()) + + // Output: + // [gf_user gf_user_detail] +} +``` + +### `Join` 数组项串连 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{"a", "b", "c", "d"}) + fmt.Println(array.Join(",")) + + // Output: + // a,b,c,d +} +``` + +### `Chunk` 数组拆分 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array := garray.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + // Chunk splits an array into multiple arrays, + // the size of each array is determined by . + // The last chunk may contain less than size elements. + fmt.Println(array.Chunk(2)) + + // Output: + // [[1 2] [3 4] [5 6] [7 8] [9]] +} +``` + +### `Merge` 数组合并 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + array1 := garray.NewFrom(g.Slice{1, 2}) + array2 := garray.NewFrom(g.Slice{3, 4}) + slice1 := g.Slice{5, 6} + slice2 := []int{7, 8} + slice3 := []string{"9", "0"} + fmt.Println(array1.Slice()) + array1.Merge(array1) + array1.Merge(array2) + array1.Merge(slice1) + array1.Merge(slice2) + array1.Merge(slice3) + fmt.Println(array1.Slice()) + + // Output: + // [1 2] + // [1 2 1 2 3 4 5 6 7 8 9 0] +} +``` + +### `JSON` 序列化/反序列 + +`garray` 模块下的所有容器类型均实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +1. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + +func main() { + type Student struct { + Id int + Name string + Scores *garray.IntArray + } + s := Student{ + Id: 1, + Name: "john", + Scores: garray.NewIntArrayFrom([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +执行后,输出结果: + +``` + {"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/garray" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *garray.IntArray + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +执行后,输出结果: + +``` +{1 john [100,99,98]} +``` diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..92198561d0b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\225\260\347\273\204\347\261\273\345\236\213-garray/\346\225\260\347\273\204\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1454 @@ +--- +slug: '/docs/components/container-garray-funcs' +title: '数组类型-方法介绍' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,数组操作,方法介绍,字符串处理,数据结构,数组优化,编程技巧,代码示例,Go编程,实例教程] +description: '使用GoFrame框架处理数组类型的方法,包括Append、At、Chunk等常用方法。您将学习如何在数组中添加、访问元素,分割数组,清除数据,克隆数组及判定数组状态的方法。每个方法配有具体示例帮助理解和应用。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) +::: +## `Append` + +- 说明:向数组的尾部追加数据,可以添加任意数量字符串。 `Append` 的方法是 `PushRight` 的别名 +- 格式: + +```go +Append(value ...string) *StrArray +``` + +- 示例:建立一个空数组,设置完数据后,并在数组尾部添加新的数据。 + +```go +func ExampleStrArray_Append() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans"}) + s.Append("a", "b", "c") + fmt.Println(s) + + // Output: + // ["We","are","GF","fans","a","b","c"] +} +``` + + +## `At` + +- 说明:返回数组指定索引的数据 +- 格式: + +```go +At(index int) (value string) +``` + +- 示例:建立一个数组,找到 `index` 为2的数据。 + +```go +func ExampleStrArray_At() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + sAt := s.At(2) + fmt.Println(sAt) + + // Output: + // GF +} +``` + + +## `Chunk` + +- 说明:把指定数组按指定的大小 `Size`,分割成多个数组,返回值为 `[][]string`。最后一个数组包含数据的数量可能小于 `Size` +- 格式: + +```go +Chunk(size int) [][]string +``` + +- 示例:建立一个数组,并将该数组分割成3个数组。 + +```go +func ExampleStrArray_Chunk() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Chunk(3) + fmt.Println(r) + + // Output: + // [[a b c] [d e f] [g h]] +} +``` + + +## `Clear` + +- 说明:删除当前数组中所有的数据 +- 格式: + +```go +Clear() *StrArray +``` + +- 示例:建立一个空数组,赋值后,并删除该数组的数据。 + +```go +func ExampleStrArray_Clear() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s) + fmt.Println(s.Clear()) + fmt.Println(s) + + // Output: + // ["a","b","c","d","e","f","g","h"] + // [] + // [] +} +``` + + +## `Clone` + +- 说明:克隆当前的数组。返回一个与当前数组相同的数组拷贝 +- 格式: + +```go +Clone() (newArray *StrArray) +``` + +- 示例:建立一个空数组,赋值后,克隆出一个新数组。 + +```go +func ExampleStrArray_Clone() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Clone() + fmt.Println(r) + fmt.Println(s) + + // Output: + // ["a","b","c","d","e","f","g","h"] + // ["a","b","c","d","e","f","g","h"] +} +``` + + +## `Contains` + +- 说明:判断一个数组是否包含给定的 `String` 值。字符串严格区分大小写。返回值为 `bool` +- 格式: + +```go +Contains(value string) bool +``` + +- 示例:建立一个空数组,设置完数据后,判断是否包含指定数据 `e` 和 `z` + +```go +func ExampleStrArray_Contains() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Contains("e")) + fmt.Println(s.Contains("z")) + + // Output: + // true + // false +} +``` + + +## `ContainsI` + +- 说明:判断一个数组是否包含给定的 `String` 值。字符串不区分大小写。返回值为 `bool` +- 格式: + +```go +ContainsI(value string) bool +``` + +- 示例:建立一个空数组,设置完数据后,判断是否包含指定数据 `E` 和 `z` + +```go +func ExampleStrArray_ContainsI() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.ContainsI("E")) + fmt.Println(s.ContainsI("z")) + + // Output: + // true + // false +} +``` + + +## `CountValues` + +- 说明:统计每个值在数组中出现的次数。返回值为 `map[string]int` +- 格式: + +```go +CountValues() map[string]int +``` + +- 示例:建立一个数组,统计数组中每个字符串包含的个数 + +```go +func ExampleStrArray_CountValues() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "c", "c", "d", "d"}) + fmt.Println(s.CountValues()) + + // Output: + // map[a:1 b:1 c:3 d:2] +} +``` + + +## `Fill` + +- 说明:在数组中指定的开始位置 `startIndex`,用指定的 `value` 进行填充。返回值为 `error` +- 格式: + +```go +Fill(startIndex int, num int, value string) error +``` + +- 示例:建立一个数组,在数组开始位置 `index` 为2的地方,用字符串 `here` 填充3个数据 + +```go +func ExampleStrArray_Fill() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + s.Fill(2, 3, "here") + fmt.Println(s) + + // Output: + // ["a","b","here","here","here","f","g","h"] +} +``` + + +## `FilterEmpty` + +- 说明:过滤指定数组中的空字符串 +- 格式: + +```go +FilterEmpty() *StrArray +``` + +- 示例:建立一个数组,在赋值后,过滤该数组中的空字符串 + +```go +func ExampleStrArray_FilterEmpty() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "", "c", "", "", "d"}) + fmt.Println(s.FilterEmpty()) + + // Output: + // ["a","b","c","d"] +} +``` + + +## `Get` + +- 说明:返回数组中指定 `index` 的值,返回值有2个参数,返回值 `value`,和是否找到指定位置的数据 `found`,为 `true` 则找到,为 `false` 则未找到 +- 格式: + +```go +Get(index int) (value string, found bool) +``` + +- 示例:建立一个数组,在赋值后,得到数组 `index` 为3的值 + +```go +func ExampleStrArray_Get() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + sGet, sBool := s.Get(3) + fmt.Println(sGet, sBool) + + // Output: + // fans true +} +``` + + +## `InsertAfter` + +- 说明:在数组中指定 `index` 的位置之后插入值 `value`,返回值为 `error` +- 格式: + +```go +InsertAfter(index int, value string) error +``` + +- 示例:建立一个数组,在 `index` 为1的值之后,插入字符串 `here` + +```go +func ExampleStrArray_InsertAfter() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.InsertAfter(1, "here") + fmt.Println(s.Slice()) + + // Output: + // [a b here c d] +} +``` + + +## `InsertBefore` + +- 说明:在数组中指定 `index` 的位置之前插入值 `value`,返回值为 `error` +- 格式: + +```go +InsertBefore(index int, value string) error +``` + +- 示例:建立一个数组并初始化,在 `index` 为1的值之前,插入字符串 `here` + +```go +func ExampleStrArray_InsertBefore() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.InsertBefore(1, "here") + fmt.Println(s.Slice()) + + // Output: + // [a here b c d] +} +``` + + +## `Interfaces` + +- 说明:把当前数组作为 `[]interface{}` 进行返回 +- 格式: + +```go +Interfaces() []interface{} +``` + +- 示例:建立一个数组并初始化,并打印出返回值 `[]interface{}` 的内容 + +```go +func ExampleStrArray_Interfaces() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Interfaces() + fmt.Println(r) + + // Output: + // [a b c d e f g h] +} +``` + + +## `IsEmpty` + +- 说明:判断当前数组是不是空数组,如果是空数组,则返回 `true`,如果不是空数组,则返回 `false` +- 格式: + +```go +IsEmpty() bool +``` + +- 示例:建立2个数组并初始化,并判断是否为空数组 + +```go +func ExampleStrArray_IsEmpty() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "", "c", "", "", "d"}) + fmt.Println(s.IsEmpty()) + s1 := garray.NewStrArray() + fmt.Println(s1.IsEmpty()) + + // Output: + // false + // true +} +``` + + +## `Iterator` + +- 说明:数组遍历 +- 格式: + +```go +Iterator(f func(k int, v string) bool) +``` + +- 示例:建立1个数组,并对其进行遍历 + +```go +func ExampleStrArray_Iterator() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c +} +``` + + +## `IteratorAsc` + +- 说明:根据给定的回调函数 `f`,按升序对数组进行遍历,如果 `f` 返回 `true`,则继续进行遍历,否则停止遍历 +- 格式: + +```go +IteratorAsc(f func(k int, v string) bool) +``` + +- 示例:建立1个数组,并按照自定义函数对其进行升序遍历 + +```go +func ExampleStrArray_Iterator() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Iterator(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 0 a + // 1 b + // 2 c +} +``` + + +## `IteratorDesc` + +- 说明:根据给定的回调函数 `f`,按降序对数组进行遍历,如果 `f` 返回 `true`,则继续进行遍历,否则停止遍历 +- 格式: + +```go +IteratorAsc(f func(k int, v string) bool) +``` + +- 示例:建立1个数组,并按照自定义函数对其进行降序遍历 + +```go +func ExampleStrArray_IteratorDesc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.IteratorDesc(func(k int, v string) bool { + fmt.Println(k, v) + return true + }) + + // Output: + // 2 c + // 1 b + // 0 a +} +``` + + +## `Join` + +- 说明:将数组元素根据给定的字符串连接符 `gule`,连接起来 +- 格式: + +```go +Join(glue string) string +``` + +- 示例:给定连接符 `','`,将数组中的字符串连接起来 + +```go +func ExampleStrArray_Join() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + fmt.Println(s.Join(",")) + + // Output: + // a,b,c +} +``` + + +## `Len` + +- 说明:得到数组的长度 +- 格式: + +```go +Len() int +``` + +- 示例:建立一个新数组,初始化后得到该数组的长度 + +```go +func ExampleStrArray_Len() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Len()) + + // Output: + // 8 +} +``` + + +## `LockFunc` + +- 说明:通过回调函数 `f` 对数组进行写锁定 +- 格式: + +```go +LockFunc(f func(array []string)) *StrArray +``` + +- 示例:建立一个新数组,并对该数组在写锁定的状态下,修改最后一个数据 + +```go +func ExampleStrArray_LockFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.LockFunc(func(array []string) { + array[len(array)-1] = "GF fans" + }) + fmt.Println(s) + + // Output: + // ["a","b","GF fans"] +} +``` + + +## `MarshalJSON` + +- 说明:实现 `json.Marshal` 的 `JSON` 格式的序列化接口 +- 格式: + +```go +MarshalJSON() ([]byte, error) +``` + +- 示例:建立一个新 `JSON` 格式的数据,并对该数据进行序列化的操作后,打印出相应结果 + +```go +func ExampleStrArray_MarshalJSON() { + type Student struct { + Id int + Name string + Lessons []string + } + s := Student{ + Id: 1, + Name: "john", + Lessons: []string{"Math", "English", "Music"}, + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // Output: + // {"Id":1,"Name":"john","Lessons":["Math","English","Music"]} +} +``` + + +## `Merge` + +- 说明:合并数组,将指定数组中的内容合并到当前数组中。参数 `array` 可以是任意 `garray` 或 `slice` 类型。 `Merge` 和 `Append` 的主要区别是 `Append` 仅仅支持 `slice` 类型, `Merge` 则支持更多的参数类型 +- 格式: + +```go +Merge(array interface{}) *StrArray +``` + +- 示例:建立2个新数组 `s1` 和 `s2`,并将 `s2` 的数据合并到 `s1` 上 + +```go +func ExampleStrArray_Merge() { + s1 := garray.NewStrArray() + s2 := garray.NewStrArray() + s1.SetArray(g.SliceStr{"a", "b", "c"}) + s2.SetArray(g.SliceStr{"d", "e", "f"}) + s1.Merge(s2) + fmt.Println(s1) + + // Output: + // ["a","b","c","d","e","f"] +} +``` + + +## `NewStrArray` + +- 说明:创建一个新数组。 `safe` 为非必需参数,布尔型,是并发安全的开关,缺省值为 `False` +- 格式: + +```go +NewStrArray(safe ...bool) *StrArray +``` + +- 示例:建立一个空数组,并添加数据。此时没有指定 `Safe` 参数,默认为非并发安全设置 + +```go +func ExampleNewStrArray() { + s := garray.NewStrArray() + s.Append("We") + s.Append("are") + s.Append("GF") + s.Append("Fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF Fans] +} +``` + + +## `NewStrArrayFrom` + +- 说明:根据给定的数组内容,创建一个新数组。 `safe` 为非必需参数,布尔型,是并发安全的开关,缺省值为 `False` +- 格式: + +```go +NewStrArrayFrom(array []string, safe ...bool) *StrArray +``` + +- 示例:建立一个空数组,并根据指定内容添加数据。此时没有指定 `Safe` 参数,默认为非并发安全设置 + +```go +func ExampleNewStrArrayFrom() { + s := garray.NewStrArrayFrom(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF fans !] 5 5 +} +``` + + +## `NewStrArrayFromCopy` + +- 说明:根据给定的数组内容的拷贝,创建一个新数组。 `safe` 为非必需参数,布尔型,是并发安全的开关,缺省值为 `False` +- 格式: + +```go +NewStrArrayFrom(array []string, safe ...bool) *StrArray +``` + +- 示例:建立一个空数组,并根据指定内容添加数据。此时没有指定 `Safe` 参数,默认为非并发安全设置 + +```go +func ExampleNewStrArrayFromCopy() { + s := garray.NewStrArrayFromCopy(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF fans !] 5 5 +} +``` + + +## `NewStrArraySize` + +- 说明:根据给定的 `size` 和 `cap`,创建一个新数组。 `safe` 为非必需参数,布尔型,是并发安全的开关,缺省值为 `False` +- 格式: + +```go +NewStrArraySize(size int, cap int, safe ...bool) *StrArray +``` + +- 示例:建立一个空数组, `Size为3`, `Cap为5`,并添加数据。打印出相应的内容。此时没有指定 `Safe` 参数,默认为非并发安全设置 + +```go +func ExampleNewStrArraySize() { + s := garray.NewStrArraySize(3, 5) + s.Set(0, "We") + s.Set(1, "are") + s.Set(2, "GF") + s.Set(3, "fans") + fmt.Println(s.Slice(), s.Len(), cap(s.Slice())) + + // Output: + // [We are GF] 3 5 +} +``` + + +## `Pad` + +- 说明:填充指定大小为 `size` 的值 `value` 到数组中。如果大小 `size` 是正数,则从数组的右边开始填充。如果 `size` 是负数,则从数组的左边开始填充。如果 `size` 的大小正好等于数组的长度,那么将不会填充任何数据。 +- 格式: + +```go +Pad(size int, value string) *StrArray +``` + +- 示例:建立1个新数组,先从左边将数组,用指定的字符串 `here` 填充到 `size` 为7,然后用指定的字符串 `there` 将数组用字符串填充到 `size` 为10 + +```go +func ExampleStrArray_Pad() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + s.Pad(7, "here") + fmt.Println(s) + s.Pad(-10, "there") + fmt.Println(s) + + // Output: + // ["a","b","c","here","here","here","here"] + // ["there","there","there","a","b","c","here","here","here","here"] +} +``` + + +## `PopLeft` + +- 说明:从数组的左侧将一个字符串数据出栈,返回值 `value` 为出栈的字符串数据。更新后的数组数据为剩余数据。当数组为空时, `found` 为 `false。` +- 格式: + +```go +PopLeft() (value string, found bool) +``` + +- 示例:建立1个新数组,将最左边的数据出栈,并打印出剩余的数据 + +```go +func ExampleStrArray_PopLeft() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PopLeft() + fmt.Println(s.Slice()) + + // Output: + // [b c d] +} +``` + + +## `PopLefts` + +- 说明:从数组的左侧将多个字符串数据出栈,返回值为出栈的字符串数据,出栈数据的个数为 `size`。如果 `size` 比数组的 `size` 大,那么方法将返回数组中所有的数据。如果 `size<=0或者为空`,那么将返回 `nil` +- 格式: + +```go +PopLefts(size int) []string +``` + +- 示例:建立1个新数组,将最左边的2个数据做出栈操作,并打印出出栈的数据和原数组的剩余数据 + +```go +func ExampleStrArray_PopLefts() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopLefts(2) + fmt.Println(r) + fmt.Println(s) + + // Output: + // [a b] + // ["c","d","e","f","g","h"] +} +``` + + +## `PopRand` + +- 说明:从数组中随机出栈1个数据,返回值为出栈的字符串数据。如果数组 `为空`,那么 `found` 将返回 `false` +- 格式: + +```go +PopRand() (value string, found bool) +``` + +- 示例:建立1个新数组,从数组中随机出栈1个数据,并打印出出栈的数据 + +```go +func ExampleStrArray_PopRand() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r, _ := s.PopRand() + fmt.Println(r) + + // May Output: + // e +} +``` + + +## `PopRands` + +- 说明:从数组中随机出栈 `size` 个数据,返回值为出栈的字符串数据。如果 `size<=0或者为空`,那么将返回 `nil` +- 格式: + +```go +PopRands(size int) []string +``` + +- 示例:建立1个新数组,从数组中随机出栈2个数据,并打印出出栈的数据 + +```go +func ExampleStrArray_PopRands() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopRands(2) + fmt.Println(r) + + // May Output: + // [e c] +} +``` + + +## `PopRight` + +- 说明:从数组的右侧将一个字符串数据出栈,返回值 `value` 为出栈的字符串数据。更新后的数组数据为剩余数据。当数组为空时, `found` 为 `false。` +- 格式: + +```go +PopRight() (value string, found bool) +``` + +- 示例:建立1个新数组,将最右边的数据出栈,并打印出剩余的数据 + +```go +func ExampleStrArray_PopRight() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PopRight() + fmt.Println(s.Slice()) + + // Output: + // [a b c] +} +``` + + +## `PopRights` + +- 说明:从数组的右侧将多个字符串数据出栈,返回值为出栈的字符串数据,出栈数据的个数为 `size`。如果 `size` 比数组的 `size` 大,那么方法将返回数组中所有的数据。如果 `size<=0或者为空`,那么将返回 `nil` +- 格式: + +```go +PopRights(size int) []string +``` + +- 示例:建立1个新数组,将最右边的2个数据做出栈操作,并打印出出栈的数据和原数组的剩余数据 + +```go +func ExampleStrArray_PopRights() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.PopRights(2) + fmt.Println(r) + fmt.Println(s) + + // Output: + // [g h] + // ["a","b","c","d","e","f"] +} +``` + + +## `PushLeft` + +- 说明:从数组的左侧入栈一个或多个字符串 +- 格式: + +```go +PushLeft(value ...string) *StrArray +``` + +- 示例:建立1个新数组,从数组的左侧入栈多个字符串数据,并打印出更新后的数据 + +```go +func ExampleStrArray_PushLeft() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PushLeft("We", "are", "GF", "fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans a b c d] +} +``` + + +## `PushRight` + +- 说明:从数组的右侧入栈一个或多个字符串 +- 格式: + +```go +PushRight(value ...string) *StrArray +``` + +- 示例:建立1个新数组,从数组的右侧入栈多个字符串数据,并打印出更新后的数据 + +```go +func ExampleStrArray_PushRight() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.PushRight("We", "are", "GF", "fans") + fmt.Println(s.Slice()) + + // Output: + // [a b c d We are GF fans] +} +``` + + +## `Rand` + +- 说明:从数组中随机取出1个字符串(非删除式) +- 格式: + +```go +Rand() (value string, found bool) +``` + +- 示例:建立1个新数组,从数组中随机取出一个字符串 + +```go +func ExampleStrArray_Rand() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Rand()) + + // May Output: + // c true +} +``` + + +## `Rands` + +- 说明:从数组中随机取出 `size` 个字符串(非删除式) +- 格式: + +```go +Rands(size int) []string +``` + +- 示例:建立1个新数组,从数组中随机取出3个字符串 + +```go +func ExampleStrArray_Rands() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Rands(3)) + + // May Output: + // [e h e] +} +``` + + +## `Range` + +- 说明:获取数组中指定范围的数据。如果是在并发安全的模式下使用,则该方法返回一个 `slice` 拷贝。 +- 格式: + +```go +Range(start int, end ...int) []string +``` + +- 示例:建立1个新数组,获取数组从 `index` 为2至5位置的数据 + +```go +func ExampleStrArray_Range() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.Range(2, 5) + fmt.Println(r) + + // Output: + // [c d e] +} +``` + + +## `Remove` + +- 说明:从数组中移除位置在 `index` 处的数据。如果 `index` 超过数组的边界,则 `found` 返回 `false` +- 格式: + +```go +Remove(index int) (value string, found bool) +``` + +- 示例:建立1个新数组,移除数组 `index` 为1的数据 + +```go +func ExampleStrArray_Remove() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.Remove(1) + fmt.Println(s.Slice()) + + // Output: + // [a c d] +} +``` + + +## `RemoveValue` + +- 说明:从数组中移除指定的数据 `value`。如果 `value` 在数组中被找到,则 `found` 返回 `true`,否则 `found` 返回 `false` +- 格式: + +```go +RemoveValue(value string) bool +``` + +- 示例:建立1个新数组,移除数组中值为 `b` 的数据 + +```go +func ExampleStrArray_RemoveValue() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d"}) + s.RemoveValue("b") + fmt.Println(s.Slice()) + + // Output: + // [a c d] +} +``` + + +## `Replace` + +- 说明:将原字符串数组用指定的字符串数组进行替换,替换从原数组的头部位置开始。 +- 格式: + +```go +Replace(array []string) *StrArray +``` + +- 示例:建立1个新数组,并用指定的字符串数组进行替换 + +```go +func ExampleStrArray_Replace() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice()) + s.Replace(g.SliceStr{"Happy", "coding"}) + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans !] + // [Happy coding GF fans !] +} +``` + + +## `Reverse` + +- 说明:将整个数组反转,例如:["qaz","wsx","edc","rfv"] => ["rfv","edc","wsx","qaz"]。 +- 格式: + +```go +Replace(array []string) *StrArray +``` + +- 示例:建立1个新数组,初始化后执行反转操作并打印 + +```go +func ExampleStrArray_Reverse() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "m", "c"}) + fmt.Println(s.Reverse()) + + // Output: + // ["c","m","a"] +} +``` + + +## `RLockFunc` + +- 说明:通过自定义回调函数 `f` 进行数组的读锁定 +- 格式: + +```go +RLockFunc(f func(array []string)) *StrArray +``` + +- 示例:建立1个新数组,在回调函数 `f` 中,对数组进行遍历,并打印出数组元素 + +```go +func ExampleStrArray_RLockFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e"}) + s.RLockFunc(func(array []string) { + for i := 0; i < len(array); i++ { + fmt.Println(array[i]) + } + }) + + // Output: + // a + // b + // c + // d + // e +} +``` + + +## `Search` + +- 说明:在数组中搜索指定的字符串,返回值为该值在数组中的 `index`,如果没有查询到,则返回 `-1` +- 格式: + +```go +Search(value string) int +``` + +- 示例:建立1个新数组,并在该数组中搜索字符串 `e` 和z + +```go +func ExampleStrArray_Search() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Search("e")) + fmt.Println(s.Search("z")) + + // Output: + // 4 + // -1 +} +``` + + +## `Set` + +- 说明:给数组中指定位置的 `index` 设置值 `value`,如果 `index<0` 或者 `index` 超出了数组的边界,则返回错误 `error` +- 格式: + +```go +Set(index int, value string) error +``` + +- 示例:建立1个新数组,长度为3,给数组设置值,值最终只按顺序设置到了 `index` 为2的位置,因为数组长度限制,最后一个值未设置成功 + +```go +func ExampleStrArray_Set() { + s := garray.NewStrArraySize(3, 5) + s.Set(0, "We") + s.Set(1, "are") + s.Set(2, "GF") + s.Set(3, "fans") + fmt.Println(s.Slice()) + + // Output: + // [We are GF] +} +``` + + +## `SetArray` + +- 说明:根据给定的 `slice` 数组内容给数组赋值 +- 格式: + +```go +SetArray(array []string) *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后,打印出来 + +```go +func ExampleStrArray_SetArray() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"We", "are", "GF", "fans", "!"}) + fmt.Println(s.Slice()) + + // Output: + // [We are GF fans !] +} +``` + + +## `Shuffle` + +- 说明:将数组中的内容进行乱序排列 +- 格式: + +```go +Shuffle() *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后进行乱序排列,并打印出来 + +```go +func ExampleStrArray_Shuffle() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Shuffle()) + + // May Output: + // ["a","c","e","d","b","g","f","h"] +} +``` + + +## `Slice` + +- 说明:得到数组的 `slice` 切片数据,注意,如果是在并发安全模式下,返回的是一份拷贝数据,否则返回的是指向数据的指针 +- 格式: + +```go +Shuffle() *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后,并打印该数组的切片数据 + +```go +func ExampleStrArray_Slice() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + fmt.Println(s.Slice()) + + // Output: + // [a b c d e f g h] +} +``` + + +## `Sort` + +- 说明:对数组内容进行升序排序。 `reverse` 控制排序的方向,默认为 `true` 升序, `false` 为降序 +- 格式: + +```go +Sort(reverse ...bool) *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后,并按升序进行排序 + +```go +func ExampleStrArray_Sort() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"b", "d", "a", "c"}) + a := s.Sort() + fmt.Println(a) + + // Output: + // ["a","b","c","d"] +} +``` + + +## `SortFunc` + +- 说明:通过自定义函数 `less` 对数组内容进行排序。 +- 格式: + +```go +SortFunc(less func(v1, v2 string) bool) *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后,首先用自定义函数对其进行降序排序,然后用自定义函数对其进行升序排序,并打印出相应的结果 + +```go +func ExampleStrArray_SortFunc() { + s := garray.NewStrArrayFrom(g.SliceStr{"b", "c", "a"}) + fmt.Println(s) + s.SortFunc(func(v1, v2 string) bool { + return gstr.Compare(v1, v2) > 0 + }) + fmt.Println(s) + s.SortFunc(func(v1, v2 string) bool { + return gstr.Compare(v1, v2) < 0 + }) + fmt.Println(s) + + // Output: + // ["b","c","a"] + // ["c","b","a"] + // ["a","b","c"] +} +``` + + +## `String` + +- 说明:将当前的数组转换成 `string`, +- 格式: + +```go +String() string +``` + +- 示例:建立1个新数组,在给其赋值后,将数组转换成 `string`,并打印出相应的结果 + +```go +func ExampleStrArray_String() { + s := garray.NewStrArrayFrom(g.SliceStr{"a", "b", "c"}) + fmt.Println(s.String()) + + // Output: + // ["a","b","c"] +} +``` + + +## `Subslice` + +- 说明:根据给定的偏离值 `offset` 和长度 `length` 参数获得数组的切片,注意,如果是在并发安全模式下,返回拷贝数据,否则返回指向数据的指针。如果偏离值 `offset` 是非负数,则从数组的开始位置进行切片,否则从数组的尾部开始切片。 +- 格式: + +```go +SubSlice(offset int, length ...int) +``` + +- 示例:建立1个新数组,在给其赋值后,将数组转换成 `string`,并打印出相应的结果 + +```go +func ExampleStrArray_SubSlice() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "d", "e", "f", "g", "h"}) + r := s.SubSlice(3, 4) + fmt.Println(r) + + // Output: + // [d e f g] +} +``` + + +## `Sum` + +- 说明:对数组中的整数值进行求和 +- 格式: + +```go +Sum() (sum int) +``` + +- 示例:建立1个新数组,在给其赋值后,对数组中的整数值进行求和 + +```go +func ExampleStrArray_Sum() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"3", "5", "10"}) + a := s.Sum() + fmt.Println(a) + + // Output: + // 18 +} +``` + + +## `Unique` + +- 说明:对数组中的数据进行去重处理 +- 格式: + +```go +Unique() *StrArray +``` + +- 示例:建立1个新数组,在给其赋值后,对数组中的整数值进行求和 + +```go +func ExampleStrArray_Unique() { + s := garray.NewStrArray() + s.SetArray(g.SliceStr{"a", "b", "c", "c", "c", "d", "d"}) + fmt.Println(s.Unique()) + + // Output: + // ["a","b","c","d"] +} +``` + + +## `UnmarshalJSON` + +- 说明:实现 `json.Unmarshal` 的 `UnmarshalJSON` 接口 +- 格式: + +```go +UnmarshalJSON(b []byte) error +``` + +- 示例:建立1个 `byte` 切片,将其赋值给结构体后,进行反序列化操作,打印出相应的内容 + +```go +func ExampleStrArray_UnmarshalJSON() { + b := []byte(`{"Id":1,"Name":"john","Lessons":["Math","English","Sport"]}`) + type Student struct { + Id int + Name string + Lessons *garray.StrArray + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // Output: + // {1 john ["Math","English","Sport"]} +} +``` + + +## `UnmarshalValue` + +- 说明:对任意类型的值实现反序列化接口 +- 格式: + +```go +UnmarshalValue(value interface{}) error +``` + +- 示例:建立1个结构体,并对其值 进行反序列化操作,打印出相应的内容 + +```go +func ExampleStrArray_UnmarshalValue() { + type Student struct { + Name string + Lessons *garray.StrArray + } + var s *Student + gconv.Struct(g.Map{ + "name": "john", + "lessons": []byte(`["Math","English","Sport"]`), + }, &s) + fmt.Println(s) + + var s1 *Student + gconv.Struct(g.Map{ + "name": "john", + "lessons": g.SliceStr{"Math", "English", "Sport"}, + }, &s1) + fmt.Println(s1) + + // Output: + // &{john ["Math","English","Sport"]} + // &{john ["Math","English","Sport"]} +} +``` + + +## `Walk` + +- 说明:通过自定义函数 `f`,对数组内容进行遍历修改 +- 格式: + +```go +Walk(f func(value string) string) *StrArray +``` + +- 示例:建立1个数组,对数组内容进行遍历修改,为每个字符串添加前缀,并打印出相应的内容 + +```go +func ExampleStrArray_Walk() { + var array garray.StrArray + tables := g.SliceStr{"user", "user_detail"} + prefix := "gf_" + array.Append(tables...) + // Add prefix for given table names. + array.Walk(func(value string) string { + return prefix + value + }) + fmt.Println(array.Slice()) + + // Output: + // [gf_user gf_user_detail] +} +``` diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" new file mode 100644 index 00000000000..3ffc49b541a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-gtree.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/container-gtree' +title: '树形类型-gtree' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,树形容器,gtree,并发安全,红黑树,AVL树,BTree,排序,大数据处理,数据结构] +description: '该文档介绍了GoFrame框架提供的树形容器组件,包括RedBlackTree、AVLTree和BTree等数据结构。树形容器支持并发安全和有序遍历,适合处理大数据量的存储需求。通过GoFrame框架,开发者可以高效地实现关联数组、排序键值对和大数据量内存CRUD等场景。' +--- + +## 基本介绍 + +支持并发安全开关特性的树形容器,树形数据结构的特点是支持有序遍历、内存占用低、复杂度稳定、适合大数据量存储。该模块包含多个数据结构的树形容器: `RedBlackTree`、 `AVLTree` 和 `BTree`。 + +| 类型 | 数据结构 | 平均复杂度 | 支持排序 | 有序遍历 | 说明 | +| --- | --- | --- | --- | --- | --- | +| `RedBlackTree` | 红黑树 | `O(log N)` | 是 | 是 | 写入性能比较好 | +| `AVLTree` | 高度平衡树 | `O(log N)` | 是 | 是 | 查找性能比较好 | +| `BTree` | B树/B-树 | `O(log N)` | 是 | 是 | 常用于外部存储 | + +> 参考连接: [https://en.wikipedia.org/wiki/Binary\_tree](https://en.wikipedia.org/wiki/Binary_tree) + +**使用场景**: + +关联数组场景、排序键值对场景、大数据量内存CRUD场景等等。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gtree" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree) + +几种容器的API方法都非常类似,特点是需要在初始化时提供用于排序的方法。 + +在 `gutil` 模块中提供了常用的一些基本类型比较方法,可以直接在程序中直接使用,后续也有示例。 + +```go +func ComparatorByte(a, b interface{}) int +func ComparatorFloat32(a, b interface{}) int +func ComparatorFloat64(a, b interface{}) int +func ComparatorInt(a, b interface{}) int +func ComparatorInt16(a, b interface{}) int +func ComparatorInt32(a, b interface{}) int +func ComparatorInt64(a, b interface{}) int +func ComparatorInt8(a, b interface{}) int +func ComparatorRune(a, b interface{}) int +func ComparatorString(a, b interface{}) int +func ComparatorTime(a, b interface{}) int +func ComparatorUint(a, b interface{}) int +func ComparatorUint16(a, b interface{}) int +func ComparatorUint32(a, b interface{}) int +func ComparatorUint64(a, b interface{}) int +func ComparatorUint8(a, b interface{}) int +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..af5637e85d7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,162 @@ +--- +slug: '/docs/components/container-gtree-example' +title: '树形类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,树形结构,RedBlackTree,AVL树,gtree,数据结构,遍历,基本使用,示例] +description: '在GoFrame框架中使用树形类型的数据结构进行基本操作。通过示例代码展示了如何创建和操作RedBlackTree和AVL树,包括节点的添加、删除和遍历等功能,使开发者能够轻松实现高效的数据存储和查找。' +--- + +## 基本使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gtree" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + m := gtree.NewRedBlackTree(gutil.ComparatorInt) + + // 设置键值对 + for i := 0; i < 10; i++ { + m.Set(i, i*10) + } + // 查询大小 + fmt.Println(m.Size()) + // 批量设置键值对(不同的数据类型对象参数不同) + m.Sets(map[interface{}]interface{}{ + 10: 10, + 11: 11, + }) + fmt.Println(m.Size()) + + // 查询是否存在 + fmt.Println(m.Contains(1)) + + // 查询键值 + fmt.Println(m.Get(1)) + + // 删除数据项 + m.Remove(9) + fmt.Println(m.Size()) + + // 批量删除 + m.Removes([]interface{}{10, 11}) + fmt.Println(m.Size()) + + // 当前键名列表(随机排序) + fmt.Println(m.Keys()) + // 当前键值列表(随机排序) + fmt.Println(m.Values()) + + // 查询键名,当键值不存在时,写入给定的默认值 + fmt.Println(m.GetOrSet(100, 100)) + + // 删除键值对,并返回对应的键值 + fmt.Println(m.Remove(100)) + + // 遍历map + m.IteratorAsc(func(k interface{}, v interface{}) bool { + fmt.Printf("%v:%v ", k, v) + return true + }) + fmt.Println() + + // 清空map + m.Clear() + + // 判断map是否为空 + fmt.Println(m.IsEmpty()) +} +``` + +执行后,输出结果为: + +```10 +12 +true +10 +11 +9 +[0 1 2 3 4 5 6 7 8] +[0 10 20 30 40 50 60 70 80] +100 +100 +0:0 1:10 2:20 3:30 4:40 5:50 6:60 7:70 8:80 +true +``` + +## 前序/后续遍历 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gtree" + "github.com/gogf/gf/v2/util/gutil" +) + +func main() { + tree := gtree.NewAVLTree(gutil.ComparatorInt) + for i := 0; i < 10; i++ { + tree.Set(i, i*10) + } + // 打印树形 + tree.Print() + // 前序遍历 + fmt.Println("ASC:") + tree.IteratorAsc(func(key, value interface{}) bool { + fmt.Println(key, value) + return true + }) + // 后续遍历 + fmt.Println("DESC:") + tree.IteratorDesc(func(key, value interface{}) bool { + fmt.Println(key, value) + return true + }) +} +``` + +执行后,输出结果为: + +```AVLTree +│ ┌── 9 +│ ┌── 8 +│ ┌── 7 +│ │ │ ┌── 6 +│ │ └── 5 +│ │ └── 4 +└── 3 + │ ┌── 2 + └── 1 + └── 0 + +ASC: +0 0 +1 10 +2 20 +3 30 +4 40 +5 50 +6 60 +7 70 +8 80 +9 90 +DESC: +9 90 +8 80 +7 70 +6 60 +5 50 +4 40 +3 30 +2 20 +1 10 +0 0 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..7fa1287e5ae --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\240\221\345\275\242\347\261\273\345\236\213-gtree/\346\240\221\345\275\242\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1301 @@ +--- +slug: '/docs/components/container-gtree-funcs' +title: '树形类型-方法介绍' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,树形结构,BTree,节点管理,并发安全,键值操作,高度计算,查找,树迭代,数据操作] +description: 'GoFrame框架中树形结构的多种操作方法,包括NewBTree、Clone、Set、Get等,详细的示例展示了如何使用这些方法进行节点的添加、查找、删除等操作,同时支持并发安全的设置,用户可以通过本文档快速掌握树形结构的使用技巧。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree](https://pkg.go.dev/github.com/gogf/gf/v2/container/gtree) +::: +## `NewBTree` + +- 说明: `NewBTree` 使用 `m`(最大子节点数量)和自定义的比较方法创建 `BTree`。参数 `safe` 用于指定是否使用并发安全的 `tree`,默认情况下为 `false`。 + +- 注意:参数 `m` 必须大于等于 `3`,否则会 `panic`。 +- 格式: + +```go +NewBTree(m int, comparator func(v1, v2 interface{}) int, safe ...bool) *BTree +``` + +- 示例: + +```go +func ExampleNewBTree() { + bTree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + bTree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + fmt.Println(bTree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `NewBTreeFrom` + +- 说明: `NewBTreeFrom` 使用 `m`(最大子节点数量),自定义的比较方法和类型为 `map[interface{}]interface{}` 的 `data` 创建 `BTree`。参数 `safe` 用于指定是否使用并发安全的 `tree`,默认情况下为 `false`。 + +- 注意:参数 `m` 必须大于等于 `3`,否则会 `panic`。 +- 格式: + +```go +NewBTreeFrom(m int, comparator func(v1, v2 interface{}) int, data map[interface{}]interface{}, safe ...bool) *BTree +``` + +- 示例: + +```go +func ExampleNewBTreeFrom() { + bTree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + bTree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + otherBTree := gtree.NewBTreeFrom(3, gutil.ComparatorString, bTree.Map()) + fmt.Println(otherBTree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `Clone` + +- 说明: `clone` 返回一个值为当前 `tree` 值的副本的新 `BTree`。 + +- 格式: + +```go +Clone() *BTree +``` + +- 示例: + +```go +func ExampleBTree_Clone() { + b := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + b.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + tree := b.Clone() + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // 6 +} +``` + + +## `Set` + +- 说明: `Set` 为 `tree` 设置 `key/value`。 + +- 格式: + +```go +Set(key interface{}, value interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Set() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // 6 +} +``` + + +## `Sets` + +- 说明: `Sets` 为 `tree` 批量设置 `key/value`。 + +- 格式: + +```go +Sets(data map[interface{}]interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Sets() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + tree.Sets(map[interface{}]interface{}{ + "key1": "val1", + "key2": "val2", + }) + + fmt.Println(tree.Map()) + fmt.Println(tree.Size()) + + // Output: + // map[key1:val1 key2:val2] + // 2 +} +``` + + +## `Get` + +- 说明: `Get` 返回参数 `key` 对应的值 `value`,如 `key` 不存在,则返回 `Nil`。 + +- 格式: + +```go +Get(key interface{}) (value interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Get() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Get("key1")) + fmt.Println(tree.Get("key10")) + + // Output: + // val1 + // +} +``` + + +## `GetOrSet` + +- 说明: `GetOrSet` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `value` 设置键值,然后返回该值。 + +- 格式: + +```go +GetOrSet(key interface{}, value interface{}) interface{} +``` + +- 示例: + +```go +func ExampleBTree_GetOrSet() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSet("key1", "newVal1")) + fmt.Println(tree.GetOrSet("key6", "val6")) + + // Output: + // val1 + // val6 +} +``` + + +## `GetOrSetFunc` + +- 说明: `GetOrSetFunc` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `func f` 的返回值设置键值,然后返回该值。 + +- 格式: + +```go +GetOrSetFunc(key interface{}, f func() interface{}) interface{} +``` + +- 示例: + +```go +func ExampleBTree_GetOrSetFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSetFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetOrSetFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `GetOrSetFuncLock` + +- 说明: `GetOrSetFunc` 如 `key` 存在,则返回 `value`,如 `key` 不存在,使用 `key` 和 `func f` 的返回值设置键值,然后返回该值。 + +- 注意: `GetOrSetFuncLock` 与 `GetOrSetFunc` 函数的不同之处在于它在写锁中执行函数 `f`。 +- 格式: + +```go +GetOrSetFuncLock(key interface{}, f func() interface{}) interface{} +``` + +- 示例: + +```go +func ExampleBTree_GetOrSetFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetOrSetFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetOrSetFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## GetVar + +- 说明: `GetVar` 根据键名 `key` 查询并返回对应的键值,类型为 `*gvar.Var`。 + +- 注意:返回的 `gvar.Var` 是非并发安全的。 +- 格式: + +```go +GetVar(key interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleBTree_GetVar() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVar("key1").String()) + + // Output: + // val1 +} +``` + + +## `GetVarOrSet` + +- 说明: `GetVarOrSet` 使用 `GetOrSet` 的结果返回,类型为 `*gvar.Var`。 + +- 注意:返回的 `gvar.Var` 是非并发安全的。 +- 格式: + +```go +GetVarOrSet(key interface{}, value interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleBTree_GetVarOrSet() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSet("key1", "newVal1")) + fmt.Println(tree.GetVarOrSet("key6", "val6")) + + // Output: + // val1 + // val6 +} +``` + + +## GetVarOrSetFunc + +- 说明: `GetVarOrSetFunc` 使用 `GetOrSetFunc` 的结果返回,类型为 `*gvar.Var`。 +- 注意:返回的 `gvar.Var` 是非并发安全的。 +- 格式: + +```go +GetVarOrSetFunc(key interface{}, f func() interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleBTree_GetVarOrSetFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSetFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetVarOrSetFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `GetVarOrSetFuncLock` + +- 说明: `GetVarOrSetFuncLock` 使用 `GetOrSetFuncLock` 的结果返回,类型为 `*gvar.Var`。 + +- 注意:返回的 `gvar.Var` 是非并发安全的。 +- 格式: + +```go +GetVarOrSetFuncLock(key interface{}, f func() interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleBTree_GetVarOrSetFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.GetVarOrSetFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.GetVarOrSetFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // val1 + // val6 +} +``` + + +## `SetIfNotExist` + +- 说明:如果 `key` 不存在,则 `SetIfNotExist` 为 `map` 设置值键值对 `key/value`,并且返回 `true`。如果 `key` 存在,则返回 `false`,而 `value` 将被忽略。 + +- 格式: + +```go +SetIfNotExist(key interface{}, value interface{}) bool +``` + +- 示例: + +```go +func ExampleBTree_SetIfNotExist() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExist("key1", "newVal1")) + fmt.Println(tree.SetIfNotExist("key6", "val6")) + + // Output: + // false + // true +} +``` + + +## `SetIfNotExistFunc` + +- 说明:如果 `key` 不存在,则 `SetIfNotExistFunc` 设置值为函数 `f` 的返回值,并且返回 `true`。如果 `key` 存在,则返回 `false`,并且 `value` 将被忽略。 + +- 格式: + +```go +SetIfNotExistFunc(key interface{}, f func() interface{}) bool +``` + +- 示例: + +```go +func ExampleBTree_SetIfNotExistFunc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExistFunc("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.SetIfNotExistFunc("key6", func() interface{} { + return "val6" + })) + + // Output: + // false + // true +} +``` + + +## `SetIfNotExistFuncLock` + +- 说明:如果 `key` 不存在,则 `SetIfNotExistFunc` 设置值为 `func c` 的返回值,然后返回 `true`。如果 `key` 存在,则返回 `false`,而 `value` 将被忽略。 + +- `SetIfNotExistFuncLock` 与 `SetIfNotExistFunc` 函数的不同之处在于它在 `mutex.Lock` 中执行函数 `f`。 +- 格式: + +```go +SetIfNotExistFuncLock(key interface{}, f func() interface{}) bool +``` + +- 示例: + +```go +func ExampleBTree_SetIfNotExistFuncLock() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.SetIfNotExistFuncLock("key1", func() interface{} { + return "newVal1" + })) + fmt.Println(tree.SetIfNotExistFuncLock("key6", func() interface{} { + return "val6" + })) + + // Output: + // false + // true +} +``` + + +## `Contains` + +- 说明: `Contains` 检查 `key` 在 `tree` 中是否存在。如果 `key` 存在,则返回 `true`,否则返回 `false`。 + +- 格式: + +```go +Contains(key interface{}) bool +``` + +- 示例: + +```go +func ExampleBTree_Contains() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Contains("key1")) + fmt.Println(tree.Contains("key6")) + + // Output: + // true + // false +} +``` + + +## `Remove` + +- 说明:按给定的 `key` 从 `tree` 中删除 `value`,并返回此删除的 `value`。 + +- 格式: + +```go +Remove(key interface{}) (value interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Remove() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Remove("key1")) + fmt.Println(tree.Remove("key6")) + fmt.Println(tree.Map()) + + // Output: + // val1 + // + // map[key0:val0 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `Removes` + +- 说明: `Removes` 按给定的 `key` 批量删除 `tree` 的 `value`。 + +- 格式: + +```go +Removes(keys []interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Removes() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + removeKeys := make([]interface{}, 2) + removeKeys = append(removeKeys, "key1") + removeKeys = append(removeKeys, "key6") + + tree.Removes(removeKeys) + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## IsEmpty + +- 说明: `IsEmpty` 检查 `tree` 是否为空。如果 `tree` 为空,则返回 `true`,否则返回 `false`。 + +- 格式: + +```go +IsEmpty() bool +``` + +- 示例: + +```go +func ExampleBTree_IsEmpty() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + fmt.Println(tree.IsEmpty()) + + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.IsEmpty()) + + // Output: + // true + // false +} +``` + + +## `Size` + +- 说明: `Size` 返回 `tree` 的大小。 + +- 格式: + +```go +Size() int +``` + +- 示例: + +```go +func ExampleBTree_Size() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + + fmt.Println(tree.Size()) + + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Size()) + + // Output: + // 0 + // 6 +} +``` + + +## `Keys` + +- 说明: `Keys` 按升序返回所有的 `key`。 + +- 格式: + +```go +Keys() []interface{} +``` + +- 示例: + +```go +func ExampleBTree_Keys() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 6; i > 0; i-- { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Keys()) + + // Output: + // [key1 key2 key3 key4 key5 key6] +} +``` + + +## `Values` + +- 说明: `Values` 按 `key` 的升序返回所有的 `value`。 + +- 格式: + +```go +Values() []interface{} +``` + +- 示例: + +```go +func ExampleBTree_Values() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 6; i > 0; i-- { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Values()) + + // Output: + // [val1 val2 val3 val4 val5 val6] +} +``` + + +## `Map` + +- 说明: `Map` 以 `map` 的形式返回所有的 `key/value`。 + +- 格式: + +```go +Map() map[interface{}]interface{} +``` + +- 示例: + +```go +func ExampleBTree_Map() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] +} +``` + + +## `MapStrAny` + +- 说明: `MapStrAny` 以 `map[string]interface{}` 的形式返回所有的 `key/value`。 + +- 格式: + +```go +MapStrAny() map[string]interface{} +``` + +- 示例: + +```go +func ExampleBTree_MapStrAny() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set(1000+i, "val"+gconv.String(i)) + } + + fmt.Println(tree.MapStrAny()) + + // Output: + // map[1000:val0 1001:val1 1002:val2 1003:val3 1004:val4 1005:val5] +} +``` + + +## `Clear` + +- 说明: `Clear` 删除 `tree` 的所有数据。 + +- 格式: + +```go +Clear() +``` + +- 示例: + +```go +func ExampleBTree_Clear() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set(1000+i, "val"+gconv.String(i)) + } + fmt.Println(tree.Size()) + + tree.Clear() + fmt.Println(tree.Size()) + + // Output: + // 6 + // 0 +} +``` + + +## `Replace` + +- 说明: `Replace` 用类型为 `map[interface{}]interface{}` 的 `data` 替换 `tree` 的 `key/value`。 + +- 格式: + +```go +Replace(data map[interface{}]interface{}) +``` + +- 示例: + +```go +func ExampleBTree_Replace() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Map()) + + data := map[interface{}]interface{}{ + "newKey0": "newVal0", + "newKey1": "newVal1", + "newKey2": "newVal2", + } + + tree.Replace(data) + + fmt.Println(tree.Map()) + + // Output: + // map[key0:val0 key1:val1 key2:val2 key3:val3 key4:val4 key5:val5] + // map[newKey0:newVal0 newKey1:newVal1 newKey2:newVal2] +} +``` + + +## `Height` + +- 说明: `Height` 返回 `tree` 的高度。 + +- 格式: + +```go +Height() int +``` + +- 示例: + +```go +func ExampleBTree_Height() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 0; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Height()) + + // Output: + // 6 +} +``` + + +## `Left` + +- 说明: `Left` 返回最左边(最小)的类型为 `*BTreeEntry` 的 `node`,如果 `tree` 是空的,则返回 `nil`。 + +- 格式: + +```go +Left() *BTreeEntry +``` + +- 示例: + +```go +func ExampleBTree_Left() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 1; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Left().Key, tree.Left().Value) + + emptyTree := gtree.NewBTree(3, gutil.ComparatorInt) + fmt.Println(emptyTree.Left()) + + // Output: + // 1 1 + // +} +``` + + +## `Right` + +- 说明: `Left` 返回最右边(最大)的类型为 `*BTreeEntry` 的 `node`,如果 `tree` 是空的,则返回 `nil`。 + +- 格式: + +```go +Right() *BTreeEntry +``` + +- 示例: + +```go +func ExampleBTree_Right() { + tree := gtree.NewBTree(3, gutil.ComparatorInt) + for i := 1; i < 100; i++ { + tree.Set(i, i) + } + fmt.Println(tree.Right().Key, tree.Right().Value) + + emptyTree := gtree.NewBTree(3, gutil.ComparatorInt) + fmt.Println(emptyTree.Left()) + + // Output: + // 99 99 + // +} +``` + + +## `String` + +- 说明: `String` 返回 `tree` 的 `node` 显示(用于调试)。 + +- 格式: + +```go +String() string +``` + +- 示例: + +```go +func ExampleBTree_String() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.String()) + + // Output: + // key0 + // key1 + // key2 + // key3 + // key4 + // key5 +} +``` + + +## `Search` + +- 说明: `Search` 使用参数 `key` 搜索 `tree`。如果找到 `key`,则返回其对应的键值,并且返回参数 `found` 为 `true`,否则为 `false`。 + +- 格式: + +```go +Search(key interface{}) (value interface{}, found bool) +``` + +- 示例: + +```go +func ExampleBTree_Search() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + fmt.Println(tree.Search("key0")) + fmt.Println(tree.Search("key6")) + + // Output: + // val0 true + // false +} +``` + + +## `Print` + +- 说明: `Print` 将 `tree` 打印到标准输出。 + +- 格式: + +```go +Print() +``` + +- 示例: + +```go +func ExampleBTree_Print() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + tree.Print() + + // Output: + // key0 + // key1 + // key2 + // key3 + // key4 + // key5 +} +``` + + +## `Iterator` + +- 说明: `Iterator` 等同于 `IteratorAsc` 。 + +- 格式: + +```go +Iterator(f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_Iterator() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + var totalKey, totalValue int + tree.Iterator(func(key, value interface{}) bool { + totalKey += key.(int) + totalValue += value.(int) + + return totalValue < 20 + }) + + fmt.Println("totalKey:", totalKey) + fmt.Println("totalValue:", totalValue) + + // Output: + // totalKey: 3 + // totalValue: 27 +} +``` + + +## `IteratorFrom` + +- 说明: `IteratorFrom` 等同于 `IteratorAscFrom` 。 + +- 格式: + +```go +IteratorFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_IteratorFrom() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorFrom(1, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + + +## `IteratorAsc` + +- 说明: `IteratorAsc` 使用自定义回调函数 `f` 以只读方式按升序迭代 `tree`。如果 `f` 返回 `true`,则继续迭代,返回 `false` 则停止。 + +- 格式: + +```go +IteratorAsc(f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_IteratorAsc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + tree.IteratorAsc(func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 0 , value: 10 + // key: 1 , value: 9 + // key: 2 , value: 8 + // key: 3 , value: 7 + // key: 4 , value: 6 + // key: 5 , value: 5 + // key: 6 , value: 4 + // key: 7 , value: 3 + // key: 8 , value: 2 + // key: 9 , value: 1 +} +``` + + +## `IteratorAscFrom` + +- 说明: `IteratorAscFrom` 使用自定义回调函数 `f` 以只读方式按升序迭代 `tree`。参数 `key` 指定从哪个 `key` 开始迭代。参数 `match` 为 `true` 时,从 `key` 完全匹配时开始迭代,否则使用索引搜索迭代。如果 `f` 返回 `true`,则继续迭代,返回 `false` 则停止。 + +- 格式: + +```go +IteratorAscFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_IteratorAscFrom_Normal() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(1, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + +```go +func ExampleBTree_IteratorAscFrom_NoExistKey() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(0, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: +} +``` + +```go +func ExampleBTree_IteratorAscFrom_NoExistKeyAndMatchFalse() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorAscFrom(0, false, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 1 , value: 10 + // key: 2 , value: 20 + // key: 3 , value: 30 + // key: 4 , value: 40 + // key: 5 , value: 50 +} +``` + + +## `IteratorDesc` + +- 说明: `IteratorDesc` 使用自定义回调函数 `f` 以只读方式按降序迭代 `tree`。如果 `f` 返回 `true`,则继续迭代,返回 `false` 则停止。 + +- 格式: + +```go +IteratorDesc(f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_IteratorDesc() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 10; i++ { + tree.Set(i, 10-i) + } + + tree.IteratorDesc(func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 9 , value: 1 + // key: 8 , value: 2 + // key: 7 , value: 3 + // key: 6 , value: 4 + // key: 5 , value: 5 + // key: 4 , value: 6 + // key: 3 , value: 7 + // key: 2 , value: 8 + // key: 1 , value: 9 + // key: 0 , value: 10 +} +``` + + +## `IteratorDescFrom` + +- 说明: `IteratorDescFrom` 使用自定义回调函数 `f` 以只读方式按降序迭代 `tree`。参数 `key` 指定从哪个 `key` 开始迭代。参数 `match` 为 `true` 时,从 `key` 完全匹配时开始迭代,否则使用索引搜索迭代。如果 `f` 返回 `true`,则继续迭代,返回 `false` 则停止。 + +- 格式: + +```go +IteratorDescFrom(key interface{}, match bool, f func(key, value interface{}) bool) +``` + +- 示例: + +```go +func ExampleBTree_IteratorDescFrom() { + m := make(map[interface{}]interface{}) + for i := 1; i <= 5; i++ { + m[i] = i * 10 + } + tree := gtree.NewBTreeFrom(3, gutil.ComparatorInt, m) + + tree.IteratorDescFrom(5, true, func(key, value interface{}) bool { + fmt.Println("key:", key, ", value:", value) + return true + }) + + // Output: + // key: 5 , value: 50 + // key: 4 , value: 40 + // key: 3 , value: 30 + // key: 2 , value: 20 + // key: 1 , value: 10 +} +``` + + +## `MarshalJson` + +- 说明: `MarshalJSON` 实现 `json.Marshal` 的接口。 + +- 格式: + +```go +MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleBTree_MarshalJSON() { + tree := gtree.NewBTree(3, gutil.ComparatorString) + for i := 0; i < 6; i++ { + tree.Set("key"+gconv.String(i), "val"+gconv.String(i)) + } + + bytes, err := json.Marshal(tree) + if err == nil { + fmt.Println(gconv.String(bytes)) + } + + // Output: + // {"key0":"val0","key1":"val1","key2":"val2","key3":"val3","key4":"val4","key5":"val5"} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" new file mode 100644 index 00000000000..a026d1dafb7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-gvar.md" @@ -0,0 +1,36 @@ +--- +slug: '/docs/components/container-gvar' +title: '泛型类型-gvar' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,gvar,泛型类型,运行时泛型,并发安全,数据类型转换,g.Var,开发效率] +description: 'GoFrame框架中的gvar类型,它是一种运行时泛型实现,旨在提高开发便捷性和效率。gvar支持内置数据类型转换,并可作为interface{}的替代类型,其与并发安全特性使其在需要频繁数据转换的场景下表现出色。此外,还介绍了gvar类型的使用方式和相关接口文档。' +--- + +![](/markdown/cd9ed75865d6b5efe704f58156a42fa4.png) + +## 基本介绍 + +`gvar` 是一种 **运行时泛型** 实现,以较小的运行时开销提高开发便捷性以及研发效率,支持各种内置的数据类型转换,可以作为 `interface{}` 类型的替代数据类型,并且该类型支持并发安全开关。 +:::tip +框架同时提供了 `g.Var` 的数据类型,其实也是 `gvar.Var` 数据类型的别名。 +::: +**使用场景**: + +使用 `interface{}` 的场景,各种不固定数据类型格式,或者需要对变量进行频繁的数据类型转换的场景。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gvar" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar](https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..a5667e73700 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,118 @@ +--- +slug: '/docs/components/container-gvar-example' +title: '泛型类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,泛型类型,JSON序列化,JSON反序列化,基本类型转换,slice转换,gvar.Var,Marshal,Unmarshal] +description: '在使用GoFrame框架时,利用gvar.Var容器进行泛型类型的基本使用,包括基本类型转换和slice转换。还展示了如何实现JSON格式的数据序列化和反序列化操作,借助GoFrame框架提供的接口,用户能够轻松操作复杂数据结构,实现高效的数据处理。' +--- + +### 基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "fmt" +) + +func main() { + var v g.Var + + v.Set("123") + + fmt.Println(v.Val()) + + // 基本类型转换 + fmt.Println(v.Int()) + fmt.Println(v.Uint()) + fmt.Println(v.Float64()) + + // slice转换 + fmt.Println(v.Ints()) + fmt.Println(v.Floats()) + fmt.Println(v.Strings()) +} +``` + +执行后,输出结果为: + +```html +123 +123 +123 +123 +[123] +[123] +[123] +``` + +### `JSON` 序列化/反序列 + +`gvar.Var` 容器实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +1. `Marshal` + +```go + package main + + import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + ) + + func main() { + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + s := Student{ + Id: g.NewVar(1), + Name: g.NewVar("john"), + Scores: g.NewVar([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + } +``` + + +执行后,输出结果: + +```bash + {"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go + package main + + import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/frame/g" + ) + + func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + } +``` + + +执行后,输出结果: + +```bash + {1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..f8769846276 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,1539 @@ +--- +slug: '/docs/components/container-gvar-funcs' +title: '泛型类型-方法介绍' +sidebar_position: 1 +hide_title: true +keywords: [方法介绍,泛型类型,GoFrame,方法使用,gvar包,数据类型转换,示例代码,变量操作,GoFrame框架,GoFrame应用] +description: 'GoFrame框架中的常用方法,包括创建新变量、克隆变量、设置变量、获取变量值等操作。通过示例代码讲解每个方法的使用方式,帮助用户更好地理解和应用这些方法。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar](https://pkg.go.dev/github.com/gogf/gf/v2/container/gvar) +::: +### `New` + +- 说明:`New`创建并返回一个带有给定`value`的新 `Var`。可选参数 `safe`指定是否在并发安全中使用 `Var`,其默认值是`false`。 +- 格式: + +```go +func New(value interface{}, safe ...bool) *Var +``` + +-  示例: + +```go +// New +func ExampleVarNew() { + v := gvar.New(400) + g.Dump(v) + + // Output: + // "400" +} +``` + + +### `Clone` + +- 说明: `Clone` 将当前 `Var`的浅拷贝并返回一个指向此`Var`的指针。 +- 格式: + +```go +func (v *Var) Clone() *Var +``` + +- 示例 + +```go +// Clone +func ExampleVar_Clone() { + tmp := "fisrt hello" + v := gvar.New(tmp) + g.DumpWithType(v.Clone()) + fmt.Println(v == v.Clone()) + + // Output: + // *gvar.Var(11) "fisrt hello" + // false +} +``` + + +### `Set` + +- 说明: `Set` 将 `v` 的值设置为 `value` 并返回`v`的旧值。 +- 格式: + +```go +func (v *Var) Set(value interface{}) (old interface{}) +``` + +- 示例: + +```go +// Set +func ExampleVar_Set() { + var v = gvar.New(100.00) + g.Dump(v.Set(200.00)) + g.Dump(v) + + // Output: + // 100 + // "200" +} +``` + + +### `Val` + +- 说明:`Val`返回 `v` 的当前值,类型为 `interface{}`。 +- 格式: + +```go +func (v *Var) Val() interface{} +``` + +- 示例: + +```go +// Val +func ExampleVar_Val() { + var v = gvar.New(100.00) + g.DumpWithType(v.Val()) + + // Output: + // float64(100) +} +``` + + +### `Interface` + +- 说明: `Interface` 是`Val`的别名。 +- 格式: + +```go +func (v *Var) Interface() interface{} +``` + +- 示例: + +```go +// Interface +func ExampleVar_Interface() { + var v = gvar.New(100.00) + g.DumpWithType(v.Interface()) + + // Output: + // float64(100) +} +``` + + +### `Bytes` + +- 说明:`Bytes`将`v`转换为字节数组。 +- 格式: + +```go +func (v *Var) Bytes() []byte +``` + +- 示例: + +```go +// Bytes +func ExampleVar_Bytes() { + var v = gvar.New("GoFrame") + g.DumpWithType(v.Bytes()) + + // Output: + // []byte(7) "GoFrame" +} +``` + + +### `String` + +- 说明: `String` 将 `v` 转换为字符串。 +- 格式: + +```go +func (v *Var) String() string +``` + +- 示例: + +```go +// String +func ExampleVar_String() { + var v = gvar.New("GoFrame") + g.DumpWithType(v.String()) + + // Output: + // string(7) "GoFrame" +} +``` + + +### `Bool` + +- 说明:`Bool`将`v`转换为布尔值。 +- 格式: + +```go +func (v *Var) Bool() bool +``` + +- 示例: + +```go +// Bool +func ExampleVar_Bool() { + var v = gvar.New(true) + g.DumpWithType(v.Bool()) + + // Output: + // bool(true) +} +``` + + +### `Int` + +- 说明: `Int` 将 `v` 转换为整型。 +- 格式: + +```go +func (v *Var) Int() int +``` + +- 示例: + +```go +// Int +func ExampleVar_Int() { + var v = gvar.New(-1000) + g.DumpWithType(v.Int()) + + // Output: + // int(-1000) +} +``` + + +### `Uint` + +- 说明: `Uint` 将 `v` 转换为无符号整型。 +- 格式: + +```go +func (v *Var) Uint() uint +``` + +- 示例: + +```go +// Uint +func ExampleVar_Uint() { + var v = gvar.New(1000) + g.DumpWithType(v.Uint()) + + // Output: + // uint(1000) +} +``` + + +### `Float32` + +- 说明: `Float32` 将 `v` 转换为 `32位` 浮点型。 +- 格式: + +```go +func (v *Var) Float32() float32 +``` + +- 示例: + +```go +// Float32 +func ExampleVar_Float32() { + var price = gvar.New(100.00) + g.DumpWithType(price.Float32()) + + // Output: + // float32(100) +} +``` + + +### `Float64` + +- 说明: `Float64` 将 `v` 转换为 `64位` 浮点型。 +- 格式: + +```go +func (v *Var) Float64() float64 +``` + +- 示例: + +```go +// Float32 +func ExampleVar_Float64() { + var price = gvar.New(100.00) + g.DumpWithType(price.Float64()) + + // Output: + // float64(100) +} +``` + + +### `Time` + +- 说明:`Time` 将 `v` 转换为 `time.Time`。参数`format`用 `gtime` 指定时间字符串的格式,例如: `Y-m-d H:i:s`。 +- 格式: + +```go +func (v *Var) Time(format ...string) time.Time +``` + +- 示例: + +```go +// Time +func ExampleVar_Time() { + var v = gvar.New("2021-11-11 00:00:00") + g.DumpWithType(v.Time()) + + // Output: + // time.Time(29) "2021-11-11 00:00:00 +0800 CST" +} +``` + + +### `GTime` + +- 说明: `G``Time` 将 `v` 转换为 `*gtime.Time`。参数`format`用`gtime` 指定时间字符串的格式,例如: `Y-m-d H:i:s`。 +- 格式: + +```go +func (v *Var) GTime(format ...string) *gtime.Time +``` + +- 示例: + +```go +// GTime +func ExampleVar_GTime() { + var v = gvar.New("2021-11-11 00:00:00") + g.DumpWithType(v.GTime()) + + // Output: + // *gtime.Time(19) "2021-11-11 00:00:00" +} +``` + + +### `Duration` + +- 说明: `Duration`将 `v` 转换为 `time.Duration`。如果 `v` 的值是字符串,那么它使用 `time.ParseDuration`进行转换。 +- 格式: + +```go +func (v *Var) Duration() time.Duration +``` + +- 示例: + +```go +// Duration +func ExampleVar_Duration() { + var v = gvar.New("300s") + g.DumpWithType(v.Duration()) + + // Output: + // time.Duration(4) "5m0s" +} +``` + + +### `MarshalJSON` + +- 说明:`MarshalJSON`实现了`json`接口的`MarshalJSON`。 +- 格式: + +```go +func (v *Var) MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +// MarshalJSON +func ExampleVar_MarshalJSON() { + testMap := g.Map{ + "code": "0001", + "name": "Golang", + "count": 10, + } + + var v = gvar.New(testMap) + res, err := json.Marshal(&v) + if err != nil { + panic(err) + } + g.DumpWithType(res) + + // Output: + // []byte(42) "{"code":"0001","count":10,"name":"Golang"}" +} +``` + + +### `UnmarshalJSON` + +- 说明:`UnmarshalJSON ` 实现了 `json` 接口的 ` UnmarshalJSON`。 +- 格式: + +```go +func (v *Var) UnmarshalJSON(b []byte) error +``` + +- 示例: + +```go +// UnmarshalJSON +func ExampleVar_UnmarshalJSON() { + tmp := []byte(`{ + "Code": "0003", + "Name": "Golang Book3", + "Quantity": 3000, + "Price": 300, + "OnSale": true + }`) + var v = gvar.New(map[string]interface{}{}) + if err := json.Unmarshal(tmp, &v); err != nil { + panic(err) + } + + g.Dump(v) + + // Output: + // "{\"Code\":\"0003\",\"Name\":\"Golang Book3\",\"OnSale\":true,\"Price\":300,\"Quantity\":3000}" +} +``` + + +### `UnmarshalValue` + +- 说明:`UnmarshalValue`是一个接口实现,它为 `Var` 设置任何类型的值。 +- 格式: + +```go +func (v *Var) UnmarshalValue(value interface{}) error +``` + +- 示例: + +```go +// UnmarshalValue +func ExampleVar_UnmarshalValue() { + tmp := g.Map{ + "code": "00002", + "name": "GoFrame", + "price": 100, + "sale": true, + } + + var v = gvar.New(map[string]interface{}{}) + if err := v.UnmarshalValue(tmp); err != nil { + panic(err) + } + g.Dump(v) + + // Output: + // "{\"code\":\"00002\",\"name\":\"GoFrame\",\"price\":100,\"sale\":true}" +} +``` + + +### `IsNil` + +- 说明:`IsNil`判断 `v` 是否为 `nil`,为 `nil` 返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsNil() bool +``` + +- 示例: + +``` +/// IsNil +func ExampleVar_IsNil() { + g.Dump(gvar.New(0).IsNil()) + g.Dump(gvar.New(0.1).IsNil()) + // true + g.Dump(gvar.New(nil).IsNil()) + g.Dump(gvar.New("").IsNil()) + + // Output: + // false + // false + // true + // false +} +``` + + +### `IsEmpty` + +- 说明:`IsEmpty` 判断 `v` 是否为空,为空返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsEmpty() bool +``` + +- 示例: + +```go +// IsEmpty +func ExampleVar_IsEmpty() { + g.Dump(gvar.New(0).IsEmpty()) + g.Dump(gvar.New(nil).IsEmpty()) + g.Dump(gvar.New("").IsEmpty()) + g.Dump(gvar.New(g.Map{"k": "v"}).IsEmpty()) + + // Output: + // true + // true + // true + // false +} +``` + + +### `IsInt` + +- 说明:`IsInt`判断 `v` 是否为`int`类型,如果是 `int` 类型返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsInt() bool +``` + +- 示例: + +```go +// IsInt +func ExampleVar_IsInt() { + g.Dump(gvar.New(0).IsInt()) + g.Dump(gvar.New(0.1).IsInt()) + g.Dump(gvar.New(nil).IsInt()) + g.Dump(gvar.New("").IsInt()) + + // Output: + // true + // false + // false + // false +} +``` + + +### `IsUint` + +- 说明: `IsUint`判断 `v` 是否为 `uint` 类型,如果是 `uint` 类型返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsUint() bool +``` + +- 示例: + +```go +// IsUint +func ExampleVar_IsUint() { + g.Dump(gvar.New(0).IsUint()) + g.Dump(gvar.New(uint8(8)).IsUint()) + g.Dump(gvar.New(nil).IsUint()) + + // Output: + // false + // true + // false +} +``` + + +### `IsFloat` + +- 说明:`IsFloat` 判断 `v` 是否为 `float` 类型,如果是 `float` 类型返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsFloat() bool +``` + +- 示例: + +```go +// IsFloat +func ExampleVar_IsFloat() { + g.Dump(g.NewVar(uint8(8)).IsFloat()) + g.Dump(g.NewVar(float64(8)).IsFloat()) + g.Dump(g.NewVar(0.1).IsFloat()) + + // Output: + // false + // true + // true +} +``` + + +### `IsSlice` + +- 说明:`IsSlice` 判断 `v` 是否为切片类型,如果是 `slice` 类型返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsSlice() bool +``` + +- 示例: + +```go +// IsSlice +func ExampleVar_IsSlice() { + g.Dump(g.NewVar(0).IsSlice()) + g.Dump(g.NewVar(g.Slice{0}).IsSlice()) + + // Output: + // false + // true +} +``` + + +### `IsMap` + +- 说明:`IsMap` 判断 `v` 是否为映射类型,如果是 `map` 类型返回`true`,反之返回`false`。 +- 格式: + +```go +func (v *Var) IsMap() bool +``` + +- 示例: + +```go +// IsMap +func ExampleVar_IsMap() { + g.Dump(g.NewVar(0).IsMap()) + g.Dump(g.NewVar(g.Map{"k": "v"}).IsMap()) + g.Dump(g.NewVar(g.Slice{}).IsMap()) + + // Output: + // false + // true + // false +} +``` + + +### `IsStruct` + +- 说明:`IsStruct` 判断 `v` 是否为结构体类型,如果是 `struct` 类型返回 `true`,反之返回 `false`。 +- 格式: + +```go +func (v *Var) IsStruct() bool +``` + +- 示例: + +```go +// IsStruct +func ExampleVar_IsStruct() { + g.Dump(g.NewVar(0).IsStruct()) + g.Dump(g.NewVar(g.Map{"k": "v"}).IsStruct()) + + a := struct{}{} + g.Dump(g.NewVar(a).IsStruct()) + g.Dump(g.NewVar(&a).IsStruct()) + + // Output: + // false + // false + // true + // true +} +``` + + +### `ListItemValues` + +- 说明:`ListItemValues`用键 `key` 检索并返回所有项结构/映射的元素。注意,参数 `list` 应该是切片类型,其中包含`map`或`struct`的元素,否则它会返回一个空切片。 +- 格式: + +```go +func (v *Var) ListItemValues(key interface{}) (values []interface{}) +``` + +- 示例: + +```go +// ListItemValues +func ExampleVar_ListItemValues() { + var goods1 = g.List{ + g.Map{"id": 1, "price": 100.00}, + g.Map{"id": 2, "price": 0}, + g.Map{"id": 3, "price": nil}, + } + var v = gvar.New(goods1) + fmt.Println(v.ListItemValues("id")) + fmt.Println(v.ListItemValues("price")) + + // Output: + // [1 2 3] + // [100 0 ] +} +``` + + +### `ListItemValuesUnique` + +- 说明:`ListItemValuesUnique`检索并返回所有带有指定 `key` 的`struct/map`的唯一元素。注意,参数 `list` 应该是切片类型,其中包含 `map` 或`struct ` 的元素,否则它会返回一个空切片。 +- 格式: + +```go +func (v *Var) ListItemValuesUnique(key string) []interface{} +``` + +- 示例: + +```go +// ListItemValuesUnique +func ExampleVar_ListItemValuesUnique() { + var ( + goods1 = g.List{ + g.Map{"id": 1, "price": 100.00}, + g.Map{"id": 2, "price": 100.00}, + g.Map{"id": 3, "price": nil}, + } + v = gvar.New(goods1) + ) + + fmt.Println(v.ListItemValuesUnique("id")) + fmt.Println(v.ListItemValuesUnique("price")) + + // Output: + // [1 2 3] + // [100 ] +} +``` + + +### `Struct` + +- 说明:`Struct`将`v`的值映射到“指针”。参数`pointer`应该是指向结构体实例的指针。参数 `mapping` 用于指定键到属性的映射规则。 +- 格式: + +```go +func (v *Var) Struct(pointer interface{}, mapping ...map[string]string) error +``` + +- 示例: + +```go +func ExampleVar_Struct() { + params1 := g.Map{ + "uid": 1, + "Name": "john", + } + v := gvar.New(params1) + type tartget struct { + Uid int + Name string + } + t := new(tartget) + if err := v.Struct(&t); err != nil { + panic(err) + } + g.Dump(t) + + // Output: + // { + // Uid: 1, + // Name: "john", + // } +} +``` + + +### `Structs` + +- 说明:`Structs`将 `v` 转换为给定结构体的切片类型。参数`pointer`应该是指向结构体实例的指针。参数`mapping`用于指定键到属性的映射规则。 +- 格式: + +```go +func (v *Var) Structs(pointer interface{}, mapping ...map[string]string) error +``` + +- 示例: + +```go +func ExampleVar_Structs() { + paramsArray := []g.Map{} + params1 := g.Map{ + "uid": 1, + "Name": "golang", + } + params2 := g.Map{ + "uid": 2, + "Name": "java", + } + + paramsArray = append(paramsArray, params1, params2) + v := gvar.New(paramsArray) + type tartget struct { + Uid int + Name string + } + var t []tartget + if err := v.Structs(&t); err != nil { + panic(err) + } + g.DumpWithType(t) + + // Output: + // []gvar_test.tartget(2) [ + // gvar_test.tartget(2) { + // Uid: int(1), + // Name: string(6) "golang", + // }, + // gvar_test.tartget(2) { + // Uid: int(2), + // Name: string(4) "java", + // }, + // ] +} +``` + + +### `Ints` + +- 说明:`Ints`将 `v` 转换为 `[]int`。 +- 格式: + +```go +func (v *Var) Ints() []int +``` + +- 示例: + +```go +// Ints +func ExampleVar_Ints() { + var ( + arr = []int{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Ints()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Int64s` + +- 说明:`Int64s`将 `v` 转换为`[]64int`。 +- 格式: + +```go +func (v *Var) Int64s() []64int +``` + +- 示例: + +```go +// Int64s +func ExampleVar_Int64s() { + var ( + arr = []int64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Int64s()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Uints` + +- 说明: `Uints`将 `v` 转换为`[]uint`。 +- 格式: + +```go +func (v *Var) Uints() []uint +``` + +- 示例: + +```go +// Uints +func ExampleVar_Uints() { + var ( + arr = []uint{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + fmt.Println(obj.Uints()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Uint64s` + +- 说明: `Uint64s`将 `v` 转换为`[]uint64`。 +- 格式: + +```go +func (v *Var) Uint64s() []uint64 +``` + +- 示例: + +```go +// Uint64s +func ExampleVar_Uint64s() { + var ( + arr = []uint64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Uint64s()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Floats` + +- 说明: `Floats``Float64s`的别名。 +- 格式: + +```go +func (v *Var) Floats() []float64 +``` + +- 示例: + +```go +// Floats +func ExampleVar_Floats() { + var ( + arr = []float64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Floats()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Float64s` + +- 说明: `Float64s`将 `v` 转换为 `[]float64`。 +- 格式: + +```go +func (v *Var) Float64s() []float64 +``` + +- 示例: + +```go +// Float64s +func ExampleVar_Float64s() { + var ( + arr = []float64{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Float64s()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Float32s` + +- 说明: `Float32s`将 `v` 转换为 `[]float32`。 +- 格式: + +```go +func (v *Var) Float32s() []float32 +``` + +- 示例: + +```go +// Float32s +func ExampleVar_Float32s() { + var ( + arr = []float32{1, 2, 3, 4, 5} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Float32s()) + + // Output: + // [1 2 3 4 5] +} +``` + + +### `Strings` + +- 说明: `Strings`将 `v` 转换为`[]string`。 +- 格式: + +```go +func (v *Var) Strings() []string +``` + +- 示例: + +```go +// Strings +func ExampleVar_Strings() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + fmt.Println(obj.Strings()) + + // Output: + // [GoFrame Golang] +} +``` + + +### `Interfaces` + +- 说明: `Interfaces`将 `v` 转换为 `[]interface{}`。 +- 格式: + +```go +func (v *Var) Interfaces() []interface{} +``` + +- 示例: + +```go +// Interfaces +func ExampleVar_Interfaces() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Interfaces()) + + // Output: + // [GoFrame Golang] +} +``` + + +### `Slice` + +- 说明: `Slice``Interfaces` 的别名。 +- 格式: + +```go +func (v *Var) Slice() []interface{} +``` + +- 示例: + +```go +// Slice +func ExampleVar_Slice() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Slice()) + + // Output: + // [GoFrame Golang] +} +``` + + +### `Array` + +- 说明: `Array``Interfaces` 的别名。 +- 格式: + +```go +func (v *Var) Array() []interface{} +``` + +- 示例: + +```go +// Array +func ExampleVar_Array() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + fmt.Println(obj.Array()) + + // Output: + // [GoFrame Golang] +} +``` + + +### `Vars` + +- 说明: `Vars`将 `v` 转换为 `[]var`。 +- 格式: + +```go +func (v *Var) Vars() []*Var +``` + +- 示例: + +```go +// Vars +func ExampleVar_Vars() { + var ( + arr = []string{"GoFrame", "Golang"} + obj = gvar.New(arr) + ) + + fmt.Println(obj.Vars()) + + // Output: + // [GoFrame Golang] +} +``` + + +### `Map` + +- 说明: `Map`将 `v` 转换为 `map[string]interface{}`。 +- 格式: + +```go +func (v *Var) Map(tags ...string) map[string]interface{} +``` + +- 示例: + +```go +// Map +func ExampleVar_Map() { + var ( + m = g.Map{"id": 1, "price": 100.00} + v = gvar.New(m) + res = v.Map() + ) + + fmt.Println(res["id"], res["price"]) + + // Output: + // 1 100 +} +``` + + +### `MapStrAny` + +- 说明: `MapStrAny`类似于 `Map` 函数,但是实现了 `MapStrAny` 接口。 +- 格式: + +```go +func (v *Var) MapStrAny() map[string]interface{} +``` + +- 示例: + +```go +// MapStrAny +func ExampleVar_MapStrAny() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrAny() + ) + + fmt.Println(v2["price"], v2["id"]) + + // Output: + // 100 1 +} +``` + + +### `MapStrStr` + +- 说明: `MapStrStr`将 `v` 转换为 `map[string]string`。 +- 格式: + +```go +func (v *Var) MapStrStr(tags ...string) map[string]string +``` + +- 示例: + +```go +// MapStrStr +func ExampleVar_MapStrStr() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrStr() + ) + + fmt.Println(v2["price"] + "$") + + // Output: + // 100$ +} +``` + + +### `MapStrVar` + +- 说明: `MapStrVar`将 `v` 转换为 `map[string]*Var`。 +- 格式: + +```go +func (v *Var) MapStrVar(tags ...string) map[string]*Var +``` + +- 示例: + +```go +// MapStrVar +func ExampleVar_MapStrVar() { + var ( + m1 = g.Map{"id": 1, "price": 100} + v = gvar.New(m1) + v2 = v.MapStrVar() + ) + + fmt.Println(v2["price"].Float64() * 100) + + // Output: + // 10000 +} +``` + + +### `MapDeep` + +- 说明: `MapDeep`将 `v` 递归转换为 `map[string]interface{}`。 +- 格式: + +```go +func (v *Var) MapDeep(tags ...string) map[string]interface{} +``` + +- 示例: + +```go +// MapDeep +func ExampleVar_MapDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // map[id:1 price:100] +} +``` + + +### `MapStrStrDeep` + +- 说明: `MapStrStrDeep`将 `v` 递归转换为 `map[string]string`。 +- 格式: + +```go +func (v *Var) MapStrStrDeep(tags ...string) map[string]string +``` + +- 示例: + +```go +// MapStrStrDeep +func ExampleVar_MapStrStrDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapStrStrDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // {"id":1,"price":100} +} +``` + + +### `MapStrVarDeep` + +- 说明: `MapStrVarDeep`将 `v` 递归转换为 `map[string]*Var`。 +- 格式: + +```go +func (v *Var) MapStrVarDeep(tags ...string) map[string]*Var +``` + +- 示例: + +```go +// MapStrVarDeep +func ExampleVar_MapStrVarDeep() { + var ( + m1 = g.Map{"id": 1, "price": 100} + m2 = g.Map{"product": m1} + v = gvar.New(m2) + v2 = v.MapStrVarDeep() + ) + + fmt.Println(v2["product"]) + + // Output: + // {"id":1,"price":100} +} +``` + + +### `Maps` + +- 说明: `Maps`将 `v` 转换为 `map[string]interface{}`。 +- 格式: + +```go +func (v *Var) Maps(tags ...string) []map[string]interface{} +``` + +- 示例: + +```go +// Maps +func ExampleVar_Maps() { + var m = gvar.New(g.ListIntInt{g.MapIntInt{0: 100, 1: 200}, g.MapIntInt{0: 300, 1: 400}}) + fmt.Printf("%#v", m.Maps()) + + // Output: + // []map[string]interface {}{map[string]interface {}{"0":100, "1":200}, map[string]interface {}{"0":300, "1":400}} +} +``` + + +### `MapsDeep` + +- 说明: `MapsDeep`将 `v` 递归转换为 `[]map[string]interface{}`。 +- 格式: + +```go +func (v *Var) MapsDeep(tags ...string) []map[string]interface{} +``` + +- 示例: + +```go +// MapsDeep +func ExampleVar_MapsDeep() { + var ( + p1 = g.MapStrAny{"product": g.Map{"id": 1, "price": 100}} + p2 = g.MapStrAny{"product": g.Map{"id": 2, "price": 200}} + v = gvar.New(g.ListStrAny{p1, p2}) + v2 = v.MapsDeep() + ) + + fmt.Printf("%#v", v2) + + // Output: + // []map[string]interface {}{map[string]interface {}{"product":map[string]interface {}{"id":1, "price":100}}, map[string]interface {}{"product":map[string]interface {}{"id":2, "price":200}}} +} +``` + + +### `MapToMap` + +- 说明: `MapToMap`将 `v` 转换为 `pointer` 指定的 `map` 类型,`mapping` 为指定的映射规则。 +- 格式: + +```go +func (v *Var) MapToMap(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- 示例: + +```go +// MapToMap +func ExampleVar_MapToMap() { + var ( + m1 = gvar.New(g.MapIntInt{0: 100, 1: 200}) + m2 = g.MapStrStr{} + ) + + err := m1.MapToMap(&m2) + if err != nil { + panic(err) + } + + fmt.Printf("%#v", m2) + + // Output: + // map[string]string{"0":"100", "1":"200"} +} +``` + + +### `MapToMaps` + +- 说明: `MapToMaps`将 `v` 转换为 `pointer`指定的 `map` 类型,`mapping` 为指定的映射规则。 +- 格式: + +```go +func (v *Var) MapToMaps(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- 示例: + +```go +// MapToMaps +func ExampleVar_MapToMaps() { + var ( + p1 = g.MapStrAny{"product": g.Map{"id": 1, "price": 100}} + p2 = g.MapStrAny{"product": g.Map{"id": 2, "price": 200}} + v = gvar.New(g.ListStrAny{p1, p2}) + v2 []g.MapStrStr + ) + + err := v.MapToMaps(&v2) + if err != nil { + panic(err) + } + fmt.Printf("%#v", v2) + + // Output: + // []map[string]string{map[string]string{"product":"{\"id\":1,\"price\":100}"}, map[string]string{"product":"{\"id\":2,\"price\":200}"}} +} +``` + + +### `MapToMapsDeep` + +- 说明: `MapToMaps`将 `v` 递归地转换为`pointer`指定的 `map` 类型,`mapping` 为指定的映射规则。 +- 格式: + +```go +func (v *Var) MapToMapsDeep(pointer interface{}, mapping ...map[string]string) (err error) +``` + +- 示例: + +```go +// MapToMapDeep +func ExampleVar_MapToMapDeep() { + var ( + p1 = gvar.New(g.MapStrAny{"product": g.Map{"id": 1, "price": 100}}) + p2 = g.MapStrAny{} + ) + + err := p1.MapToMap(&p2) + if err != nil { + panic(err) + } + fmt.Printf("%#v", p2) + + // Output: + // map[string]interface {}{"product":map[string]interface {}{"id":1, "price":100}} +} +``` + + +### `Scan` + +- 说明: `Scan`会自动检查 `pointer` 的类型,并将 `params` 转换为 `pointer`,`pointer` 支持的类型为: +`*map、 *[]map、 *[]*map、 *struct、 **struct、 *[]struct、 *[]*struct` + +- 格式: + +```go +func (v *Var) Scan(pointer interface{}, mapping ...map[string]string) error +``` + +- 示例: + +```go +// Scan +func ExampleVar_Scan() { + type Student struct { + Id *g.Var + Name *g.Var + Scores *g.Var + } + var ( + s Student + m = g.Map{ + "Id": 1, + "Name": "john", + "Scores": []int{100, 99, 98}, + } + ) + if err := gconv.Scan(m, &s); err != nil { + panic(err) + } + + g.DumpWithType(s) + + // Output: + // gvar_test.Student(3) { + // Id: *gvar.Var(1) "1", + // Name: *gvar.Var(4) "john", + // Scores: *gvar.Var(11) "[100,99,98]", + // } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" new file mode 100644 index 00000000000..f02a64322a8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\346\263\233\345\236\213\347\261\273\345\236\213-gvar/\346\263\233\345\236\213\347\261\273\345\236\213-\346\263\250\346\204\217\344\272\213\351\241\271.md" @@ -0,0 +1,41 @@ +--- +slug: '/docs/components/container-gvar-notice' +title: '泛型类型-注意事项' +sidebar_position: 2 +hide_title: true +description: '使用GoFrame框架中的泛型类型的注意事项。尽管泛型提高了开发便捷性,但在复杂业务项目中可能影响长期维护。建议在基础组件和中间件项目中使用泛型,同时明确业务模型的数据类型以发挥编译型语言的优势。' +keywords: [GoFrame,GoFrame框架,泛型类型,业务模型,长期维护,基础组件,中间件项目,编译型语言,数据类型,类型检查] +--- + +## 注意事项 + +虽然框架提供的泛型类型极大提高的开发的简便性,但对于业务模型来说应当慎重使用(不能滥用),因为泛型类型将会掩盖真实的数据类型,这对于业务项目长期维护来说弊大于利,特别是复杂的业务项目。业务模型的数据类型定义应当尽可能地明确、有意义、不可变,才有利于编译型语言在编译阶段做类型检查和优化、有利于业务后续长期维护。 + +举个例子,以下是社区热心小伙伴提供的真实业务模型案例: + +```go +type MiDispatchData struct { + Status *g.Var `json:"status"` + BrandId *g.Var `json:"brand_id"` + AreaId *g.Var `json:"area_id"` + Year *g.Var `json:"year"` + Month *g.Var `json:"month"` + Day *g.Var `json:"day"` + Hour *g.Var `json:"hour"` + RequestTime *g.Var `json:"request_time"` + Source *g.Var `json:"source"` + BikeId *g.Var `json:"bike_id"` + BikeType *g.Var `json:"bike_type"` + Lon *g.Var `json:"lon"` + Lat *g.Var `json:"lat"` + SiteId *g.Var `json:"site_id"` + BikeMac *g.Var `json:"bike_mac"` +} +``` + +虽然这样的做法程序也能正常运行,业务场景也能正常覆盖。但却丢失了编译型语言的编译器优势(这就类似于PHP变量了),在后期项目维护时很难再确定字段的数据类型。 + +## 使用建议 + +- 泛型在基础组件、中间件项目中使用较多。 +- 如果字段在业务场景中有多重含义、类型,那么可以使用泛型代替如 `interface{}` 类型。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" new file mode 100644 index 00000000000..31a6ea9282e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-glist.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/components/container-glist' +title: '链表类型-glist' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,链表,双向链表,并发安全,glist,组件,Go语言,编程,数据结构] +description: 'GoFrame框架中的glist组件,支持并发安全的双向链表。glist提供了链表的数据结构和并发控制,适用于需要使用双向链表的场景,从而提高Go语言程序的开发效率和运行性能。' +--- + +## 基本介绍 + +带并发安全开关的双向列表。 + +**使用场景**: + +双向链表。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/container/glist" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/glist](https://pkg.go.dev/github.com/gogf/gf/v2/container/glist) + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..44470ebc036 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,358 @@ +--- +slug: '/docs/components/container-glist-example' +title: '链表类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,链表,glist,并发安全,数据结构,JSON序列化,Go语言,容器操作,编程示例] +description: '使用GoFrame框架中的glist容器进行链表操作,包括基本使用、链表遍历、元素入栈与出栈、插入与移动操作、串联与移除操作以及JSON序列化反序列化。示例代码展示了在非并发安全与并发安全场景下的不同操作,帮助理解Go语言中链表的应用。' +--- + +### 基础使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" +) + +func main() { + // Not concurrent-safe in default. + l := glist.New() + + // Push + l.PushBack(1) //从后面插入值 + l.PushBack(2) //从后面插入值 + e := l.PushFront(0) //从前面插入值 + + // Insert + l.InsertBefore(e, -1) //从0的前面插入值 + l.InsertAfter(e, "a") //从0的后面插入值 + fmt.Println(l) + + // Pop Pop 出栈后,从list里移除 + fmt.Println(l.PopFront()) // 从前面出栈,返回出栈的值 + fmt.Println(l.PopBack()) //从后面出栈 + fmt.Println(l) + + // All + fmt.Println(l.FrontAll()) //正序返回一个复本 + fmt.Println(l.BackAll()) //逆序返回一个复本 + + // Output: + // [-1,0,"a",1,2] + // -1 + // 2 + // [0,"a",1] + // [0 "a" 1] + // [1 "a" 0] +} +``` + +### 链表遍历 + +该示例中我们将通过读锁和写锁遍历一个并发安全的链表,分别通过 `RLockFunc` 和 `LockFunc` 实现。执行后,输出结果为: + +```go +package main + +import ( + "container/list" + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/container/glist" +) + +func main() { + // concurrent-safe list. + l := glist.NewFrom(garray.NewArrayRange(1, 9, 1).Slice(), true) + fmt.Println(l) + // iterate reading from head. + l.RLockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() { + fmt.Print(e.Value) + } + } + }) + fmt.Println() + // iterate reading from tail. + l.RLockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Back(); i < length; i, e = i+1, e.Prev() { + fmt.Print(e.Value) + } + } + }) + + fmt.Println() + + // iterate reading from head using IteratorAsc. + l.IteratorAsc(func(e *glist.Element) bool { + fmt.Print(e.Value) + return true + }) + fmt.Println() + // iterate reading from tail using IteratorDesc. + l.IteratorDesc(func(e *glist.Element) bool { + fmt.Print(e.Value) + return true + }) + + fmt.Println() + + // iterate writing from head. + l.LockFunc(func(list *list.List) { + length := list.Len() + if length > 0 { + for i, e := 0, list.Front(); i < length; i, e = i+1, e.Next() { + if e.Value == 6 { + e.Value = "M" + break + } + } + } + }) + fmt.Println(l) + + // Output: + // [1,2,3,4,5,6,7,8,9] + // 123456789 + // 987654321 + // 123456789 + // 987654321 + // [1,2,3,4,5,M,7,8,9] +``` + +### `Push*` 元素项入栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5}) + + l.PushBack(6) + fmt.Println(l) + + l.PushFront(0) + fmt.Println(l) + + // 正数从右边入栈 + l.PushBacks(g.Slice{7, 8}) + fmt.Println(l) + + // 负数从左边入栈 + l.PushFronts(g.Slice{-1, -2}) + fmt.Println(l) + + l.PushFrontList(glist.NewFrom(g.Slice{"a", "b", "c"})) + l.PushBackList(glist.NewFrom(g.Slice{"d", "e", "f"})) + fmt.Println(l) + + // Output: + // [1,2,3,4,5,6] + // [0,1,2,3,4,5,6] + // [0,1,2,3,4,5,6,7,8] + // [-2,-1,0,1,2,3,4,5,6,7,8] + // ["a","b","c",-2,-1,0,1,2,3,4,5,6,7,8,"d","e","f"] + +} +``` + +### `Pop*` 元素项出栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + fmt.Println(l.PopBack()) + fmt.Println(l.PopBacks(2)) + fmt.Println(l.PopFront()) + fmt.Println(l.PopFronts(2)) + + fmt.Println(glist.NewFrom(g.Slice{"a", "b", "c", "d"}).PopFrontAll()) + fmt.Println(glist.NewFrom(g.Slice{"a", "b", "c", "d"}).PopBackAll()) + + // Output: + // 9 + // [8 7] + // 1 + // [2 3] + // [4,5,6] + // [a b c d] + // [d c b a] +} +``` + +### `Move*/Insert*` 元素项移动、插入 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7, 8, 9}) + + l.MoveToBack(l.Front()) //将第一个元素(1)移动最右边 [2,3,4,5,6,7,8,9,1] + l.MoveToFront(l.Back().Prev()) //将最后一项的前一个元素(9)移动最左边 [9,2,3,4,5,6,7,8,1] + fmt.Println(l) + + // 将2到栈首元素的前面 + l.MoveBefore(l.Front().Next(), l.Front()) + // 将8到栈尾元素的后面 + l.MoveAfter(l.Back().Prev(), l.Back()) + fmt.Println(l) + + // 在栈尾元素前插入新元素 + l.InsertBefore(l.Back(), "a") + // 在栈首元素后插入新元素 + l.InsertAfter(l.Front(), "b") + + // Output: + // [9,2,3,4,5,6,7,8,1] + // [2,9,3,4,5,6,7,1,8] + // [2,"b",9,3,4,5,6,7,1,"a",8] +} +``` + +### `Join` 元素项串连 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var l glist.List + l.PushBacks(g.Slice{"a", "b", "c", "d"}) + + fmt.Println(l.Join(",")) + + // Output: + // a,b,c,d +} +``` + +### `Remove*` 元素项移除 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + l := glist.NewFrom(g.Slice{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}) + fmt.Println(l) + + fmt.Println(l.Remove(l.Front())) + fmt.Println(l) + + l.Removes([]*glist.Element{l.Front(), l.Front().Next()}) + fmt.Println(l) + + l.RemoveAll() + fmt.Println(l) + + // Output: + // [0,1,2,3,4,5,6,7,8,9] + // 0 + // [1,2,3,4,5,6,7,8,9] + // [3,4,5,6,7,8,9] + // [] } +``` + +### `JSON` 序列化/反序列 + +`glist` 容器实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +- `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/glist" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + type Student struct { + Id int + Name string + Scores *glist.List + } + s := Student{ + Id: 1, + Name: "john", + Scores: glist.NewFrom(g.Slice{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // Output: + // {"Id":1,"Name":"john","Scores":[100,99,98]} +} +``` + + +- `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/glist" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *glist.List + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // Output: + // {1 john [100,99,98]} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..ad0733913bd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\223\276\350\241\250\347\261\273\345\236\213-glist/\351\223\276\350\241\250\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,22 @@ +--- +slug: '/docs/components/container-glist-benchmark' +title: '链表类型-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,链表性能测试,Go语言,性能基准,PushBack,PushFront,Len,PopFront,PopBack] +description: '在GoFrame框架下,链表(container/glist)的性能测试结果。通过一系列基准测试,包括PushBack、PushFront、Len、PopFront和PopBack,评估了链表操作的效率和性能,以帮助开发者优化代码性能。' +--- + +[https://github.com/gogf/gf/blob/master/container/glist/glist\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/glist/glist_z_bench_test.go) + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/glist +Benchmark_PushBack-4 5000000 268 ns/op 56 B/op 2 allocs/op +Benchmark_PushFront-4 10000000 435 ns/op 56 B/op 2 allocs/op +Benchmark_Len-4 30000000 44.5 ns/op 0 B/op 0 allocs/op +Benchmark_PopFront-4 20000000 71.1 ns/op 0 B/op 0 allocs/op +Benchmark_PopBack-4 30000000 70.1 ns/op 0 B/op 0 allocs/op +PASS +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" new file mode 100644 index 00000000000..84e6474b4af --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue.md" @@ -0,0 +1,32 @@ +--- +slug: '/docs/components/container-gqueue' +title: '队列类型-gqueue' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,gqueue,队列,并发安全,动态队列,固定队列,goroutine,数据通信,Go语言] +description: 'GoFrame框架中的动态大小并发安全队列gqueue,其支持固定和动态大小队列的功能,与标准库的channel效率相当。gqueue特别适合于多goroutine之间的数据通信,并为开发者提供了简便且强大的并发处理能力。' +--- + +## 基本介绍 + +动态大小的并发安全队列。同时, `gqueue` 也支持固定队列大小,固定队列大小时队列效率和标准库的 `channel` 无异。 + +**使用场景**: + +该队列是并发安全的,常用于多 `goroutine` 数据通信且支持动态队列大小的场景。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gqueue" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/container/gqueue](https://pkg.go.dev/github.com/gogf/gf/v2/container/gqueue) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..15e5f6b3559 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,185 @@ +--- +slug: '/docs/components/container-gqueue-example' +title: '队列类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,队列,gqueue,gtimer,Pop,Push,队列长度,队列关闭,glist] +description: '使用GoFrame框架中的gqueue组件进行基本的队列操作,包括元素的入队和出队、队列长度的获取以及队列的关闭。详细演示了通过Push和Pop方法管理队列元素,并展示了队列与glist链表的关系,确保在GoFrame框架下高效构建并发安全的程序逻辑。' +--- + +## 基本使用 + +### 使用 `Queue.Pop` + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + // 数据生产者,每隔1秒往队列写数据 + gtimer.SetInterval(time.Second, func() { + v := gtime.Now().String() + q.Push(v) + fmt.Println("Push:", v) + }) + + // 3秒后关闭队列 + gtimer.SetTimeout(3*time.Second, func() { + q.Close() + }) + + // 消费者,不停读取队列数据并输出到终端 + for { + if v := q.Pop(); v != nil { + fmt.Println(" Pop:", v) + } else { + break + } + } + + // 第3秒时关闭队列,这时程序立即退出,因此结果中只会打印2秒的数据。 执行后,输出结果为: + // Output: + // Push: 2021-09-07 14:03:00 + // Pop: 2021-09-07 14:03:00 + // Push: 2021-09-07 14:03:01 + // Pop: 2021-09-07 14:03:01 +} +``` + +### 使用 `Queue.C` + +```go +package main + +import ( + "context" + "fmt" + "time" + + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + "github.com/gogf/gf/v2/container/gqueue" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + queue := gqueue.New() + gtimer.AddTimes(gctx.GetInitCtx(), time.Second, 3, func(ctx context.Context) { + queue.Push(gtime.Now().String()) + }) + for { + select { + case queueItem := <-queue.C: + fmt.Println(queueItem) + + case <-time.After(3 * time.Second): + fmt.Println("timeout, exit loop") + return + } + } +} +``` + +## 元素入队/出队 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + for i := 0; i < 10; i++ { + q.Push(i) + } + + fmt.Println(q.Pop()) + fmt.Println(q.Pop()) + fmt.Println(q.Pop()) + + // Output: + // 0 + // 1 + // 2 +} +``` + +## 队列长度 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + q.Push(1) + q.Push(2) + + fmt.Println(q.Len()) + // size是len方法的别称 + fmt.Println(q.Size()) + + // May Output: + // 2 + // 2 +} +``` + +## 队列关闭 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/v2/container/gqueue" +) + +func main() { + q := gqueue.New() + + for i := 0; i < 10; i++ { + q.Push(i) + } + + fmt.Println(q.Pop()) + q.Close() + fmt.Println(q.Pop()) + fmt.Println(q.Len()) + + // Output: + // 0 + // + // 0 +} +``` + +## `gqueue` 与 `glist` + +`gqueue` 的底层基于 `glist` 链表实现动态大小特性,在队列满写入数据或在队列空时读取数据会产生阻塞。 + +`glist` 是一个并发安全的链表,并可以允许在关闭并发安全特性的时和一个普通的 `list` 链表无异,在存储和读取数据时不会发生阻塞。 diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..0d7ef13c4dc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\230\237\345\210\227\347\261\273\345\236\213-gqueue/\351\230\237\345\210\227\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,25 @@ +--- +slug: '/docs/components/container-gqueue-benchmark' +title: '队列类型-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gqueue,性能测试,队列类型,channel,基准测试,动态队列,队列性能,benchmark] +description: '在GoFrame框架中gqueue与标准库channel的性能测试。通过基准测试展示了gqueue在动态存储和弹性容量上的优势,相对于channel的固定内存分配和容量限制,gqueue在创建效率和灵活性表现更佳。' +--- + +[https://github.com/gogf/gf/blob/master/container/gqueue/gqueue\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/gqueue/gqueue_z_bench_test.go) + +`gqueue` 与标准库 `channel` 的性能基准测试,其中每一次基准测试的 `b.N` 值均为 `20000000`,以保证动态队列存取一致防止 `deadlock`: + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/container/gqueue +Benchmark_Gqueue_StaticPushAndPop-4 20000000 84.2 ns/op +Benchmark_Gqueue_DynamicPush-4 20000000 164 ns/op +Benchmark_Gqueue_DynamicPop-4 20000000 121 ns/op +Benchmark_Channel_PushAndPop-4 20000000 70.0 ns/op +PASS +``` + +可以看到标准库的 `channel` 的读写性能是非常高的,但是创建的时候由于需要初始化内存,因此创建 `channel` 的时候效率非常非常低(初始化即分配内存),并且受到队列大小的限制,写入的数据不能超过指定的队列大小。 `gqueue` 使用起来比 `channel` 更加灵活,不仅创建效率高(动态分配内存),不受队列大小限制(也可限定大小)。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" new file mode 100644 index 00000000000..55103135baa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-gset.md" @@ -0,0 +1,31 @@ +--- +slug: '/docs/components/container-gset' +title: '集合类型-gset' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gset,集合类型,并发安全,元素集合,Go语言,集合操作,GoFrame框架,集合接口] +description: 'GoFrame框架中的集合类型gset,其特点是不重复元素集合,支持任意类型的元素。gset提供了并发安全的选项,是一种高效的集合操作工具,适合在Go语言中应用。提供了详细的使用方式及接口文档链接,便于开发者查阅。' +--- + +## 基本介绍 + +集合,即不可重复的一组元素,元素项可以为任意类型。 + +同时, `gset` 支持可选的并发安全参数选项,支持并发安全的场景。 + +**使用场景**: + +集合操作。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/container/gset" +``` + +**接口文档**: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..1f441a257f5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,350 @@ +--- +slug: '/docs/components/container-gset-example' +title: '集合类型-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,集合,gset,交集,差集,并集,补集,并发安全,序列化] +description: '在GoFrame框架中使用集合类型及其基本操作方法,包括集合的创建、添加、删除、遍历等,并探讨了交集、差集、并集、补集等高级操作。此外,文章还详细解释了包含判断、集合项出栈、子集判断、判断性写入等功能,并以代码示例展示了如何进行JSON序列化和反序列化处理。' +--- + +## 基本使用 + +```go +package main + +import ( + "github.com/gogf/gf/v2/container/gset" + "fmt" +) + +func main() { + // 创建一个并发安全的集合对象 + s := gset.New(true) + + // 添加数据项 + s.Add(1) + + // 批量添加数据项 + s.Add([]interface{}{1, 2, 3}...) + + // 集合数据项大小 + fmt.Println(s.Size()) + + // 集合中是否存在指定数据项 + fmt.Println(s.Contains(2)) + + // 返回数据项slice + fmt.Println(s.Slice()) + + // 删除数据项 + s.Remove(3) + + // 遍历数据项 + s.Iterator(func(v interface{}) bool { + fmt.Println("Iterator:", v) + return true + }) + + // 将集合转换为字符串 + fmt.Println(s.String()) + + // 并发安全写锁操作 + s.LockFunc(func(m map[interface{}]struct{}) { + m[4] = struct{}{} + }) + + // 并发安全读锁操作 + s.RLockFunc(func(m map[interface{}]struct{}) { + fmt.Println(m) + }) + + // 清空集合 + s.Clear() + fmt.Println(s.Size()) +} +``` + +执行后,输出结果为: + +```3 +true +[1 2 3] +Iterator: 1 +Iterator: 2 +[1 2] +map[1:{} 2:{} 4:{}] +0 +``` + +## 交差并补集 + +我们可以使用以下方法实现交差并补集,并返回一个新的结果集合, + +```go +func (set *Set) Intersect(others ...*Set) (newSet *Set) +func (set *Set) Diff(others ...*Set) (newSet *Set) +func (set *Set) Union(others ...*Set) (newSet *Set) +func (set *Set) Complement(full *Set) (newSet *Set) +``` + +1. `Intersect`: 交集,属于set且属于others的元素为元素的集合。 +2. `Diff`: 差集,属于set且不属于others的元素为元素的集合。 +3. `Union`: 并集,属于set或属于others的元素为元素的集合。 +4. `Complement`: 补集,(前提: set应当为full的子集)属于全集full不属于集合set的元素组成的集合。如果给定的full集合不是set的全集时,返回full与set的差集. + +通过集合方法我们可以发现,交差并集方法支持多个集合参数进行计算。以下为简化示例,只使用一个参数集合。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + s1 := gset.NewFrom(g.Slice{1, 2, 3}) + s2 := gset.NewFrom(g.Slice{4, 5, 6}) + s3 := gset.NewFrom(g.Slice{1, 2, 3, 4, 5, 6, 7}) + + // 交集 + fmt.Println(s3.Intersect(s1).Slice()) + // 差集 + fmt.Println(s3.Diff(s1).Slice()) + // 并集 + fmt.Println(s1.Union(s2).Slice()) + // 补集 + fmt.Println(s1.Complement(s3).Slice()) +} +``` + +执行后,输出结果为: + +``` +[1 2 3] +[4 5 6 7] +[1 2 3 4 5 6] +[7 4 5 6] +``` + +## `Contains/ContainsI` 包含判断 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.Contains("a")) + fmt.Println(set.Contains("A")) + fmt.Println(set.ContainsI("A")) + + // Output: + // true + // false + // true +} +``` + +## `Pop/Pops` 集合项出栈 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + set.Add(1, 2, 3, 4) + fmt.Println(set.Pop()) + fmt.Println(set.Pops(2)) + fmt.Println(set.Size()) + + // May Output: + // 1 + // [2 3] + // 1 +} +``` + +## `Join` 集合项串连 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + set.Add("a", "b", "c", "d") + fmt.Println(set.Join(",")) + + // May Output: + // a,b,c,d +} +``` + +## `IsSubsetOf` 子集判断 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var s1, s2 gset.Set + s1.Add(g.Slice{1, 2, 3}...) + s2.Add(g.Slice{2, 3}...) + fmt.Println(s1.IsSubsetOf(&s2)) + fmt.Println(s2.IsSubsetOf(&s1)) + + // Output: + // false + // true +} +``` + +## `AddIfNotExist*` 判断性写入 + +判断性写入是指当指定的数据项不存在时则写入并且方法返回 `true`,否则忽略吸入并且方法返回 `false`。相关方法如下: + +- `AddIfNotExist` +- `AddIfNotExistFunc` +- `AddIfNotExistFuncLock` + +方法具体描述请查看接口文档或源码注释。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + var set gset.Set + fmt.Println(set.AddIfNotExist(1)) + fmt.Println(set.AddIfNotExist(1)) + fmt.Println(set.Slice()) + + // Output: + // true + // false + // [1] +} +``` + +## `Walk` 遍历修改 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/container/gset" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + var ( + set gset.StrSet + names = g.SliceStr{"user", "user_detail"} + prefix = "gf_" + ) + set.Add(names...) + // Add prefix for given table names. + set.Walk(func(item string) string { + return prefix + item + }) + fmt.Println(set.Slice()) + + // May Output: + // [gf_user gf_user_detail] +} +``` + +## `JSON` 序列化/反序列 + +`gset` 模块下的所有容器类型均实现了标准库 `json` 数据格式的序列化/反序列化接口。 + +1. `Marshal` + +```go +package main + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + +func main() { + type Student struct { + Id int + Name string + Scores *gset.IntSet + } + s := Student{ + Id: 1, + Name: "john", + Scores: gset.NewIntSetFrom([]int{100, 99, 98}), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) +} +``` + +执行后,终端输出: + +``` +{"Id":1,"Name":"john","Scores":[100,99,98]} +``` + +2. `Unmarshal` + +```go +package main + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/container/gset" +) + + +func main() { + b := []byte(`{"Id":1,"Name":"john","Scores":[100,99,98]}`) + type Student struct { + Id int + Name string + Scores *gset.IntSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) +} +``` + +执行后,输出结果: + +``` +{1 john [100,99,98]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..a34f5cc84b9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,33 @@ +--- +slug: '/docs/components/container-gset-benchmark' +title: '集合类型-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,性能测试,集合类型,container,benchmark,测试,GoFrame框架,gset,GoFrame框架,优化] +description: '在GoFrame框架中集合类型的性能测试结果,包含各种数据类型如整数、任意数据类型和字符串的集合操作的基准测试。这些基准测试展示了不同集合操作的性能指标,帮助开发者优化代码性能并在使用GoFrame框架构建高效应用时提高效率。' +--- + +[https://github.com/gogf/gf/blob/master/container/gset/gset\_z\_bench\_test.go](https://github.com/gogf/gf/blob/master/container/gset/gset_z_bench_test.go) + +``` +goos: linux +goarch: amd64 +Benchmark_IntSet_Add-4 10000000 277 ns/op 8 B/op 0 allocs/op +Benchmark_IntSet_Contains-4 20000000 60.6 ns/op 0 B/op 0 allocs/op +Benchmark_IntSet_Remove-4 10000000 211 ns/op 0 B/op 0 allocs/op +Benchmark_AnySet_Add-4 5000000 312 ns/op 21 B/op 1 allocs/op +Benchmark_AnySet_Contains-4 30000000 68.2 ns/op 0 B/op 0 allocs/op +Benchmark_AnySet_Remove-4 5000000 267 ns/op 0 B/op 0 allocs/op +Benchmark_StrSet_Add-4 5000000 383 ns/op 20 B/op 1 allocs/op +Benchmark_StrSet_Contains-4 10000000 160 ns/op 7 B/op 0 allocs/op +Benchmark_StrSet_Remove-4 5000000 306 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Add-4 10000000 258 ns/op 35 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Contains-4 20000000 146 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_IntSet_Remove-4 10000000 173 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnySet_Add-4 5000000 355 ns/op 41 B/op 1 allocs/op +Benchmark_Unsafe_AnySet_Contains-4 10000000 150 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_AnySet_Remove-4 200000000 11.9 ns/op 0 B/op 0 allocs/op +Benchmark_Unsafe_StrSet_Add-4 5000000 486 ns/op 59 B/op 1 allocs/op +Benchmark_Unsafe_StrSet_Contains-4 5000000 298 ns/op 7 B/op 0 allocs/op +Benchmark_Unsafe_StrSet_Remove-4 10000000 158 ns/op 7 B/op 0 allocs/op +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..8a66eac86da --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\225\260\346\215\256\347\273\223\346\236\204/\351\233\206\345\220\210\347\261\273\345\236\213-gset/\351\233\206\345\220\210\347\261\273\345\236\213-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,803 @@ +--- +slug: '/docs/components/container-gset-funcs' +title: '集合类型-方法介绍' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,集合类型,StrSet,方法介绍,集合操作,并发安全,字符串集合,代码示例,元素操作,集合函数] +description: '利用GoFrame库实现集合类型的基本操作方法,包括创建集合、新增元素、集合运算、元素检查与删除、集合迭代等多种功能,还提供了具体的代码示例以帮助理解和应用这些方法。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) +::: +:::tip +这里以 `StrSet` 类型介绍方法使用,其他集合类型的方法与此类似,不再赘述。 +::: +## `NewStrSet` + +- 说明: `NewStrSet` 创建并返回一个空的集合,其中包含没有重复字符串的数据。参数 `safe` 用于指定是否在并发安全中使用,默认情况下是 `false`。 +- 格式: + +```go +func NewStrSet(safe ...bool) *StrSet +``` + +- 示例: + +```go +func ExampleNewStrSet() { + strSet := gset.NewStrSet(true) + strSet.Add([]string{"str1", "str2", "str3"}...) + fmt.Println(strSet.Slice()) + + // May Output: + // [str3 str1 str2] +} +``` + + +## `NewStrSetFrom` + +- 说明: `NewStrSetFrom` 通过给定的数组创建集合集合。参数 `safe` 用于指定是否在并发安全中使用,默认情况下是 `false`。 +- 格式: + +```go +func NewStrSetFrom(items []string, safe ...bool) *StrSet +``` + +- 示例: + +```go +func ExampleNewStrSetFrom() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(strSet.Slice()) + + // May Output: + // [str1 str2 str3] +} +``` + + +## `Add` + +- 说明: `Add` 添加一个或多个元素项到集合中。 +- 格式: + +```go +func (set *StrSet) Add(item ...string) +``` + +- 示例: + +```go +func ExampleStrSet_Add() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExist("str")) + + // Mya Output: + // [str str1 str2 str3] + // false +} +``` + + +## `AddIfNotExist` + +- 说明: `Addifnotexist` 检查集合中是否存在指定元素项 `item`,如果不存在则将 `item` 添加到集合中并返回 `true`,否则什么也不做并返回 `false`。 +- 格式: + +```go +func (set *StrSet) AddIfNotExist(item string) bool +``` + +- 示例: + +```go +func ExampleStrSet_AddIfNotExist() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExist("str")) + + // Mya Output: + // [str str1 str2 str3] + // false +} +``` + + +## `AddIfNotExistFunc` + +- 说明:`AddIfNotExistFunc` 检查集合中存在指定元素项 `item`,如果不存在并且方法 `f` 返回 `true` 时,则将 `item` 设置到集合中并返回 `true`,否则什么也不做并返回 `false`。 +- 格式: + +```go +func (set *StrSet) AddIfNotExistFunc(item string, f func() bool) bool +``` + +- 示例: + +```go +func ExampleStrSet_AddIfNotExistFunc() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExistFunc("str5", func() bool { + return true + })) + + // May Output: + // [str1 str2 str3 str] + // true +} +``` + + +## `AddIfNotExistFuncLock` + +- 说明:`AddifnotExistFuncLock` 与 `AddIfNotExistFunc` 类似,不过当多个 `goroutine` 同时调用 `AddifnotExistFuncLock` 方法时,内部使用并发安全锁机制保证同时只能一个 `goroutine` 执行。该方法只有在创建集合时的 `safe` 参数设置 `true` 时有效,否则表现和方法一致 `AddIfNotExistFunc`。 +- 格式: + +```go +func (set *StrSet) AddIfNotExistFuncLock(item string, f func() bool) bool +``` + +- 示例: + +```go +func ExampleStrSet_AddIfNotExistFuncLock() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + strSet.Add("str") + fmt.Println(strSet.Slice()) + fmt.Println(strSet.AddIfNotExistFuncLock("str4", func() bool { + return true + })) + + // May Output: + // [str1 str2 str3 str] + // true +} +``` + + +## `Clear` + +- 说明: `Clear` 删除集合的所有元素项。 +- 格式: + +```go +func (set *StrSet) Clear() +``` + +- 示例: + +```go +func ExampleStrSet_Clear() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(strSet.Size()) + strSet.Clear() + fmt.Println(strSet.Size()) + + // Output: + // 3 + // 0 +} +``` + + +## `Intersect` + +- 说明: `Intrersect` 执行集合 `set` 与 `others` 的交集,并返回一个新的集合 `newSet`,在 `newSet` 中的元素项同时存在于集合 `set` 与 `others` 中。 +- 格式: + +```go +func (set *StrSet) Intersect(others ...*StrSet) (newSet *StrSet) +``` + +- 示例: + +```go +func ExampleStrSet_Intersect() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c"}...) + var s2 gset.StrSet + s2.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s2.Intersect(s1).Slice()) + + // May Output: + // [c a b] +} +``` + + +## `Diff` + +- 说明: `Diff` 执行集合 `set` 与 `others` 差集操作,并返回一个新的集合 `newSet`。在 `newSet` 中的元素项存在于 `set` 但不存在于集合 `others`。注意,参数 `others` 可以指定多个集合参数。 +- 格式: + +```go +func (set *StrSet) Diff(others ...*StrSet) (newSet *StrSet) +``` + +- 示例: + +```go +func ExampleStrSet_Diff() { + s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s2 := gset.NewStrSetFrom([]string{"a", "b", "c", "d"}, true) + fmt.Println(s2.Diff(s1).Slice()) + + // Output: + // [d] +} +``` + + +## `Union` + +- 说明: `union` 执行集合 `set` 与 `others` 的并集操作,并返回一个新的集合 `newSet`。 +- 格式: + +```go +func (set *StrSet) Union(others ...*StrSet) (newSet *StrSet) +``` + +- 示例: + +```go +func ExampleStrSet_Union() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s2 := gset.NewStrSet(true) + s2.Add([]string{"a", "b", "d"}...) + fmt.Println(s1.Union(s2).Slice()) + + // May Output: + // [a b c d] +} +``` + + +## `Complement` + +- 说明: `Complement` 执行 `set` 与 `full` 的补集操作,并返回一个新集合 `newSet`。 +- 格式: + +```go +func (set *StrSet) Complement(full *StrSet) (newSet *StrSet) +``` + +- 示例: + +```go +func ExampleStrSet_Complement() { + strSet := gset.NewStrSetFrom([]string{"str1", "str2", "str3", "str4", "str5"}, true) + s := gset.NewStrSetFrom([]string{"str1", "str2", "str3"}, true) + fmt.Println(s.Complement(strSet).Slice()) + + // May Output: + // [str4 str5] +} +``` + + +## `Contains` + +- 说明: `Contains` 包含检查集是否包含 `item`。 +- 格式: + +```go +func (set *StrSet) Contains(item string) bool +``` + +- 示例: + +```go +func ExampleStrSet_Contains() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.Contains("a")) + fmt.Println(set.Contains("A")) + + // Output: + // true + // false +} +``` + + +## `ContainsI` + +- 说明:`ContainsI` 方法类似于 `Contains`,只是它不区分大小写比较大小。 +- 格式: + +```go +func (set *StrSet) ContainsI(item string) bool +``` + +- 示例: + +```go +func ExampleStrSet_ContainsI() { + var set gset.StrSet + set.Add("a") + fmt.Println(set.ContainsI("a")) + fmt.Println(set.ContainsI("A")) + + // Output: + // true + // true +} +``` + + +## `Equal` + +- 说明:`Equal` 检查两个集合是否完全相等(包括大小以及元素项)。 +- 格式: + +```go +func (set *StrSet) Equal(other *StrSet) bool +``` + +- 示例: + +```go +func ExampleStrSet_Equal() { + s1 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s2 := gset.NewStrSetFrom([]string{"a", "b", "c", "d"}, true) + fmt.Println(s2.Equal(s1)) + + s3 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + s4 := gset.NewStrSetFrom([]string{"a", "b", "c"}, true) + fmt.Println(s3.Equal(s4)) + + // Output: + // false + // true +} +``` + + +## `IsSubSetOf` + +- 说明: `IsSumSetOf` 检查当前集合 `set` 是否是指定集合 `other` 的子集。 +- 格式: + +```go +func (set *StrSet) IsSubsetOf(other *StrSet) bool +``` + +- 示例: + +```go +func ExampleStrSet_IsSubsetOf() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + var s2 gset.StrSet + s2.Add([]string{"a", "b", "d"}...) + fmt.Println(s2.IsSubsetOf(s1)) + + // Output: + // true +} +``` + + +## `Iterator` + +- 说明: `Iterator` 迭代器通过给定的回调函数 `f` 随机遍历当前集合 `set`,如果方法 `f` 返回 `true`,则继续遍历,否则停止。 +- 格式: + +```go +func (set *StrSet) Iterator(f func(v string) bool) +``` + +- 示例: + +```go +func ExampleStrSet_Iterator() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.Iterator(func(v string) bool { + fmt.Println("Iterator", v) + return true + }) + + // May Output: + // Iterator a + // Iterator b + // Iterator c + // Iterator d +} +``` + + +## `Join` + +- 说明: `Join` 将集合中的元素项通过字符串 `glue` 拼接成新的字符串返回。 +- 格式: + +```go +func (set *StrSet) Join(glue string) string +``` + +- 示例: + +```go +func ExampleStrSet_Join() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Join(",")) + + // May Output: + // b,c,d,a +} +``` + + +## `LockFunc` + +- 说明: `LockFunc` 仅在并发安全场景下有用,该方法通过写锁锁定集合 `set`,并执行回调方法 `f`。 +- 格式: + +```go +func (set *StrSet) LockFunc(f func(m map[string]struct{})) +``` + +- 示例: + +```go +func ExampleStrSet_LockFunc() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"1", "2"}...) + s1.LockFunc(func(m map[string]struct{}) { + m["3"] = struct{}{} + }) + fmt.Println(s1.Slice()) + + // May Output + // [2 3 1] + +} +``` + + +## `RLockFunc` + +- 说明: `RLockFunc` 仅在并发安全场景下有用,该方法通过读锁锁定集合 `set`,并执行回调方法 `f`。 +- 格式: + +```go +func (set *StrSet) RLockFunc(f func(m map[string]struct{})) +``` + +- 示例: + +```go +func ExampleStrSet_RLockFunc() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.RLockFunc(func(m map[string]struct{}) { + fmt.Println(m) + }) + + // Output: + // map[a:{} b:{} c:{} d:{}] +} +``` + + +## `Merge` + +- 说明: `Merge` 将集合 `others` 中的所有元素项合并到 `set` 中。 +- 格式: + +```go +func (set *StrSet) Merge(others ...*StrSet) *StrSet +``` + +- 示例: + +```go +func ExampleStrSet_Merge() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + + s2 := gset.NewStrSet(true) + fmt.Println(s1.Merge(s2).Slice()) + + // May Output: + // [d a b c] +} +``` + + +## `Pop` + +- 说明: `Pop` 随机从集合中取出一个元素项。 +- 格式: + +```go +func (set *StrSet) Pop() string +``` + +- 示例: + +```go +func ExampleStrSet_Pop() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + + fmt.Println(s1.Pop()) + + // May Output: + // a +} +``` + + +## `Pops` + +- 说明: `Pops` 从集合中随机弹出 `size` 个元素项。如果 `size == -1`,则返回所有元素项。 +- 格式: + +```go +func (set *StrSet) Pops(size int) []string +``` + +- 示例: + +```go +func ExampleStrSet_Pops() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + for _, v := range s1.Pops(2) { + fmt.Println(v) + } + + // May Output: + // a + // b +} +``` + + +## `Remove` + +- 说明: `Remove` 从集合中删除指定的元素项 `item`。 +- 格式: + +```go +func (set *StrSet) Remove(item string) +``` + +- 示例: + +```go +func ExampleStrSet_Remove() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + s1.Remove("a") + fmt.Println(s1.Slice()) + + // May Output: + // [b c d] +} +``` + + +## `Size` + +- 说明: `Size` 返回集合的大小。 +- 格式: + +```go +func (set *StrSet) Size() int +``` + +- 示例: + +```go +func ExampleStrSet_Size() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Size()) + + // Output: + // 4 +} +``` + + +## `Silce` + +- 说明: `Slice` 将集合中的元素项以 `slice` 的形式返回。 +- 格式: + +```go +func (set *StrSet) Slice() []string +``` + +- 示例: + +```go +func ExampleStrSet_Slice() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.Slice()) + + // May Output: + // [a,b,c,d] +} +``` + + +## `String` + +- 说明: `String` 将集合按照字符串返回。 +- 格式: + +```go +func (set *StrSet) String() string +``` + +- 示例: + +```go +func ExampleStrSet_String() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"a", "b", "c", "d"}...) + fmt.Println(s1.String()) + + // May Output: + // "a","b","c","d" +} + + +``` + + +## `Sum` + +- 说明: `Sum` 将集合中的元素项执行求和,注意:只有元素项为数字时才有效,否则您将得到一个意想不到的结果。 +- 格式: + +```go +func (set *StrSet) Sum() (sum int) +``` + +- 示例: + +```go +func ExampleStrSet_Sum() { + s1 := gset.NewStrSet(true) + s1.Add([]string{"1", "2", "3", "4"}...) + fmt.Println(s1.Sum()) + + // Output: + // 10 +} +``` + + +## `Walk` + +- 说明: `Walk` 按照用户给定的回调方法 `f` 遍历当前集合,并将 `f` 的返回结果重新设置当前集合。注意,在并发安全场景中,该方法内部使用写锁来保证并发安全性。 +- 格式: + +```go +func (set *StrSet) Walk(f func(item string) string) *StrSet +``` + +- 示例: + +```go +func ExampleStrSet_Walk() { + var ( + set gset.StrSet + names = g.SliceStr{"user", "user_detail"} + prefix = "gf_" + ) + set.Add(names...) + // Add prefix for given table names. + set.Walk(func(item string) string { + return prefix + item + }) + fmt.Println(set.Slice()) + + // May Output: + // [gf_user gf_user_detail] +} +``` + + +## `MarshalJSON` + +- 说明: `MarshalJSON` 实现了 `json.Marshal` 的 `MarshalJSON` 接口。 +- 格式: + +```go +func (set *StrSet) MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleStrSet_MarshalJSON() { + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{ + Id: 1, + Name: "john", + Scores: gset.NewStrSetFrom([]string{"100", "99", "98"}, true), + } + b, _ := json.Marshal(s) + fmt.Println(string(b)) + + // May Output: + // {"Id":1,"Name":"john","Scores":["100","99","98"]} +} +``` + + +## `UnmarshalJSON` + +- 说明: `UnmarshalJSON` 实现了 `json.Unmarshal` 中的 `UnmarshalJSON` 接口。 +- 格式: + +```go +func (set *StrSet) UnmarshalJSON(b []byte) error +``` + +- 示例: + +```go +func ExampleStrSet_UnmarshalJSON() { + b := []byte(`{"Id":1,"Name":"john","Scores":["100","99","98"]}`) + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // May Output: + // {1 john "99","98","100"} +} +``` + + +## `UnmarshalValue` + +- 说明:`UnfarshalValue` 实现 `goframe` 框架内部统一的设置接口,它通过一个 `interface{}` 类型的参数初始化当前对象,至于 `interface{}` 参数的使用逻辑由该接口实现方法决定。 +- 格式: + +```go +func (set *StrSet) UnmarshalValue(value interface{}) (err error) +``` + +- 示例: + +```go +func ExampleStrSet_UnmarshalValue() { + b := []byte(`{"Id":1,"Name":"john","Scores":["100","99","98"]}`) + type Student struct { + Id int + Name string + Scores *gset.StrSet + } + s := Student{} + json.Unmarshal(b, &s) + fmt.Println(s) + + // May Output: + // {1 john "99","98","100"} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" new file mode 100644 index 00000000000..b84cb1afdbd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\345\255\227\347\254\246\344\270\262\345\244\204\347\220\206-gstr.md" @@ -0,0 +1,2743 @@ +--- +slug: '/docs/components/text-gstr' +title: '字符串处理-gstr' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,字符串处理,文本处理组件,字符串判断,大小写转换,字符串比较,切分组合,命名转换,GoFrame框架,字符串转换] +description: 'gstr提供了强大便捷的文本处理组件,包含字符串判断、大小写转换、字符串比较、切分组合、命名转换等多种功能,相较于Golang标准库更加全面丰富。' +--- + +`gstr` 提供了强大便捷的文本处理组件,组件内置了大量常用的字符串处理方法,比较于 `Golang` 标准库更加全面丰富,可应对绝大部分业务场景。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/text/gstr" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +::: +## 字符串判断 + +### `IsNumeric` + +- 说明:`IsNumeric` 验证字符串 `s` 是否为数字。 + +- 格式: + +```go +IsNumeric(s string) bool +``` + +- 示例: + +```go +func ExampleIsNumeric() { + fmt.Println(gstr.IsNumeric("88")) + fmt.Println(gstr.IsNumeric("3.1415926")) + fmt.Println(gstr.IsNumeric("abc")) + // Output: + // true + // true + // false +} +``` + + +## 字符串长度 + +### `LenRune` + +- 说明:`LenRune` 返回 `unicode` 字符串长度。 + +- 格式: + +```go +LenRune(str string) int +``` + +- 示例: + +```go +func ExampleLenRune() { + var ( + str = `GoFrame框架` + result = gstr.LenRune(str) + ) + fmt.Println(result) + + // Output: + // 9 +} +``` + + +## 字符串创建 + +### `Repeat` + +- 说明:`Repeat` 返回一个由 `input` 重复 `multiplier` 次后组成的新字符串。 + +- 格式: + +```go +Repeat(input string, multiplier int) string +``` + +- 示例: + +```go +func ExampleRepeat() { + var ( + input = `goframe ` + multiplier = 3 + result = gstr.Repeat(input, multiplier) + ) + fmt.Println(result) + + // Output: + // goframe goframe goframe +} +``` + + +## 大小写转换 + +### `ToLower` + +- 说明:`ToLower` 将 `s` 中所有 `Unicode` 字符都变为小写并返回其副本。 + +- 格式: + +```go +ToLower(s string) string +``` + +- 示例: + +```go +func ExampleToLower() { + var ( + s = `GOFRAME` + result = gstr.ToLower(s) + ) + fmt.Println(result) + + // Output: + // goframe +} +``` + + +### `ToUpper` + +- 说明:`ToUpper` 将 `s` 中所有 `Unicode` 字符都变为大写并返回其副本。 + +- 格式: + +```go +ToUpper(s string) string +``` + +- 示例: + +```go +func ExampleToUpper() { + var ( + s = `goframe` + result = gstr.ToUpper(s) + ) + fmt.Println(result) + + // Output: + // GOFRAME +} +``` + + +### `UcFirst` + +- 说明:`UcFirst` 将 `s` 中首字符变为大写并返回其副本。 + +- 格式: + +```go +UcFirst(s string) string +``` + +- 示例: + +```go +func ExampleUcFirst() { + var ( + s = `hello` + result = gstr.UcFirst(s) + ) + fmt.Println(result) + + // Output: + // Hello +} +``` + + +### `LcFirst` + +- 说明: `LcFirst` 将 `s` 中首字符变为小写并返回其副本。 + +- 格式: + +```go +LcFirst(s string) string +``` + +- 示例: + +```go +func ExampleLcFirst() { + var ( + str = `Goframe` + result = gstr.LcFirst(str) + ) + fmt.Println(result) + + // Output: + // goframe +} +``` + + +### `UcWords` + +- 说明: `UcWords` 将字符串 `str` 中每个单词的第一个字符变为大写。 + +- 格式: + +```go +UcWords(str string) string +``` + +- 示例: + +```go +func ExampleUcWords() { + var ( + str = `hello world` + result = gstr.UcWords(str) + ) + fmt.Println(result) + + // Output: + // Hello World +} +``` + + +### `IsLetterLower` + +- 说明:`IsLetterLower` 验证给定的字符 `b` 是否是小写字符。 + +- 格式: + +```go +IsLetterLower(b byte) bool +``` + +- 示例: + +```go +func ExampleIsLetterLower() { + fmt.Println(gstr.IsLetterLower('a')) + fmt.Println(gstr.IsLetterLower('A')) + + // Output: + // true + // false +} +``` + + +### `IsLetterUpper` + +- 说明:`IsLetterUpper` 验证字符 `b` 是否是大写字符。 + +- 格式: + +```go +IsLetterUpper(b byte) bool +``` + +- 示例: + +```go +func ExampleIsLetterUpper() { + fmt.Println(gstr.IsLetterUpper('A')) + fmt.Println(gstr.IsLetterUpper('a')) + + // Output: + // true + // false +} +``` + + +## 字符串比较 + +### `Compare` + +- 说明:`Compare` 返回一个按字典顺序比较两个字符串的整数。 如果 `a == b`,结果为 `0`,如果 `a < b`,结果为 `-1`,如果 `a > b`,结果为 `+1`。 + +- 格式: + +```go +Compare(a, b string) int +``` + +- 示例: + +```go +func ExampleCompare() { + fmt.Println(gstr.Compare("c", "c")) + fmt.Println(gstr.Compare("a", "b")) + fmt.Println(gstr.Compare("c", "b")) + + // Output: + // 0 + // -1 + // 1 +} +``` + + +### `Equal` + +- 说明: `Equal` 返回 `a` 和 `b` 在不区分大小写的情况下是否相等。 + +- 格式: + +```go +Equal(a, b string) bool +``` + +- 示例: + +```go +func ExampleEqual() { + fmt.Println(gstr.Equal(`A`, `a`)) + fmt.Println(gstr.Equal(`A`, `A`)) + fmt.Println(gstr.Equal(`A`, `B`)) + + // Output: + // true + // true + // false +} +``` + + +## 切分组合 + +### `Split` + +- 说明:`Split` 用 `delimiter` 将 `str` 拆分为 `[]string`。 + +- 格式: + +```go +Split(str, delimiter string) []string +``` + +- 示例: + +```go +func ExampleSplit() { + var ( + str = `a|b|c|d` + delimiter = `|` + result = gstr.Split(str, delimiter) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"a", "b", "c", "d"} +} +``` + + +### `SplitAndTrim` + +- 说明:`SplitAndTrim` 使用 `delimiter` 将 `str` 拆分为 `[]string`,并对 `[]string` 的每个元素调用 `Trim`,并忽略在 `Trim` 之后为空的元素。 + +- 格式: + +```go +SplitAndTrim(str, delimiter string, characterMask ...string) []string +``` + +- 示例: + +```go +func ExampleSplitAndTrim() { + var ( + str = `a|b|||||c|d` + delimiter = `|` + result = gstr.SplitAndTrim(str, delimiter) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"a", "b", "c", "d"} +} +``` + + +### `Join` + +- 说明: `Join` 将 `array` 中的每一个元素连接并生成一个新的字符串。参数 `sep` 会作为新字符串的分隔符。 + +- 格式: + +```go +Join(array []string, sep string) string +``` + +- 示例: + +```go +func ExampleJoin() { + var ( + array = []string{"goframe", "is", "very", "easy", "to", "use"} + sep = ` ` + result = gstr.Join(array, sep) + ) + fmt.Println(result) + + // Output: + // goframe is very easy to use +} +``` + + +### `JoinAny` + +- 说明: `JoinAny` 将 `array` 中的每一个元素连接并生成一个新的字符串。参数 `sep` 会作为新字符串的分隔符。参数 `array` 可以是任意的类型。 + +- 格式: + +```go +JoinAny(array interface{}, sep string) string +``` + +- 示例: + +```go +func ExampleJoinAny() { + var ( + sep = `,` + arr2 = []int{99, 73, 85, 66} + result = gstr.JoinAny(arr2, sep) + ) + fmt.Println(result) + + // Output: + // 99,73,85,66 +} +``` + + +### `Explode` + +- 说明: `Explode` 使用分隔符 `delimiter` 字符串 `str` 拆分成 `[]string` + +- 格式: + +```go +Explode(delimiter, str string) []string +``` + +- 示例: + +```go +func ExampleExplode() { + var ( + str = `Hello World` + delimiter = " " + result = gstr.Explode(delimiter, str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"Hello", "World"} +} +``` + + +### `Implode` + +- 说明: `Implode` 使用 `glue` 连接 `pieces` 字符串数组的每一个元素。 + +- 格式: + +```go +Implode(glue string, pieces []string) string +``` + +- 示例: + +```go +func ExampleImplode() { + var ( + pieces = []string{"goframe", "is", "very", "easy", "to", "use"} + glue = " " + result = gstr.Implode(glue, pieces) + ) + fmt.Println(result) + + // Output: + // goframe is very easy to use +} +``` + + +### `ChunkSplit` + +- 说明:`ChunkSplit` 将字符串拆分为单位为 `chunkLen` 长度更小的每一份,并用 `end` 连接每一份拆分出的字符串。 + +- 格式: + +```go +ChunkSplit(body string, chunkLen int, end string) string +``` + +- 示例: + +```go +func ExampleChunkSplit() { + var ( + body = `1234567890` + chunkLen = 2 + end = "#" + result = gstr.ChunkSplit(body, chunkLen, end) + ) + fmt.Println(result) + + // Output: + // 12#34#56#78#90# +} +``` + + +### `Fields` + +- 说明: `Fields` 以 `[]string` 的形式返回字符串中的每个单词。 + +- 格式: + +```go +Fields(str string) []string +``` + +- 示例: + +```go +func ExampleFields() { + var ( + str = `Hello World` + result = gstr.Fields(str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // []string{"Hello", "World"} +} +``` + + +## 转义处理 + +### `AddSlashes` + +- 说明: `AddSlashes` 将字符串中的符号前添加转义字符 `'\'` + +- 格式: + +```go +AddSlashes(str string) string +``` + +- 示例: + +```go +func ExampleAddSlashes() { + var ( + str = `'aa'"bb"cc\r\n\d\t` + result = gstr.AddSlashes(str) + ) + + fmt.Println(result) + + // Output: + // \'aa\'\"bb\"cc\\r\\n\\d\\t +} +``` + + +### `StripSlashes` + +- 说明: `StripSlashes` 去掉字符串 `str` 中的转义字符 `'\'`。 + +- 格式: + +```go +StripSlashes(str string) string +``` + +- 示例: + +```go +func ExampleStripSlashes() { + var ( + str = `C:\\windows\\GoFrame\\test` + result = gstr.StripSlashes(str) + ) + fmt.Println(result) + + // Output: + // C:\windows\GoFrame\test +} +``` + + +### `QuoteMeta` + +- 说明:`QuoteMeta` 为str中' `. \ + * ? [ ^ ] ( $ )` 中的每个字符前添加一个转义字符 `'\'。` + +- 格式: + +```go +QuoteMeta(str string, chars ...string) string +``` + +- 示例: + +```go +func ExampleQuoteMeta() { + { + var ( + str = `.\+?[^]()` + result = gstr.QuoteMeta(str) + ) + fmt.Println(result) + } + { + var ( + str = `https://goframe.org/pages/viewpage.action?pageId=1114327` + result = gstr.QuoteMeta(str) + ) + fmt.Println(result) + } + + // Output: + // \.\\\+\?\[\^\]\(\) + // https://goframe\.org/pages/viewpage\.action\?pageId=1114327 + +} +``` + + +## 统计计数 + +### `Count` + +- 说明:`Count` 计算 `substr` 在 `s` 中出现的次数。  如果在 `s` 中没有找到 `substr`,则返回 `0`。 + +- 格式: + +```go +Count(s, substr string) int +``` + +- 示例: + +```go +func ExampleCount() { + var ( + str = `goframe is very, very easy to use` + substr1 = "goframe" + substr2 = "very" + result1 = gstr.Count(str, substr1) + result2 = gstr.Count(str, substr2) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // 1 + // 2 +} +``` + + +### `CountI` + +- 说明:`Count` 计算 `substr` 在 `s` 中出现的次数,不区分大小写。  如果在 `s` 中没有找到 `substr`,则返回 `0`。 + +- 格式: + +```go +CountI(s, substr string) int +``` + +- 示例: + +```go +func ExampleCountI() { + var ( + str = `goframe is very, very easy to use` + substr1 = "GOFRAME" + substr2 = "VERY" + result1 = gstr.CountI(str, substr1) + result2 = gstr.CountI(str, substr2) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // 1 + // 2 +} +``` + + +### `CountWords` + +- 说明:`CountWords` 以 `map[string]int` 的形式返回 `str` 中使用的单词的统计信息。 + +- 格式: + +```go +CountWords(str string) map[string]int +``` + +- 示例: + +```go +func ExampleCountWords() { + var ( + str = `goframe is very, very easy to use!` + result = gstr.CountWords(str) + ) + fmt.Printf(`%#v`, result) + + // Output: + // map[string]int{"easy":1, "goframe":1, "is":1, "to":1, "use!":1, "very":1, "very,":1} +} +``` + + +### `CountChars` + +- 说明:`CountChars` 以 `map[string]int` 的形式返回 `str` 中使用的字符的统计信息。 `noSpace` 参数可以控制是否计算空格。 + +- 格式: + +```go +CountChars(str string, noSpace ...bool) map[string]int +``` + +- 示例: + +```go +func ExampleCountChars() { + var ( + str = `goframe` + result = gstr.CountChars(str) + ) + fmt.Println(result) + + // May Output: + // map[a:1 e:1 f:1 g:1 m:1 o:1 r:1] +} +``` + + +## 数组处理 + +### `SearchArray` + +- 说明:`SearchArray` 在 `[]string 'a'` 中区分大小写地搜索字符串 `'s'`,返回其在 `'a'` 中的索引。 如果在 `'a'` 中没有找到 `'s'`,则返回 `-1`。 + +- 格式: + +```go +SearchArray(a []string, s string) int +``` + +- 示例: + +```go +func ExampleSearchArray() { + var ( + array = []string{"goframe", "is", "very", "nice"} + str = `goframe` + result = gstr.SearchArray(array, str) + ) + fmt.Println(result) + + // Output: + // 0 +} +``` + + +### `InArray` + +- 说明:`InArray校验` `[]string 'a'` 中是否有字符串 `' s '`。 + +- 格式: + +```go +InArray(a []string, s string) bool +``` + +- 示例: + +```go +func ExampleInArray() { + var ( + a = []string{"goframe", "is", "very", "easy", "to", "use"} + s = "goframe" + result = gstr.InArray(a, s) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +### `PrefixArray` + +- 说明: `PrefixArray` 位 `[]string array` 的每一个字符串添加 `'prefix'` 的前缀。 + +- 格式: + +```go +PrefixArray(array []string, prefix string) +``` + +- 示例: + +```go +func ExamplePrefixArray() { + var ( + strArray = []string{"tom", "lily", "john"} + ) + + gstr.PrefixArray(strArray, "classA_") + + fmt.Println(strArray) + + // Output: + // [classA_tom classA_lily classA_john] +} +``` + + +## 命名转换 + +### `CaseCamel` + +- 说明: `CaseCamel` 将字符串转换为大驼峰形式(首字母大写)。 + +- 格式: + +```go +CaseCamel(s string) string +``` + +- 示例: + +```go +func ExampleCaseCamel() { + var ( + str = `hello world` + result = gstr.CaseCamel(str) + ) + fmt.Println(result) + + // Output: + // HelloWorld +} +``` + + +### `CaseCamelLower` + +- 说明: `CaseCamelLower` 将字符串转换为小驼峰形式(首字母小写)。 + +- 格式: + +```go +CaseCamelLower(s string) string +``` + +- 示例: + +```go +func ExampleCaseCamelLower() { + var ( + str = `hello world` + result = gstr.CaseCamelLower(str) + ) + fmt.Println(result) + + // Output: + // helloWorld +} +``` + + +### `CaseSnake` + +- 说明: `CaseSnake` 将字符串转换中的符号(下划线,空格,点,中横线)用下划线( `_` )替换,并全部转换为小写字母。 + +- 格式: + +```go +CaseSnake(s string) string +``` + +- 示例: + +```go +func ExampleCaseSnake() { + var ( + str = `hello world` + result = gstr.CaseSnake(str) + ) + fmt.Println(result) + + // Output: + // hello_world +} +``` + + +### `CaseSnakeScreaming` + +- 说明: `CaseSnakeScreaming` 把字符串中的符号(下划线,空格,点,中横线),全部替换为下划线 `'_'`,并将所有英文字母转为大写。 + +- 格式: + +```go +CaseSnakeScreaming(s string) string +``` + +- 示例: + +```go +func ExampleCaseSnakeScreaming() { + var ( + str = `hello world` + result = gstr.CaseSnakeScreaming(str) + ) + fmt.Println(result) + + // Output: + // HELLO_WORLD +} +``` + + +### `CaseSnakeFirstUpper` + +- 说明: `CaseSnakeFirstUpper` 将字符串中的字母为大写时,将大写字母转换为小写字母并在其前面增加一个下划线 `'_'`,首字母大写时,只转换为小写,前面不增加下划线 `'_'`。 + +- 格式: + +```go +CaseSnakeFirstUpper(word string, underscore ...string) string +``` + +- 示例: + +```go +func ExampleCaseSnakeFirstUpper() { + var ( + str = `RGBCodeMd5` + result = gstr.CaseSnakeFirstUpper(str) + ) + fmt.Println(result) + + // Output: + // rgb_code_md5 +} +``` + + +### `CaseKebab` + +- 说明: `CaseKebab` 将字符串转换中的符号(下划线,空格,点,)用中横线 `'-'` 替换,并全部转换为小写字母。 + +- 格式: + +```go +CaseKebab(s string) string +``` + +- 示例: + +```go +func ExampleCaseKebab() { + var ( + str = `hello world` + result = gstr.CaseKebab(str) + ) + fmt.Println(result) + + // Output: + // hello-world +} +``` + + +### `CaseKebabScreaming` + +- 说明: `CaseKebabScreaming` 将字符串转换中的符号(下划线,空格,点,中横线)用中横线 `'-'` 替换,并全部转换为大写字母。 + +- 格式: + +```go +CaseKebabScreaming(s string) string +``` + +- 示例: + +```go +func ExampleCaseKebabScreaming() { + var ( + str = `hello world` + result = gstr.CaseKebabScreaming(str) + ) + fmt.Println(result) + + // Output: + // HELLO-WORLD +} +``` + + +### `CaseDelimited` + +- 说明: `CaseDelimited` 将字符串转换中的符号进行替换。 + +- 格式: + +```go +CaseDelimited(s string, del byte) string +``` + +- 示例: + +```go +func ExampleCaseDelimited() { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimited(str, del) + ) + fmt.Println(result) + + // Output: + // hello-world +} +``` + + +### `CaseDelimitedScreaming` + +- 说明: `CaseDelimitedScreaming` 将字符串中的符号(空格,下划线,点,中横线)用第二个参数进行替换,该函数第二个参数为替换的字符,第三个参数为大小写转换, `true` 为全部转换大写字母, `false` 为全部转为小写字母。 + +- 格式: + +```go +CaseDelimitedScreaming(s string, del uint8, screaming bool) string +``` + +- 示例: + +```go +func ExampleCaseDelimitedScreaming() { + { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimitedScreaming(str, del, true) + ) + fmt.Println(result) + } + { + var ( + str = `hello world` + del = byte('-') + result = gstr.CaseDelimitedScreaming(str, del, false) + ) + fmt.Println(result) + } + + // Output: + // HELLO-WORLD + // hello-world +} +``` + + +## 包含判断 + +### `Contains` + +- 说明: `Contains` 返回字符串 `str` 是否包含子字符串 `substr`,区分大小写。 + +- 格式: + +```go +Contains(str, substr string) bool +``` + +- 示例: + +```go +func ExampleContains() { + { + var ( + str = `Hello World` + substr = `Hello` + result = gstr.Contains(str, substr) + ) + fmt.Println(result) + } + { + var ( + str = `Hello World` + substr = `hello` + result = gstr.Contains(str, substr) + ) + fmt.Println(result) + } + + // Output: + // true + // false +} +``` + + +### `ContainsI` + +- 说明:`ContainsI` 校验 `substr` 是否在 `str` 中,不区分大小写。 + +- 格式: + +```go +ContainsI(str, substr string) bool +``` + +- 示例: + +```go +func ExampleContainsI() { + var ( + str = `Hello World` + substr = "hello" + result1 = gstr.Contains(str, substr) + result2 = gstr.ContainsI(str, substr) + ) + fmt.Println(result1) + fmt.Println(result2) + + // Output: + // false + // true +} +``` + + +### `ContainsAny` + +- 说明:`ContainsAny` 校验 `s` 中是否包含 `chars`。 + +- 格式: + +```go +ContainsAny(s, chars string) bool +``` + +- 示例: + +```go +func ExampleContainsAny() { + { + var ( + s = `goframe` + chars = "g" + result = gstr.ContainsAny(s, chars) + ) + fmt.Println(result) + } + { + var ( + s = `goframe` + chars = "G" + result = gstr.ContainsAny(s, chars) + ) + fmt.Println(result) + } + + // Output: + // true + // false +} +``` + + +## 字符串转换 + +### `Chr` + +- 说明:`Chr` 返回一个数字 `0-255` 对应的 `ascii` 字符串。 + +- 格式: + +```go +Chr(ascii int) string +``` + +- 示例: + +```go +func ExampleChr() { + var ( + ascii = 65 // A + result = gstr.Chr(ascii) + ) + fmt.Println(result) + + // Output: + // A +} +``` + + +### `Ord` + +- 说明:`Ord` 将字符串的第一个字节转换为 `0-255` 之间的值。 + +- 格式: + +```go +Ord(char string) int +``` + +- 示例: + +```go +func ExampleOrd() { + var ( + str = `goframe` + result = gstr.Ord(str) + ) + + fmt.Println(result) + + // Output: + // 103 +} +``` + + +### `OctStr` + +- 说明:`OctStr` 将字符串 `str` 中的八进制字符串转换为其原始字符串。 + +- 格式: + +```go +OctStr(str string) string +``` + +- 示例: + +```go +func ExampleOctStr() { + var ( + str = `\346\200\241` + result = gstr.OctStr(str) + ) + fmt.Println(result) + + // Output: + // 怡 +} +``` + + +### `Reverse` + +- 说明:`Reverse` 返回 `str` 的反转字符串。 + +- 格式: + +```go +Reverse(str string) string +``` + +- 示例: + +```go +func ExampleReverse() { + var ( + str = `123456` + result = gstr.Reverse(str) + ) + fmt.Println(result) + + // Output: + // 654321 +} +``` + + +### `NumberFormat` + +- 说明:`NumberFormat` 以千位分组来格式化数字。 + + - 参数 `decimal` 设置小数点的个数。 + - 参数 `decPoint` 设置小数点的分隔符。 + - 参数 `thousand` 设置千位分隔符。 +- 格式: + +```go +NumberFormat(number float64, decimals int, decPoint, thousandsSep string) string +``` + +- 示例: + +```go +func ExampleNumberFormat() { + var ( + number float64 = 123456 + decimals = 2 + decPoint = "." + thousandsSep = "," + result = gstr.NumberFormat(number, decimals, decPoint, thousandsSep) + ) + fmt.Println(result) + + // Output: + // 123,456.00 +} +``` + + +### `Shuffle` + +- 说明:`Shuffle` 返回将 `str` 随机打散后的字符串。 + +- 格式: + +```go +Shuffle(str string) string +``` + +- 示例: + +```go +func ExampleShuffle() { + var ( + str = `123456` + result = gstr.Shuffle(str) + ) + fmt.Println(result) + + // May Output: + // 563214 +} +``` + + +### `HideStr` + +- 说明: `HideStr` 将字符串 `str` 从中间字符开始,百分比 `percent` 的字符转换成 `hide` 字符串。 + +- 格式: + +```go +HideStr(str string, percent int, hide string) string +``` + +- 示例: + +```go +func ExampleHideStr() { + var ( + str = `13800138000` + percent = 40 + hide = `*` + result = gstr.HideStr(str, percent, hide) + ) + fmt.Println(result) + + // Output: + // 138****8000 +} +``` + + +### `Nl2Br` + +- 说明:`Nl2Br` 在字符串中的所有换行符之前插入 `HTML` 换行符 `(' br ' |
    ): \n\r, \r\n, \r, \n`。 + +- 格式: + +```go +Nl2Br(str string, isXhtml ...bool) string +``` + +- 示例: + +```go +func ExampleNl2Br() { + var ( + str = `goframe +is +very +easy +to +use` + result = gstr.Nl2Br(str) + ) + + fmt.Println(result) + + // Output: + // goframe
    is
    very
    easy
    to
    use +} +``` + + +### `WordWrap` + +- 说明: `WordWrap` 使用换行符将 `str` 换行到给定字符数(不会切分单词)。 + +- 格式: + +```go +WordWrap(str string, width int, br string) string +``` + +- 示例: + +```go +func ExampleWordWrap() { + { + var ( + str = `A very long woooooooooooooooooord. and something` + width = 8 + br = "\n" + result = gstr.WordWrap(str, width, br) + ) + fmt.Println(result) + } + { + var ( + str = `The quick brown fox jumped over the lazy dog.` + width = 20 + br = "
    \n" + result = gstr.WordWrap(str, width, br) + ) + fmt.Printf("%v", result) + } + + // Output: + // A very + // long + // woooooooooooooooooord. + // and + // something + // The quick brown fox
    + // jumped over the lazy
    + // dog. +} +``` + + +## 域名处理 + +### `IsSubDomain` + +- 说明:`IsSubDomain` 校验 `subDomain` 是否为 `mainDomain` 的子域名。 支持 `mainDomain` 中的 `'*'`。 + +- 格式: + +```go +IsSubDomain(subDomain string, mainDomain string) bool +``` + +- 示例: + +```go +func ExampleIsSubDomain() { + var ( + subDomain = `s.goframe.org` + mainDomain = `goframe.org` + result = gstr.IsSubDomain(subDomain, mainDomain) + ) + fmt.Println(result) + + // Output: + // true +} + + +``` + + +## 参数解析 + +### `Parse` + +- 说明: `Parse` 解析字符串并以 `map[string]interface{}` 类型返回。 + +- 格式: + +```go +Parse(s string) (result map[string]interface{}, err error) +``` + +- 示例: + +```go +func ExampleParse() { + { + var ( + str = `v1=m&v2=n` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + { + var ( + str = `v[a][a]=m&v[a][b]=n` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + { + // The form of nested Slice is not yet supported. + var str = `v[][]=m&v[][]=n` + result, err := gstr.Parse(str) + if err != nil { + panic(err) + } + fmt.Println(result) + } + { + // This will produce an error. + var str = `v=m&v[a]=n` + result, err := gstr.Parse(str) + if err != nil { + println(err) + } + fmt.Println(result) + } + { + var ( + str = `a .[[b=c` + result, _ = gstr.Parse(str) + ) + fmt.Println(result) + } + + // May Output: + // map[v1:m v2:n] + // map[v:map[a:map[a:m b:n]]] + // map[v:map[]] + // Error: expected type 'map[string]interface{}' for key 'v', but got 'string' + // map[] + // map[a___[b:c] +} +``` + + +## 位置查找 + +### `Pos` + +- 说明:`Pos` 返回 `needle` 在 `haystack` 中第一次出现的位置,区分大小写。 如果没有找到,则返回-1。 + +- 格式: + +```go +Pos(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePos() { + var ( + haystack = `Hello World` + needle = `World` + result = gstr.Pos(haystack, needle) + ) + fmt.Println(result) + + // Output: + // 6 +} +``` + + +### `PosRune` + +- 说明: `PosRune` 的作用于函数 `Pos` 相似,但支持 `haystack` 和 `needle` 为 `unicode` 字符串。 + +- 格式: + +```go +PosRune(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `Go` + posI = gstr.PosRune(haystack, needle) + posR = gstr.PosRRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +### `PosI` + +- 说明:`PosI` 返回 `needle` 在 `haystack` 中第一次出现的位置,不区分大小写。 如果没有找到,则返回-1。 + +- 格式: + +```go +PosI(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosI() { + var ( + haystack = `goframe is very, very easy to use` + needle = `very` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosR(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRuneI` + +- 说明: `PosRuneI` 的作用于函数 `PosI` 相似,但支持 `haystack` 和 `needle` 为 `unicode` 字符串。 + +- 格式: + +```go +PosIRune(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosIRune() { + { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `高性能` + startOffset = 10 + result = gstr.PosIRune(haystack, needle, startOffset) + ) + fmt.Println(result) + } + { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `高性能` + startOffset = 30 + result = gstr.PosIRune(haystack, needle, startOffset) + ) + fmt.Println(result) + } + + // Output: + // 14 + // -1 +} +``` + + +### `PosR` + +- 说明:`PosR` 返回 `needle` 在 `haystack` 中最后一次出现的位置,区分大小写。 如果没有找到,则返回-1。 + +- 格式: + +```go +PosR(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosR() { + var ( + haystack = `goframe is very, very easy to use` + needle = `very` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosR(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRuneR` + +- 说明: `PosRuneR` 的作用于函数 `PosR` 相似,但支持 `haystack` 和 `needle` 为 `unicode` 字符串。 + +- 格式: + +```go +PosRRune(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosRRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `Go` + posI = gstr.PosIRune(haystack, needle) + posR = gstr.PosRRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +### `PosRI` + +- 说明:`PosRI` 返回 `needle` 在 `haystack` 中最后一次出现的位置,不区分大小写。 如果没有找到,则返回-1。 + +- 格式: + +```go +PosRI(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosRI() { + var ( + haystack = `goframe is very, very easy to use` + needle = `VERY` + posI = gstr.PosI(haystack, needle) + posR = gstr.PosRI(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 11 + // 17 +} +``` + + +### `PosRIRune` + +- 说明:`PosRIRune`的作用于函数`PosRI`相似,但支持 `haystack` 和 `needle` 为 `unicode` 字符串。 + +- 格式: + +```go +PosRIRune(haystack, needle string, startOffset ...int) int +``` + +- 示例: + +```go +func ExamplePosRIRune() { + var ( + haystack = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架` + needle = `GO` + posI = gstr.PosIRune(haystack, needle) + posR = gstr.PosRIRune(haystack, needle) + ) + fmt.Println(posI) + fmt.Println(posR) + + // Output: + // 0 + // 22 +} +``` + + +## 查找替换 + +### `Replace` + +- 说明: `Replace` 返回 `origin` 字符串中, `search` 被 `replace` 替换后的新字符串。 `search` 区分大小写。 + +- 格式: + +```go +Replace(origin, search, replace string, count ...int) string +``` + +- 示例: + +```go +func ExampleReplace() { + var ( + origin = `golang is very nice!` + search = `golang` + replace = `goframe` + result = gstr.Replace(origin, search, replace) + ) + fmt.Println(result) + + // Output: + // goframe is very nice! +} +``` + + +### `ReplaceI` + +- 说明: `ReplaceI` 返回 `origin` 字符串中, `search` 被 `replace` 替换后的新字符串。 `search` 不区分大小写。 + +- 格式: + +```go +ReplaceI(origin, search, replace string, count ...int) string +``` + +- 示例: + +```go +func ExampleReplaceI() { + var ( + origin = `golang is very nice!` + search = `GOLANG` + replace = `goframe` + result = gstr.ReplaceI(origin, search, replace) + ) + fmt.Println(result) + + // Output: + // goframe is very nice! +} +``` + + +### `ReplaceByArray` + +- 说明:`ReplaceByArray` 返回 `origin` 被一个切片按两个一组 `(search, replace)` 顺序替换的新字符串,区分大小写。 + +- 格式: + +```go +ReplaceByArray(origin string, array []string) string +``` + +- 示例: + +```go +func ExampleReplaceByArray() { + { + var ( + origin = `golang is very nice` + array = []string{"lang", "frame"} + result = gstr.ReplaceByArray(origin, array) + ) + fmt.Println(result) + } + { + var ( + origin = `golang is very good` + array = []string{"golang", "goframe", "good", "nice"} + result = gstr.ReplaceByArray(origin, array) + ) + fmt.Println(result) + } + + // Output: + // goframe is very nice + // goframe is very nice +} +``` + + +### `ReplaceIByArray` + +- 说明:`ReplaceIByArray` 返回 `origin` 被一个切片按两个一组 `(search, replace)` 顺序替换的新字符串,不区分大小写。 + +- 格式: + +```go +ReplaceIByArray(origin string, array []string) string +``` + +- 示例: + +```go +func ExampleReplaceIByArray() { + var ( + origin = `golang is very Good` + array = []string{"Golang", "goframe", "GOOD", "nice"} + result = gstr.ReplaceIByArray(origin, array) + ) + + fmt.Println(result) + + // Output: + // goframe is very nice +} +``` + + +### `ReplaceByMap` + +- 说明:`ReplaceByMap` 返回 `origin` 中 `map` 的 `key` 替换为 `value` 的新字符串,区分大小写。 + +- 格式: + +```go +ReplaceByMap(origin string, replaces map[string]string) string +``` + +- 示例: + +```go +func ExampleReplaceByMap() { + { + var ( + origin = `golang is very nice` + replaces = map[string]string{ + "lang": "frame", + } + result = gstr.ReplaceByMap(origin, replaces) + ) + fmt.Println(result) + } + { + var ( + origin = `golang is very good` + replaces = map[string]string{ + "golang": "goframe", + "good": "nice", + } + result = gstr.ReplaceByMap(origin, replaces) + ) + fmt.Println(result) + } + + // Output: + // goframe is very nice + // goframe is very nice +} +``` + + +### `ReplaceIByMap` + +- 说明:`ReplaceIByMap` 返回 `origin` 中 `map` 的 `key` 替换为 `value` 的新字符串,不区分大小写。 + +- 格式: + +```go +ReplaceIByMap(origin string, replaces map[string]string) string +``` + +- 示例: + +```go +func ExampleReplaceIByMap() { + var ( + origin = `golang is very nice` + replaces = map[string]string{ + "Lang": "frame", + } + result = gstr.ReplaceIByMap(origin, replaces) + ) + fmt.Println(result) + + // Output: + // goframe is very nice +} +``` + + +## 子串截取 + +### `Str` + +- 说明: `Str` 返回从 `needle` 第一次出现的位置开始,到 `haystack` 结尾的字符串(包含 `needle` 本身)。 + +- 格式: + +```go +Str(haystack string, needle string) string +``` + +- 示例: + +```go +func ExampleStr() { + var ( + haystack = `xxx.jpg` + needle = `.` + result = gstr.Str(haystack, needle) + ) + fmt.Println(result) + + // Output: + // .jpg +} +``` + + +### `StrEx` + +- 说明: `StrEx` 返回从 `needle` 第一次出现的位置开始,到 `haystack` 结尾的字符串(不包含 `needle` 本身)。 + +- 格式: + +```go +StrEx(haystack string, needle string) string +``` + +- 示例: + +```go +func ExampleStrEx() { + var ( + haystack = `https://goframe.org/index.html?a=1&b=2` + needle = `?` + result = gstr.StrEx(haystack, needle) + ) + fmt.Println(result) + + // Output: + // a=1&b=2 +} +``` + + +### `StrTill` + +- 说明: `StrTill` 返回从 `haystack` 字符串开始到 `needle` 第一次出现的位置的字符串(包含 `needle` 本身)。 + +- 格式: + +```go +StrTill(haystack string, needle string) string +``` + +- 示例: + +```go +func ExampleStrTill() { + var ( + haystack = `https://goframe.org/index.html?test=123456` + needle = `?` + result = gstr.StrTill(haystack, needle) + ) + fmt.Println(result) + + // Output: + // https://goframe.org/index.html? +} +``` + + +### `StrTillEx` + +- 说明: `StrTillEx` 返回从 `haystack` 字符串开始到 `needle` 第一次出现的位置的字符串(不包含 `needle` 本身)。 + +- 格式: + +```go +StrTillEx(haystack string, needle string) string +``` + +- 示例: + +```go +func ExampleStrTillEx() { + var ( + haystack = `https://goframe.org/index.html?test=123456` + needle = `?` + result = gstr.StrTillEx(haystack, needle) + ) + fmt.Println(result) + + // Output: + // https://goframe.org/index.html +} +``` + + +### `SubStr` + +- 说明:`SubStr` 返回字符串 `str` 从 `start` 开始,长度为 `length` 的新字符串。 参数 `length` 是可选的,它默认使用 `str` 的长度。 + +- 格式: + +```go +SubStr(str string, start int, length ...int) (substr string) +``` + +- 示例: + +```go +func ExampleSubStr() { + var ( + str = `1234567890` + start = 0 + length = 4 + subStr = gstr.SubStr(str, start, length) + ) + fmt.Println(subStr) + + // Output: + // 1234 +} +``` + + +### `SubStrRune` + +- 说明:`SubStrRune` 返回 `unicode` 字符串 `str` 从 `start` 开始,长度为 `length` 的新字符串。 参数 `length` 是可选的,它默认使用 `str` 的长度。 + +- 格式: + +```go +SubStrRune(str string, start int, length ...int) (substr string) +``` + +- 示例: + +```go +func ExampleSubStrRune() { + var ( + str = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架。` + start = 14 + length = 3 + subStr = gstr.SubStrRune(str, start, length) + ) + fmt.Println(subStr) + + // Output: + // 高性能 +} +``` + + +### `StrLimit` + +- 说明: `StrLimit` 取 `str` 字符串开始,长度为 `length` 的字符串,加上 `suffix...` 后返回新的字符串。 + +- 格式: + +```go +StrLimit(str string, length int, suffix ...string) string +``` + +- 示例: + +```go +func ExampleStrLimit() { + var ( + str = `123456789` + length = 3 + suffix = `...` + result = gstr.StrLimit(str, length, suffix) + ) + fmt.Println(result) + + // Output: + // 123... +} +``` + + +### `StrLimitRune` + +- 说明: `StrLimitRune` 取 `unicode` 字符串 `str` 开始,长度为 `length` 的字符串,加上 `suffix...` 后返回新的字符串。 + +- 格式: + +```go +StrLimitRune(str string, length int, suffix ...string) string +``` + +- 示例: + +```go +func ExampleStrLimitRune() { + var ( + str = `GoFrame是一款模块化、高性能、企业级的Go基础开发框架。` + length = 17 + suffix = "..." + result = gstr.StrLimitRune(str, length, suffix) + ) + fmt.Println(result) + + // Output: + // GoFrame是一款模块化、高性能... +} +``` + + +### `SubStrFrom` + +- 说明:`SubStrFrom` 返回字符串 `str` 从第一次出现 `need` 到 `str` 的结尾的字符串(包含 `need`)。 + +- 格式: + +```go +SubStrFrom(str string, need string) (substr string) +``` + +- 示例: + +```go +func ExampleSubStrFrom() { + var ( + str = "我爱GoFrameGood" + need = `爱` + ) + + fmt.Println(gstr.SubStrFrom(str, need)) + + // Output: + // 爱GoFrameGood +} +``` + + +### `SubStrFromEx` + +- 说明:`SubStrFromEx` 返回字符串 `str` 从第一次出现 `need` 到 `str` 的结尾的字符串(不包含 `need`)。 + +- 格式: + +```go +SubStrFromEx(str string, need string) (substr string) +``` + +- 示例: + +```go +func ExampleSubStrFromEx() { + var ( + str = "我爱GoFrameGood" + need = `爱` + ) + + fmt.Println(gstr.SubStrFromEx(str, need)) + + // Output: + // GoFrameGood +} +``` + + +### `SubStrFromR` + +- 说明:`SubStrFromR` 返回字符串 `str` 从最后一次出现 `need` 到 `str` 的结尾的字符串(包含 `need`)。 + +- 格式: + +```go +SubStrFromR(str string, need string) (substr string) +``` + +- 示例: + +```go +func ExampleSubStrFromR() { + var ( + str = "我爱GoFrameGood" + need = `Go` + ) + + fmt.Println(gstr.SubStrFromR(str, need)) + + // Output: + // Good +} +``` + + +### `SubStrFromREx` + +- 说明:`SubStrFromREx` 返回字符串 `str` 从最后一次出现 `need` 到 `str` 的结尾的字符串(不包含 `need`)。 + +- 格式: + +```go +SubStrFromREx(str string, need string) (substr string) +``` + +- 示例: + +```go +func ExampleSubStrFromREx() { + var ( + str = "我爱GoFrameGood" + need = `Go` + ) + + fmt.Println(gstr.SubStrFromREx(str, need)) + + // Output: + // od +} +``` + + +## 字符/子串过滤 + +### `Trim` + +- 说明: `Trim` 从字符串的开头和结尾剪切空白(或其他字符)。 可选参数 `characterMask` 指定额外剥离的字符。 + +- 格式: + +```go +Trim(str string, characterMask ...string) string +``` + +- 示例: + +```go +func ExampleTrim() { + var ( + str = `*Hello World*` + characterMask = "*d" + result = gstr.Trim(str, characterMask) + ) + fmt.Println(result) + + // Output: + // Hello Worl +} +``` + + +### `TrimStr` + +- 说明:`TrimStr` 从字符串的开头和结尾去掉所有 `cut` 字符串(不会删除开头或结尾的空白)。 + +- 格式: + +```go +TrimStr(str string, cut string, count ...int) string +``` + +- 示例: + +```go +func ExampleTrimStr() { + var ( + str = `Hello World` + cut = "World" + count = -1 + result = gstr.TrimStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // Hello +} +``` + + +### `TrimLeft` + +- 说明:`TrimLeft` 将字符串开头的空格(或其他字符)删除。 + +- 格式: + +```go +TrimLeft(str string, characterMask ...string) string +``` + +- 示例: + +```go +func ExampleTrimLeft() { + var ( + str = `*Hello World*` + characterMask = "*" + result = gstr.TrimLeft(str, characterMask) + ) + fmt.Println(result) + + // Output: + // Hello World* +} +``` + + +### `TrimLeftStr` + +- 说明:`TrimLeftStr` 从字符串的开头删除 `count` 个 `cut` 字符串(不会删除开头的空格)。 + +- 格式: + +```go +TrimLeftStr(str string, cut string, count ...int) string +``` + +- 示例: + +```go +func ExampleTrimLeftStr() { + var ( + str = `**Hello World**` + cut = "*" + count = 1 + result = gstr.TrimLeftStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // *Hello World** +} +``` + + +### `TrimRight` + +- 说明: `TrimRight` 从字符串的末尾去掉空白(或其他字符)。 + +- 格式: + +```go +TrimRight(str string, characterMask ...string) string +``` + +- 示例: + +```go +func ExampleTrimRight() { + var ( + str = `**Hello World**` + characterMask = "*def" // []byte{"*", "d", "e", "f"} + result = gstr.TrimRight(str, characterMask) + ) + fmt.Println(result) + + // Output: + // **Hello Worl +} +``` + + +### `TrimRightStr` + +- 说明:`TrimRightStr` 从字符串的尾部删除 `count` 个 `cut` 字符串(不会删除尾部的空格)。 + +- 格式: + +```go +TrimRightStr(str string, cut string, count ...int) string +``` + +- 示例: + +```go +func ExampleTrimRightStr() { + var ( + str = `Hello World!` + cut = "!" + count = -1 + result = gstr.TrimRightStr(str, cut, count) + ) + fmt.Println(result) + + // Output: + // Hello World +} +``` + + +### `TrimAll` + +- 说明: `TrimAll` 删除字符串 `str` 中的所有空格(或其他字符)以及 `characterMask` 字符。 + +- 格式: + +```go +TrimAll(str string, characterMask ...string) string +``` + +- 示例: + +```go +func ExampleTrimAll() { + var ( + str = `*Hello World*` + characterMask = "*" + result = gstr.TrimAll(str, characterMask) + ) + fmt.Println(result) + + // Output: + // HelloWorld +} +``` + + +### `HasPrefix` + +- 说明: `HasPrefix` 返回 `s` 是否以 `prefix` 开头。 + +- 格式: + +```go +HasPrefix(s, prefix string) bool +``` + +- 示例: + +```go +func ExampleHasPrefix() { + var ( + s = `Hello World` + prefix = "Hello" + result = gstr.HasPrefix(s, prefix) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +### `HasSuffix` + +- 说明: `HasSuffix` 返回 `s` 是否以 `suffix` 结束。 + +- 格式: + +```go +HasSuffix(s, suffix string) bool +``` + +- 示例: + +```go +func ExampleHasSuffix() { + var ( + s = `my best love is goframe` + prefix = "goframe" + result = gstr.HasSuffix(s, prefix) + ) + fmt.Println(result) + + // Output: + // true +} +``` + + +## 版本比较 + +### `CompareVersion` + +- 说明:`CompareVersion` 将 `a` 和 `b` 作为标准 `GNU` 版本进行比较。 + +- 格式: + +```go +CompareVersion(a, b string) int +``` + +- 示例: + +```go +func ExampleCompareVersion() { + fmt.Println(gstr.CompareVersion("v2.11.9", "v2.10.8")) + fmt.Println(gstr.CompareVersion("1.10.8", "1.19.7")) + fmt.Println(gstr.CompareVersion("2.8.beta", "2.8")) + + // Output: + // 1 + // -1 + // 0 +} +``` + + +### `CompareVersionGo` + +- 说明: `CompareVersionGo` 将 `a` 和 `b` 作为标准的 `Golang` 版本进行比较。 + +- 格式: + +```go +CompareVersionGo(a, b string) int +``` + +- 示例: + +```go +func ExampleCompareVersionGo() { + fmt.Println(gstr.CompareVersionGo("v2.11.9", "v2.10.8")) + fmt.Println(gstr.CompareVersionGo("v4.20.1", "v4.20.1+incompatible")) + fmt.Println(gstr.CompareVersionGo( + "v0.0.2-20180626092158-b2ccc119800e", + "v1.0.1-20190626092158-b2ccc519800e", + )) + + // Output: + // 1 + // 1 + // -1 +} +``` + + +## 相似计算 + +### `Levenshtein` + +- 说明: `Levenshtein` 计算两个字符串之间的 `Levenshtein` 距离。 + +- 格式: + +```go +Levenshtein(str1, str2 string, costIns, costRep, costDel int) int +``` + +- 示例: + +```go +func ExampleLevenshtein() { + var ( + str1 = "Hello World" + str2 = "hallo World" + costIns = 1 + costRep = 1 + costDel = 1 + result = gstr.Levenshtein(str1, str2, costIns, costRep, costDel) + ) + fmt.Println(result) + + // Output: + // 2 +} +``` + + +### `SimilarText` + +- 说明: `SimilarText` 计算两个字符串之间的相似度。 + +- 格式: + +```go +SimilarText(first, second string, percent *float64) int +``` + +- 示例: + +```go +func ExampleSimilarText() { + var ( + first = `AaBbCcDd` + second = `ad` + percent = 0.80 + result = gstr.SimilarText(first, second, &percent) + ) + fmt.Println(result) + + // Output: + // 2 +} +``` + + +### `Soundex` + +- 说明: `Soundex` 用于计算字符串的 `Soundex` 键。 + +- 格式: + +```go +Soundex(str string) string +``` + +- 示例: + +```go +func ExampleSoundex() { + var ( + str1 = `Hello` + str2 = `Hallo` + result1 = gstr.Soundex(str1) + result2 = gstr.Soundex(str2) + ) + fmt.Println(result1, result2) + + // Output: + // H400 H400 +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..f393eb641de --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\226\207\346\234\254\345\244\204\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/text' +title: '文本处理' +sidebar_position: 2 +hide_title: true +keywords: [文本处理,GoFrame,GoFrame框架,组件,编程,开发,工具,前端开发,后端开发,Web开发] +description: '使用GoFrame框架进行文本处理,帮助开发者高效地在GoFrame环境中构建和管理文本内容。利用GoFrame的强大功能,简化编程过程,提高开发效率,让开发者专注于核心业务逻辑实现。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" new file mode 100644 index 00000000000..7e67fdcfa0c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex.md" @@ -0,0 +1,28 @@ +--- +slug: '/docs/components/text-gregex' +title: '正则表达式-gregex' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gregex,正则表达式,正则库,正则解析,解析缓存,执行效率,项目文档,golang] +description: 'GoFrame框架中gregex库的基本功能和用法。gregex是对标准库regexp的封装,提供了简化的正则表达式使用方式,并通过解析缓存设计优化了执行效率,使得正则操作更加高效便捷。' +--- + +## 基本介绍 + +`gregex` 提供了对正则表达式的支持,底层是对标准库 `regexp` 的封装,极大地简化了正则的使用,并采用了解析缓存设计,提高了执行效率。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/text/gregex" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex](https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..013f6f9be04 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,30 @@ +--- +slug: '/docs/components/text-gregex-example' +title: '正则表达式-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,正则表达式,基本使用,编程示例,Golang,文本处理,软件开发,开源框架,Go语言] +description: '展示了如何在GoFrame框架中使用正则表达式进行基本的文本匹配操作。通过一个简单的示例代码,您可以了解如何提取和处理字符串中的信息,是开发者进行文本处理的重要参考。' +--- + +一个简单示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/text/gregex" +) + +func main() { + match, _ := gregex.MatchString(`(\w+).+\-\-\s*(.+)`, `GoFrame is best! -- John`) + fmt.Printf(`%s says "%s" is the one he loves!`, match[2], match[1]) +} +``` + +执行后,输出结果为: + +``` +John says "GoFrame" is the one he loves! +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..b07ca6a2934 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\346\226\207\346\234\254\345\244\204\347\220\206/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-gregex/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,345 @@ +--- +slug: '/docs/components/text-gregex-funcs' +title: '正则表达式-方法介绍' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,正则表达式,IsMatch,Match,MatchAll,Quote,Replace,ReplaceFunc,ReplaceFuncMatch,Split] +description: '使用GoFrame框架进行正则表达式匹配,包括了IsMatch、Match、MatchAll等常用方法,并提供了函数定义和示例。GoFrame框架以其高效的执行性能被用于多种正则处理场景,本文档通过具体示例教学,帮助您更好地理解和应用这些功能。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex](https://pkg.go.dev/github.com/gogf/gf/v2/text/gregex) +::: +> 当函数名中有 `All` 的时候,它会继续查找非重叠的后续并返回 `slice` +> +> 当函数名中有 `String` 的时候,参数及返回值都是 `string`,否则为 `[]byte` + +## `IsMatch/IsMatchString` + +- 说明: `IsMatch()` 方法可以测试字符串,判断是否匹配正则表达式的模式。如果发现一次匹配,该方法返回 `true`,否则返回 `false`。 +- 格式: + +```go +IsMatch(pattern string, src []byte) bool +IsMatchString(pattern string, src string) bool +``` + +- Tip: ` regexp` 已经在底层处理并缓存了 `Regex` 对象,以极大地提高执行效率,无需每次显式重新创建,下同。 +- 示例: + +```go +func ExampleIsMatch() { + patternStr := `\d+` + g.Dump(gregex.IsMatch(patternStr, []byte("hello 2022! hello GoFrame!"))) + g.Dump(gregex.IsMatch(patternStr, nil)) + g.Dump(gregex.IsMatch(patternStr, []byte("hello GoFrame!"))) + + // Output: + // true + // false + // false +} +``` + + +## `Match/MatchString` + +- 说明:用来匹配子字符串, `Match` 只返回第一个匹配的结果。区别于原生的正则方法, `gregex` 是对 `FindSubmatch` 进行封装,直接返回第一个包括子模式结果的 `slice`。 +- 格式 + +```go +Match(pattern string, src []byte) ([][]byte, error) +MatchString(pattern string, src string) ([]string, error) +``` + +- 示例:匹配 `url` 中的参数。 + +```go +func ExampleMatch() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + // This method looks for the first match index + result, err := gregex.Match(patternStr, []byte(matchStr)) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ] + // +} +``` + + +## `MatchAll/MatchAllString` + +- 说明:用来匹配子字符串, `MatchAll` 返回全部的结果。区别于原生的正则方法, `gregex`的 `MatchAll` 是对 `FindAllSubmatch` 方法进行封装,返回所有结果集的 `slice`,包括结果集中的子模式的结果。 +- 格式: + +```go +MatchAllString(pattern string, src string) ([][]string, error) +MatchAll(pattern string, src []byte) ([][][]byte, error) +``` + +- 示例:下面这个例子是匹配 `url` 中的参数。 + +```go +func ExampleMatchAll() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + result, err := gregex.MatchAll(patternStr, []byte(matchStr)) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ], + // [ + // "searchId=8QC5D1D2E", + // "searchId", + // "8QC5D1D2E", + // ], + // ] + // +} + +func ExampleMatchAllString() { + patternStr := `(\w+)=(\w+)` + matchStr := "https://goframe.org/pages/viewpage.action?pageId=1114219&searchId=8QC5D1D2E!" + result, err := gregex.MatchAllString(patternStr, matchStr) + g.Dump(result) + g.Dump(err) + + // Output: + // [ + // [ + // "pageId=1114219", + // "pageId", + // "1114219", + // ], + // [ + // "searchId=8QC5D1D2E", + // "searchId", + // "8QC5D1D2E", + // ], + // ] + // +} +``` + + +## `Quote` + +- 说明:将指定正则表达式中的特定符号进行转义。 +- 格式: + +```go +Quote(s string) string +``` + +- 示例: + +```go +func ExampleQuote() { + result := gregex.Quote(`[1-9]\d+`) + g.Dump(result) + + // Output: + // "\[1-9\]\\d\+" +} +``` + + +## ` Replace/ReplaceString` + +- 说明:用来替换所有匹配的字符串并返回一个源字符串的拷贝。 +- 格式: + +```go +Replace(pattern string, replace, src []byte) ([]byte, error) +ReplaceString(pattern, replace, src string) (string, error) +``` + + +- 示例 + +```go +func ExampleReplace() { + var ( + patternStr = `\d+` + str = "hello GoFrame 2020!" + repStr = "2021" + result, err = gregex.Replace(patternStr, []byte(repStr), []byte(str)) + ) + g.Dump(err) + g.Dump(result) + + // Output: + // + // "hello GoFrame 2021!" +} +``` + + +## `ReplaceFunc/ReplaceStringFunc` + +- 说明:用来替换所有匹配的字符串,返回一个源字符串的拷贝。与 `Replace` 方法的区别在于,该方法可以在闭包中对查询进行二次判断或处理,而非简单的替换。 +- 格式: + +```go +ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte) ([]byte, error) +ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) string) (string, error) +``` + + +- 示例: + +```go +func ExampleReplaceFunc() { + var ( + patternStr = `(\d+)~(\d+)` + str = "hello GoFrame 2018~2020!" + ) + // In contrast to [ExampleReplace] + result, err := gregex.ReplaceFunc(patternStr, []byte(str), func(match []byte) []byte { + g.Dump(match) + return bytes.Replace(match, []byte("2020"), []byte("2023"), -1) + }) + g.Dump(result) + g.Dump(err) + + // ReplaceStringFunc + resultStr, _ := gregex.ReplaceStringFunc(patternStr, str, func(match string) string { + g.Dump(match) + return strings.Replace(match, "2020", "2023", -1) + }) + g.Dump(resultStr) + g.Dump(err) + + // Output: + // "2018~2020" + // "hello gf 2018~2023!" // ReplaceFunc result + // + // "2018~2020" + // "hello gf 2018-2023!" // ReplaceStringFunc result + // +} +``` + + +## `ReplaceFuncMatch/ReplaceStringFuncMatch` + +> `ReplaceFuncMatch` 返回 `src` 的拷贝,其中 `regexp` 的所有匹配都被应用于匹配字节切片的函数的返回值替换。 返回的替换直接替换。 + +- 说明:用来替换所有匹配的字符串,返回一个源字符串的拷贝。该方法的强大之处在于可以在闭包中对查询进行二次判断或处理,且 `MatchString` 函数包含了所有子模式的查询结果,而非简单的替换。 +- 格式: + +```go +ReplaceFuncMatch(pattern string, src []byte, replaceFunc func(match [][]byte) []byte) ([]byte, error) +ReplaceStringFuncMatch(pattern string, src string, replaceFunc func(match []string) string) (string, error) +``` + + +- 示例: + +```go +func ExampleReplaceStringFuncMatch() { + var ( + patternStr = `([A-Z])\w+` + str = "hello Golang 2018~2021!" + ) + // In contrast to [ExampleReplaceFunc] + // the result contains the `pattern' of all subpatterns that use the matching function + result, err := gregex.ReplaceFuncMatch(patternStr, []byte(str), func(match [][]byte) []byte { + g.Dump(match) + return []byte("GoFrame") + }) + g.Dump(result) + g.Dump(err) + + // ReplaceStringFuncMatch + resultStr, err := gregex.ReplaceStringFuncMatch(patternStr, str, func(match []string) string { + g.Dump(match) + match[0] = "Gf" + return match[0] + }) + g.Dump(resultStr) + g.Dump(err) + + // Output: + // [ + // "Golang", + // "G", + // ] + // "hello GoFrame 2018~2021!" // ReplaceFuncMatch result + // + // [ + // "Golang", + // "G", + // ] + // "hello Gf 2018~2021!" // ReplaceStringFuncMatch result + // +} +``` + + +## `Split` + +- 说明:将文本内容由指定的正则表达式进行切割。不包含元字符,相当于 `strings.SplitN`。 +- 格式: + +```go +Split(pattern string, src string) []string +``` + + +- 示例: + +```go +func ExampleSplit() { + patternStr := `\d+` + str := "hello2020GoFrame" + result := gregex.Split(patternStr, str) + g.Dump(result) + + // Output: + // [ + // "hello", + // "GoFrame", + // ] +} +``` + + +## `Validate` + +- 说明:基于原生方法 `compile` 封装,检查给定的正则表达式是否有效 +- 格式: + +```go +Validate(pattern string) error +``` + +- 示例: + +```go +func ExampleValidate() { + // Valid match statement + g.Dump(gregex.Validate(`\d+`)) + // Mismatched statement + g.Dump(gregex.Validate(`[a-9]\d+`)) + + // Output: + // + // { + // Code: "invalid character class range", + // Expr: "a-9", + // } +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" new file mode 100644 index 00000000000..eda894e98c6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\270\212\344\270\213\346\226\207-gctx.md" @@ -0,0 +1,24 @@ +--- +slug: '/docs/components/os-gctx' +title: '上下文-gctx' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gctx,上下文管理,链路跟踪,进程初始化,上下文对象,组件API,New方法,GetInitCtx] +description: 'GoFrame框架中gctx组件的基本概念及常用方法。gctx用于简化链路跟踪及上下文对象的管理,方便进程初始化和上下文操作。主要涉及的内容包括如何创建和获取支持链路跟踪的上下文对象,以及在进程和init包中的应用。结合示例代码和API文档,可更详细地了解gctx的实际应用。' +--- + +## 基本介绍 + +`gctx` 组件实现了对上下文操作的封装,用以简化链路跟踪以及进程初始化上下文对象的管理。 + +## 常用方法 + +这里只简单介绍几个常用的方法,其他方法请查看组件API文档或者源码。 + +### `New` + +用以创建一个带有链路跟踪能力的上下文对象。 + +### `GetInitCtx` + +用在启动进程或者init包方法中,用于获取当前进程带有链路跟踪能力的上下文对象,也用于进程间传递链路跟踪信息。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" new file mode 100644 index 00000000000..9f1c6e5cb13 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\344\272\222\346\226\245\351\224\201-gmutex.md" @@ -0,0 +1,154 @@ +--- +slug: '/docs/components/os-gmutex' +title: '互斥锁-gmutex' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame框架,gmutex互斥锁,互斥锁教程,并发读写控制,锁机制优化,TryLock方法,Func方法示例,基准测试,并发安全,Go编程] +description: 'GoFrame框架中的gmutex互斥锁,该锁支持并发读写控制,与标准库sync.RWMutex类似。其特点是包含Try*方法和*Func方法,用于非阻塞锁机制和特定作用域锁控制。通过示例代码展示了其便捷的使用方式,以及与标准库锁的基准测试对比,展示了其性能优势。适用于需要高效锁机制的并发编程场景。' +--- + +`gmutex.Mutex` 互斥锁对象支持读写控制,互斥锁功能逻辑与标准库 `sync.RWMutex` 类似,可并发读但不可并发写。 + +> 互斥锁的设计细节,推荐阅读轻量级高清版的实现源码: [https://github.com/gogf/gf/blob/master/os/gmutex/gmutex.go](https://github.com/gogf/gf/blob/master/os/gmutex/gmutex.go) + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gmutex" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gmutex](https://pkg.go.dev/github.com/gogf/gf/v2/os/gmutex) + +```go +type Mutex + func (m *Mutex) LockFunc(f func()) + func (m *Mutex) TryLockFunc(f func()) (result bool) +type RWMutex + func New() *RWMutex + func (m *RWMutex) LockFunc(f func()) + func (m *RWMutex) RLockFunc(f func()) + func (m *RWMutex) TryLockFunc(f func()) (result bool) + func (m *RWMutex) TryRLockFunc(f func()) (result bool) +``` + +1. 该互斥锁模块最大的特点是支持 `Try*` 方法以及 `*Func` 方法。 +2. `Try*` 方法用于实现尝试获得特定类型的锁,如果获得锁成功则立即返回 `true`,否则立即返回 `false`,不会阻塞等待,这对于需要使用非阻塞锁机制的业务逻辑非常实用。 +3. `*Func` 方法使用闭包匿名函数的方式实现特定作用域的并发安全锁控制,这对于特定代码块的并发安全控制特别方便,由于内部使用了 `defer` 来释放锁,因此即使函数内部产生异常错误,也不会影响锁机制的安全性控制。 + +### 基准测试 + +`gmutex.Mutex` 与标准库的 `sync.Mutex` 及 `sync.RWMutex` 的基准测试对比结果: [gmutex\_bench\_test.go](https://github.com/gogf/gf/blob/master/os/gmutex/gmutex_bench_test.go) + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/os/gmutex +Benchmark_Mutex_LockUnlock-4 50000000 31.5 ns/op +Benchmark_RWMutex_LockUnlock-4 30000000 54.1 ns/op +Benchmark_RWMutex_RLockRUnlock-4 50000000 27.9 ns/op +Benchmark_GMutex_LockUnlock-4 50000000 27.2 ns/op +Benchmark_GMutex_TryLock-4 100000000 16.7 ns/op +Benchmark_GMutex_RLockRUnlock-4 50000000 38.0 ns/op +Benchmark_GMutex_TryRLock-4 100000000 16.8 ns/op +``` + +### 示例1,基本使用 + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gmutex" +) + +func main() { + ctx := gctx.New() + mu := gmutex.New() + for i := 0; i < 10; i++ { + go func(n int) { + mu.Lock() + defer mu.Unlock() + glog.Print(ctx, "Lock:", n) + time.Sleep(time.Second) + }(i) + } + for i := 0; i < 10; i++ { + go func(n int) { + mu.RLock() + defer mu.RUnlock() + glog.Print(ctx, "RLock:", n) + time.Sleep(time.Second) + }(i) + } + time.Sleep(15 * time.Second) +} +``` + +执行后,终端输出: + +```html +2019-07-13 16:19:55.417 Lock: 0 +2019-07-13 16:19:56.421 Lock: 1 +2019-07-13 16:19:57.424 RLock: 0 +2019-07-13 16:19:57.424 RLock: 4 +2019-07-13 16:19:57.425 RLock: 8 +2019-07-13 16:19:57.425 RLock: 2 +2019-07-13 16:19:57.425 RLock: 7 +2019-07-13 16:19:57.425 RLock: 5 +2019-07-13 16:19:57.425 RLock: 9 +2019-07-13 16:19:57.425 RLock: 1 +2019-07-13 16:19:57.425 RLock: 6 +2019-07-13 16:19:57.425 RLock: 3 +2019-07-13 16:19:58.429 Lock: 3 +2019-07-13 16:19:59.433 Lock: 4 +2019-07-13 16:20:00.438 Lock: 5 +2019-07-13 16:20:01.443 Lock: 6 +2019-07-13 16:20:02.448 Lock: 7 +2019-07-13 16:20:03.452 Lock: 8 +2019-07-13 16:20:04.456 Lock: 9 +2019-07-13 16:20:05.461 Lock: 2 +``` + +这里使用 `glog` 打印的目的,是可以方便地看到打印输出的时间。可以看到,在第3秒的时候,读锁抢占到了机会,由于 `gmutex.Mutex` 对象支持并发读但不支持并发写,因此读锁抢占后迅速执行完毕;而写锁依旧保持每秒打印一条日志继续执行。 + +### 示例2, `*Func` 使用 + +```go +package main + +import ( + "time" + + "github.com/gogf/gf/v2/os/glog" + + "github.com/gogf/gf/v2/os/gmutex" +) + +func main() { + mu := gmutex.New() + go mu.LockFunc(func() { + glog.Println("lock func1") + time.Sleep(1 * time.Second) + }) + time.Sleep(time.Millisecond) + go mu.LockFunc(func() { + glog.Println("lock func2") + }) + time.Sleep(2 * time.Second) +} +``` + +执行后,终端输出: + +```html +2019-07-13 16:28:10.381 lock func1 +2019-07-13 16:28:11.385 lock func2 +``` + +可以看到,使用 `*Func` 方法实现特定作用域的锁控制非常方便。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" new file mode 100644 index 00000000000..326764eae04 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\206\205\345\255\230\351\224\201-gmlock.md" @@ -0,0 +1,149 @@ +--- +slug: '/docs/components/os-gmlock' +title: '内存锁-gmlock' +sidebar_position: 1 +hide_title: true +keywords: [内存锁,动态互斥锁,GoFrame,gmlock,并发安全,TryLock,Remove方法,动态创建互斥锁,GoFrame框架,锁管理] +description: '内存锁模块提供了基于GoFrame框架的动态互斥锁功能,支持给定键名动态生成锁,实现并发安全和TryLock特性。通过GoFrame提供的方法,可以方便地在需要动态创建大量互斥锁的场景中应用,如在多goroutine并发处理中有效管理锁,确保资源安全访问。' +--- + +内存锁模块,也称之为 `动态互斥锁` 模块,支持按照 `给定键名动态生成互斥锁`,并发安全并支持 `Try*Lock` 特性。 + +> 当维护大量动态互斥锁的场景时,如果不再使用的互斥锁对象,请手动调用 `Remove` 方法删除掉。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gmlock" +``` + +**使用场景**: 需要 `动态创建互斥锁`,或者需要 `维护大量动态锁` 的场景; + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gmlock](https://pkg.go.dev/github.com/gogf/gf/v2/os/gmlock) + +```go +func Lock(key string) +func LockFunc(key string, f func()) +func RLock(key string) +func RLockFunc(key string, f func()) +func RUnlock(key string) +func Remove(key string) +func TryLock(key string) bool +func TryLockFunc(key string, f func()) bool +func TryRLock(key string) bool +func TryRLockFunc(key string, f func()) bool +func Unlock(key string) +type Locker + func New() *Locker + func (l *Locker) Clear() + func (l *Locker) Lock(key string) + func (l *Locker) LockFunc(key string, f func()) + func (l *Locker) RLock(key string) + func (l *Locker) RLockFunc(key string, f func()) + func (l *Locker) RUnlock(key string) + func (l *Locker) Remove(key string) + func (l *Locker) TryLock(key string) bool + func (l *Locker) TryLockFunc(key string, f func()) bool + func (l *Locker) TryRLock(key string) bool + func (l *Locker) TryRLockFunc(key string, f func()) bool + func (l *Locker) Unlock(key string) +``` + +### 示例1,基本使用 + +```go +package main + +import ( + "time" + "sync" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gmlock" +) + +func main() { + key := "lock" + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + gmlock.Lock(key) + glog.Println(i) + time.Sleep(time.Second) + gmlock.Unlock(key) + wg.Done() + }(i) + } + wg.Wait() +} +``` + +该示例中,模拟了同时开启 `10` 个 `goroutine`,但同一时刻只能有一个 `goroutine` 获得锁,获得锁的 `goroutine` 执行 `1` 秒后退出,其他 `goroutine` 才能获得锁。 + +执行后,输出结果为: + +```html +2018-10-15 23:57:28.295 9 +2018-10-15 23:57:29.296 0 +2018-10-15 23:57:30.296 1 +2018-10-15 23:57:31.296 2 +2018-10-15 23:57:32.296 3 +2018-10-15 23:57:33.297 4 +2018-10-15 23:57:34.297 5 +2018-10-15 23:57:35.297 6 +2018-10-15 23:57:36.298 7 +2018-10-15 23:57:37.298 8 +``` + +### 示例2,TryLock非阻塞锁 + +`TryLock` 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回 `true`;如果获取失败(即互斥锁已被其他 `goroutine` 获取),则返回 `false`。 + +```go +package main + +import ( + "sync" + "github.com/gogf/gf/v2/os/glog" + "time" + "github.com/gogf/gf/v2/os/gmlock" +) + +func main() { + key := "lock" + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(i int) { + if gmlock.TryLock(key) { + glog.Println(i) + time.Sleep(time.Second) + gmlock.Unlock(key) + } else { + glog.Println(false) + } + wg.Done() + }(i) + } + wg.Wait() +} +``` + +同理,在该示例中,同时也只有 `1` 个 `goroutine` 能获得锁,其他 `goroutine` 在 `TryLock` 失败便直接退出了。 + +执行后,输出结果为: + +```html +2018-10-16 00:01:59.172 9 +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.172 false +2018-10-16 00:01:59.176 false +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" new file mode 100644 index 00000000000..6d776e7fdd8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\215\217\347\250\213\347\256\241\347\220\206-grpool.md" @@ -0,0 +1,380 @@ +--- +slug: '/docs/components/os-grpool' +title: '协程管理-grpool' +sidebar_position: 16 +hide_title: true +keywords: [GoFrame,GoFrame框架,goroutine,协程池,高并发,grpool,任务队列,资源管理,异步执行,内存优化] +description: 'Go语言中轻量级的协程管理工具grpool,讨论其在高并发下的性能优势和资源复用。通过池化技术,对大量goroutine进行管理,以降低内存占用和优化全局调度,适用于异步任务和内存使用率要求高的场景。' +--- + +## 基本介绍 + +`Go` 语言中的 `goroutine` 虽然相对于系统线程来说比较轻量级(初始栈大小仅 `2KB`),(并且支持动态扩容),而正常采用 `Java`、 `C++` 等语言启用的线程一般都是内核态的占用的内存资源一般在 `4m` 左右,而假设我们的服务器 `CPU` 内存为 `4G`,那么很明显内核态线程的并发总数量也就 `1024` 个,相反 `Go` 语言的协程则可以达到 `4*1024*1024/2=200w`,这么一看就明白了为什么Go语言天生支持高并发。 + +## 痛点描述 + +### 协程执行的资源消耗大 + +但是在高并发量下的 `goroutine` 频繁创建和销毁对于性能损耗以及 `GC` 来说压力也不小。充分将 `goroutine` 复用,减少 `goroutine` 的创建/销毁的性能损耗,这便是 `grpool` 对 `goroutine` 进行池化封装的目的。例如,针对于 `100W` 个执行任务,使用 `goroutine` 的话需要不停创建并销毁 `100W` 个 `goroutine`,而使用 `grpool` 也许底层只需要几万个 `goroutine` 便能充分复用地执行完成所有任务。 + +经测试, `goroutine` 池对于业务逻辑的执行效率(降低执行时间/CPU使用率)提升不大,甚至没有原生的 `goroutine` 执行快速(池化 `goroutine` 执行调度并没有底层 `Go` 调度器高效,因为池化 `goroutine` 的执行调度也是基于底层 `Go` 调度器),但是由于采用了复用的设计,池化后对内存的使用率得到极大的降低。 + +### 大量协程影响全局协程调度 + +某些业务模块需要动态创建协程来执行,例如异步采集任务、指标计算任务等等。这些业务逻辑不是服务的核心逻辑,并且会产生协程。在极端情况下可能会引起协程大暴涨,影响底层 `Go` 引擎全局的写成调度,造成服务整体执行延迟较大。 + +拿异步采集任务来举个例子,每隔 `5` 秒执行一次,每次创建 `100` 个协程来采集不同的目标端。当采集出现网络延迟时,上一步的任务并未执行完,下一次的任务又新创建协程开始执行。当积累的任务越来越多,会造成协程的暴涨,影响全局的服务执行。针对这一类场景,我们可以通过池化的技术来将任务进行定量执行,当池中的任务堆积到达一定量时,后续的任务应当阻塞。例如,我们设定池中任务的最大数量为 `10000` 个,后续不停将任务丢到池中执行,当超过池的最大数量时,任务执行将会阻塞,但并不会影响全局的服务执行。 + +## 概念介绍 + +### `Pool` + +`goroutine` 池,用于管理若干可复用的 `goroutine` 协程资源。 + +### `Job` + +添加到池对象的任务队列中等待执行的任务,是一个 `Func` 的方法,一个 `Job` 同时只能被一个 `Worker` 获取并执行。 `Func` 的定义如下: + +```go +type Func func(ctx context.Context) +``` + +### `Worker` + +池对象中参与任务执行的 `goroutine`,一个 `Worker` 可以执行若干个 `Job`,直到队列中再无等待的 `Job`。 + +## 使用介绍 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/grpool" + +``` + +**使用场景**: + +管理大量异步任务的场景、需要异步协程复用的场景、需要降低内存使用率的场景。 + +**接口文档**: + +```go +func Add(ctx context.Context, f Func) error +func AddWithRecover(ctx context.Context, userFunc Func, recoverFunc RecoverFunc) error +func Jobs() int +func Size() int +func New(limit ...int) *Pool + func (p *Pool) Add(ctx context.Context, f Func) error + func (p *Pool) AddWithRecover(ctx context.Context, userFunc Func, recoverFunc RecoverFunc) error + func (p *Pool) Cap() int + func (p *Pool) Close() + func (p *Pool) IsClosed() bool + func (p *Pool) Jobs() int + func (p *Pool) Size() int +``` + +通过 `grpool.New` 方法创建一个 `goroutine池` 对象,参数 `limit` 为非必需参数,用于限定池中的工作 `goroutine` 数量,默认为不限制。需要注意的是,任务可以不停地往池中添加,没有限制,但是工作的 `goroutine` 是可以做限制的。我们可以通过 `Size()` 方法查询当前的工作 `goroutine` 数量,使用 `Jobs()` 方法查询当前池中待处理的任务数量。 + +同时,为便于使用, `grpool` 包提供了默认的 `goroutine` 池,默认的池对象不限制 `goroutine` 数量,直接通过 `grpool.Add` 即可往默认的池中添加任务,任务参数必须是一个 `func()` 类型的函数/方法。 + +这个模块大家问得最多的是外部如何给 `grpool` 里面的任务传递参数,具体请看示例2。 + +## 使用示例 + +### 使用默认的 `goroutine` 池,限制 `100` 个 `goroutine` 执行 `1000` 个任务 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +var ( + ctx = gctx.New() +) + +func job(ctx context.Context) { + time.Sleep(1*time.Second) +} + +func main() { + pool := grpool.New(100) + for i := 0; i < 1000; i++ { + pool.Add(ctx,job) + } + fmt.Println("worker:", pool.Size()) + fmt.Println(" jobs:", pool.Jobs()) + gtimer.SetInterval(ctx,time.Second, func(ctx context.Context) { + fmt.Println("worker:", pool.Size()) + fmt.Println(" jobs:", pool.Jobs()) + fmt.Println() + }) + + select {} +} +``` + +这段程序中的任务函数的功能是 `sleep 1秒钟`,这样便能充分展示出goroutine数量限制功能。其中,我们使用了 `gtime.SetInterval` 定时器每隔1秒钟打印出当前默认池中的工作 `goroutine` 数量以及待处理的任务数量。 + +### 异步传参:来个新手容易出错的例子 + +> 这个例子在go版本≥1.22时不再生效,即go 1.22以后不再有循环变量陷阱。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "sync" +) + +var ( + ctx = gctx.New() +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + grpool.Add(ctx,func(ctx context.Context) { + fmt.Println(i) + wg.Done() + }) + } + wg.Wait() +} +``` + +我们这段代码的目的是要顺序地打印出0-9,然而运行后却输出: + +```10 +10 +10 +10 +10 +10 +10 +10 +10 +10 +``` + +为什么呢?这里的执行结果无论是采用 `go` 关键字来执行还是 `grpool` 来执行都是如此。原因是,对于异步线程/协程来讲,函数进行异步执行注册时,该函数并未真正开始执行(注册时只在 `goroutine` 的栈中保存了变量 `i` 的内存地址),而一旦开始执行时函数才会去读取变量 `i` 的值,而这个时候变量 `i` 的值已经自增到了 `10`。 清楚原因之后,改进方案也很简单了,就是在注册异步执行函数的时候,把当时变量 `i` 的值也一并传递获取;或者把当前变量i的值赋值给一个不会改变的临时变量,在函数中使用该临时变量而不是直接使用变量 `i`。 + +改进后的示例代码如下: + +**1)、使用go关键字** + +```go +package main + +import ( + "fmt" + "sync" +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + go func(v int){ + fmt.Println(v) + wg.Done() + }(i) + } + wg.Wait() +} + +``` + +执行后,输出结果为: + +```0 +9 +3 +4 +5 +6 +7 +8 +1 +2 +``` + +注意,异步执行时并不会保证按照函数注册时的顺序执行,以下同理。 + +**2)、使用临时变量** + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "sync" +) + +var ( + ctx = gctx.New() +) + +func main() { + wg := sync.WaitGroup{} + for i := 0; i < 10; i++ { + wg.Add(1) + v := i + grpool.Add(ctx, func(ctx context.Context) { + fmt.Println(v) + wg.Done() + }) + } + wg.Wait() +} +``` + +执行后,输出结果为: + +```9 +0 +1 +2 +3 +4 +5 +6 +7 +8 +``` + +这里可以看到,使用 `grpool` 进行任务注册时,注册方法为 `func(ctx context.Context)`,因此无法在任务注册时把变量 `i` 的值注册进去(请尽量不要通过 `ctx` 传递业务参数),因此只能采用临时变量的形式来传递当前变量 `i` 的值。 + +### 自动捕获 `goroutine` 错误: `AddWithRecover` + +`AddWithRecover` 将新作业推送到具有指定恢复功能的池中。当 `userFunc` 执行过程中出现 `panic` 时,会调用可选的 `Recovery Func`。如果没有传入 `Recovery Func` 或赋空,则忽略 `userFunc` 引发的 `panic`。该作业将异步执行。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/container/garray" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "time" +) + +var ( + ctx = gctx.New() +) +func main() { + array := garray.NewArray(true) + grpool.AddWithRecover(ctx, func(ctx context.Context) { + array.Append(1) + array.Append(2) + panic(1) + }, func(err error) { + array.Append(1) + }) + grpool.AddWithRecover(ctx, func(ctx context.Context) { + panic(1) + array.Append(1) + }) + time.Sleep(500 * time.Millisecond) + fmt.Print(array.Len()) +} +``` + +### 测试一下 `grpool` 和原生的 `goroutine` 之间的性能 + +**1)、 `grpool`** + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/grpool" + "github.com/gogf/gf/v2/os/gtime" + "sync" + "time" +) + +var ( + ctx = gctx.New() +) + +func main() { + start := gtime.TimestampMilli() + wg := sync.WaitGroup{} + for i := 0; i < 10000000; i++ { + wg.Add(1) + grpool.Add(ctx,func(ctx context.Context) { + time.Sleep(time.Millisecond) + wg.Done() + }) + } + wg.Wait() + fmt.Println(grpool.Size()) + fmt.Println("time spent:", gtime.TimestampMilli() - start) +} +``` + +**2)、 `goroutine`** + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" + "sync" + "time" +) + +func main() { + start := gtime.TimestampMilli() + wg := sync.WaitGroup{} + for i := 0; i < 10000000; i++ { + wg.Add(1) + go func() { + time.Sleep(time.Millisecond) + wg.Done() + }() + } + wg.Wait() + fmt.Println("time spent:", gtime.TimestampMilli() - start) +} +``` + +**3)、运行结果比较** + +测试结果为两个程序各运行 `3` 次取平均值。 + +```shell +grpool: + goroutine count: 847313 + memory spent: ~2.1 G + time spent: 37792 ms + +goroutine: + goroutine count: 1000W + memory spent: ~4.8 GB + time spent: 27085 ms + +``` + +可以看到池化过后,执行相同数量的任务, `goroutine` 数量减少很多,相对的内存也降低了一倍以上,CPU时间耗时也勉强可以接受。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" new file mode 100644 index 00000000000..21a1841a35d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\221\275\344\273\244\347\256\241\347\220\206-gcmd.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcmd' +title: '命令管理-gcmd' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,命令管理,gcmd,核心组件,命令行工具,开发框架,命令执行,代码管理,软件开发] +description: '使用GoFrame框架中的命令管理组件gcmd进行命令行操作,包括如何创建和管理命令、执行命令以及命令的参数配置。是GoFrame核心组件之一,适用于各种开发场景,提高开发效率。' +--- + +具体请参考章节: [命令管理](../../核心组件/命令管理/命令管理.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" new file mode 100644 index 00000000000..bd92fd4d12a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron.md" @@ -0,0 +1,48 @@ +--- +slug: '/docs/components/os-gcron' +title: '定时任务-gcron' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcron,定时任务,crontab,CRON语法,任务管理,编程接口,框架教程,Go语言] +description: 'GoFrame框架中gcron模块的使用,提供类似crontab的定时任务管理功能,支持秒级管理。介绍了如何创建、添加、管理和删除定时任务,并强调了全局时区对定时任务执行的影响,适用于需要编写定时任务的开发者。' +--- + +## 基本介绍 + +`gcron` 模块提供了对定时任务的实现,支持类似 `crontab` 的配置管理方式,并支持最小粒度到 `秒` 的定时任务管理。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gcron" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gcron](https://pkg.go.dev/github.com/gogf/gf/v2/os/gcron) + +**简要说明:** + +1. `New` 方法用于创建自定义的定时任务管理对象。 +2. `Add` 方法用于添加定时任务,其中: + - \- `pattern` 参数使用 `CRON语法格式`(具体说明见本章后续相关说明)。 + - \- `job` 参数为需要执行的任务方法(方法地址)。 + - \- `name` 为非必需参数,用于给定时任务指定一个 **唯一的** 名称,注意如果已存在相同名称的任务,那么添加定时任务将会失败。 +3. `AddSingleton` 方法用于添加单例定时任务,即同时只能有一个该任务正在运行(在内存中进行去重判断)。 +4. `AddOnce` 方法用于添加只运行一次的定时任务,当运行一次数后该定时任务自动销毁。 +5. `AddTimes` 方法用于添加运行指定次数的定时任务,当运行 `times` 次数后该定时任务自动销毁。 +6. `Entries` 方法用于获取当前所有已注册的定时任务信息。 +7. `Remove` 方法用于根据名称删除定时任务(停止并删除)。 +8. `Search` 方法用于根据名称进行定时任务搜索(返回定时任务 `*Entry` 对象指针)。 +9. `Start` 方法用于启动定时任务( `Add` 后自动启动定时任务), 可通过 `name` 参数指定需要启动的任务名称。 +10. `Stop` 方法用于停止定时任务( `Remove` 会停止并删除), 可通过 `name` 参数指定需要停止的任务名称。 +11. `Close` 方法用于关闭自定义的定时任务管理对象。 + +## 注意事项 + +- 进程全局时区的影响:由于定时任务严格依赖时间计算,因此进程的全局时区对定时任务执行影响比较大。在添加定时任务时,请注意当前进程的全局时区设置,在没有设置全局时区时,默认使用的是系统时区。关于时区设置更多信息请参考: [时间管理-时区设置](../时间管理-gtime/时间管理-时区设置.md) + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" new file mode 100644 index 00000000000..edc4273538e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-gcron\344\270\216gtimer.md" @@ -0,0 +1,22 @@ +--- +slug: '/docs/components/os-gcron-differ-with-gtimer' +title: '定时任务-gcron与gtimer' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,定时任务,gcron,gtimer,性能模块,crontab,时间间隔,TCP通信,游戏开发] +description: 'GoFrame框架中定时任务模块gcron与定时器模块gtimer的区别。gtimer是高性能模块,适用于各种定时任务场景,包括TCP通信和游戏开发。gcron支持crontab语法,基于gtimer实现,为用户提供了便捷的定时任务管理方式。' +--- + +## `gcron` 与 `gtimer` 区别 + +[定时任务-gcron](定时任务-gcron.md) 与 [定时器-gtimer](../定时器-gtimer/定时器-gtimer.md) 区别: + +- `gtimer` 属于高性能模块,是框架核心模块,构建任何定时任务的基础,任何方法操作耗时均在 `纳秒` 级别。 +- `gtimer` 可适用于任何的定时任务场景中,例如: TCP通信、游戏开发等场景。 +- `gcron` 支持经典的 `crontab` 形式的定时任务语法,最小时间设定间隔为 `秒`。 +- `gcron` 底层实现基于 `gtimer`。 + +| 相似模块 | 说明 | 性能 | 类Linux Crontab模式 | 底层实现 | +| --- | --- | --- | --- | --- | +| [定时任务-gcron](定时任务-gcron.md) | 定时任务。
    较上层封装,时间刻度以自然秒为单位。 | 一般 | 支持 | 基于 `gtimer` | +| [定时器-gtimer](../定时器-gtimer/定时器-gtimer.md) | 定时器。
    底层组件,时间刻度以时间槽为单位(时间槽可自定义)。 | 高效 | 不支持 | 基于 `PriorityQueue` 数据结构自实现 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..e93198b426e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,421 @@ +--- +slug: '/docs/components/os-gcron-example' +title: '定时任务-基本使用' +sidebar_position: 1 +hide_title: true +keywords: [定时任务,GoFrame,GoFrame框架,gcron,单例定时任务,单次定时任务,指定次数任务,任务搜索,任务停止,任务删除] +description: '在GoFrame框架中使用gcron来管理定时任务。你将学习如何添加、启动、停止、删除和搜索定时任务。此外,还涵盖了单例定时任务、单次定时任务以及运行指定次数的任务等高级功能。这些功能帮助开发者更高效地管理和调试应用内的定时任务,提高应用的性能和可靠性。' +--- + +## 基本使用 + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + ) + _, err = gcron.Add(ctx, "* * * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "Every second") + }, "MySecondCronJob") + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "0 30 * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour on the half hour") + }) + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "@hourly", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour") + }) + if err != nil { + panic(err) + } + + _, err = gcron.Add(ctx, "@every 1h30m", func(ctx context.Context) { + g.Log().Print(ctx, "Every hour thirty") + }) + if err != nil { + panic(err) + } + + g.Dump(gcron.Entries()) + + time.Sleep(3 * time.Second) + + g.Log().Print(ctx, `stop cronjob "MySecondCronJob"`) + gcron.Stop("MySecondCronJob") + + time.Sleep(3 * time.Second) + + g.Log().Print(ctx, `start cronjob "MySecondCronJob"`) + gcron.Start("MySecondCronJob") + + time.Sleep(3 * time.Second) +} +``` + +执行后,输出结果为: + +```text +[ + { + Name: "MySecondCronJob", + Job: 0x14077e0, + Time: "2021-11-14 12:13:53.445132 +0800 CST m=+0.006167069", + }, + { + Name: "cron-1", + Job: 0x14078a0, + Time: "2021-11-14 12:13:53.44515 +0800 CST m=+0.006185688", + }, + { + Name: "cron-2", + Job: 0x1407960, + Time: "2021-11-14 12:13:53.445161 +0800 CST m=+0.006196483", + }, + { + Name: "cron-3", + Job: 0x1407a20, + Time: "2021-11-14 12:13:53.445218 +0800 CST m=+0.006252937", + }, +] +2021-11-14 12:13:54.442 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:55.441 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:56.440 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:13:56.445 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} stop cronjob "MySecondCronJob" +2021-11-14 12:13:59.445 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} start cronjob "MySecondCronJob" +2021-11-14 12:14:00.443 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:14:01.442 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +2021-11-14 12:14:02.443 {189cwi9ngk0cfp7l8gcwciw100sr9cuu} Every second +``` + +## `AddSingleton`添加单例定时任务 + +单例定时任务,即同时只能有一个该任务正在运行。当第二个相同的定时任务触发执行时,如果发现已有该任务正在执行,第二个任务将会退出不执行,定时器将会继续等待下一次定时任务的触发检测,以此类推。可以使用 `AddSingleton` 添加单例定时任务。 + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + ) + _, err = gcron.AddSingleton(ctx, "* * * * * *", func(ctx context.Context) { + g.Log().Print(ctx, "doing") + time.Sleep(2 * time.Second) + }) + if err != nil { + panic(err) + } + select {} +} +``` + +执行后,输出结果为: + +```text +2021-11-14 12:16:54.073 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:16:57.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:00.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:03.071 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:06.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +2021-11-14 12:17:09.072 {189cwi9nmm0cfp7niz319fc100zrw0ig} doing +... +``` + +## `AddOnce`添加单次定时任务 + +单次定时任务, `AddOnce` 方法用于添加只运行一次的定时任务,当运行一次数后该定时任务自动销毁,`Size` 方法可以查看运行状态,相关方法: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddOnce(ctx, "@every 2s", func(ctx context.Context) { + array.Append(1) + }) + fmt.Println(cron.Size(),array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(),array.Len()) + +} +``` + +执行后,输出结果为: + +```text +1 0 +0 1 +``` + +## `AddTimes`添加指定次数的定时任务 + +指定次数的定时任务, `AddTimes` 方法用于添加运行指定次数的定时任务,当运行 `times` 次数后该定时任务自动销毁, `Size` 方法可以查看运行状态,相关方法: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 2,func(ctx context.Context) { + array.Append(1) + }) + fmt.Println(cron.Size(), array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(), array.Len()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(cron.Size(), array.Len()) +} +``` + +执行后,输出结果为: + +```text +1 0 +1 1 +0 2 +``` + +## `Entries`获取注册的定时任务列表 + +获取所有注册的定时任务信息, `Entries` 方法用于获取当前所有已注册的定时任务信息,以切片的形式返回(按注册时间 `asc` 排序),相关方法: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 1s", 2,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 1s",func(ctx context.Context) { + array.Append(1) + },"cron2") + entries := cron.Entries() + for k, v := range entries{ + fmt.Println(k,v.Name,v.Time) + + } + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len()) + +} +``` + +执行后,输出结果为: + +```text +0 cron2 2022-02-09 10:11:47.2421345 +0800 CST m=+0.159116501 +1 cron1 2022-02-09 10:11:47.2421345 +0800 CST m=+0.159116501 +3 +``` + +## `Search`搜索定时任务 + +`Search` 搜索返回具有指定“名称”的计划任务。(返回定时任务 `*Entry` 对象指针),如果找不到,则返回nil。相关方法: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 1s", 2,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 1s",func(ctx context.Context) { + array.Append(1) + },"cron2") + search := cron.Search("cron2") + + g.Log().Print(ctx, search) + + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len()) + + // Output: + // 3 +} + +``` + +执行后,输出结果为: + +```text +2022-02-09 10:52:30.011 {18a909957cfed11680c1b145da1ef096} {"Name":"cron2","Time":"2022-02-09T10:52:29.9972842+08:00"} +``` + +## `Stop`暂停定时任务 + +`Stop` 方法用于停止定时任务( `Stop`会停止但不会删除), 可通过 `name` 参数指定需要停止的任务名称,如果没有指定 `name`,它将停止整个 `cron`。相关方法: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 1,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + fmt.Println(array.Len(),cron.Size()) + cron.Stop("cron2") + fmt.Println(array.Len(),cron.Size()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + + // Output: + // 1 + // 1 +} +``` + +执行后,输出结果为: + +```text +0 2 +0 2 +1 1 +``` + + +:::info +`Stop`方法会对定时任务做暂停标记,并立即返回,并不会等待正在执行的定时任务结束。但在某些场景下,我们可能需要等待当前正在执行的定时任务完成后再退出。从`v2.8`版本开始,提供了`StopGracefully`方法,用于优雅地暂停定时任务。该方法会阻塞等待正在执行的定时任务完成后再返回。 +::: + +## `Remove`停止并删除任务 + +`Remove` 方法用于根据名称 `name` 删除定时任务(停止并删除);相关方法: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddTimes(ctx, "@every 2s", 1,func(ctx context.Context) { + array.Append(1) + },"cron1") + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + fmt.Println(array.Len(),cron.Size()) + cron.Remove("cron2") + fmt.Println(array.Len(),cron.Size()) + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + // Output: + // 0 2 + // 0 1 + // 1 0 +} +``` + +执行后,输出结果为: + +```text +0 2 +0 1 +1 0 +``` + +## `Start`启动定时任务 + +`Start` 方法用于启动定时任务( `Add` 后自动启动定时任务), 可通过 `name` 参数指定需要启动的任务名称。如果没有指定 `name`,它将启动整个 `cron`。相关方法: + +```go +func main() { + var ( + ctx = gctx.New() + ) + cron := gcron.New() + array := garray.New(true) + cron.AddOnce(ctx, "@every 2s",func(ctx context.Context) { + array.Append(1) + },"cron2") + cron.Stop("cron2") + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + cron.Start("cron2") + time.Sleep(3000 * time.Millisecond) + fmt.Println(array.Len(),cron.Size()) + + // Output: + // 0 1 + // 1 0 +} +``` + +执行后,输出结果为: + +```text +0 1 +1 0 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..fdf83fe92fe --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\346\227\245\345\277\227\347\256\241\347\220\206.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/components/os-gcron-logging' +title: '定时任务-日志管理' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcron,日志管理,定时任务,日志组件,日志输出,日志级别,GoFrame日志,glog] +description: '在GoFrame框架中的gcron组件中进行日志管理。gcron支持设置日志输出文件和级别,默认记录错误级别日志。通过GoFrame框架的日志组件,用户可以复用日志的所有特性。文章中提供了Go代码示例,展示了如何设置和使用gcron的日志功能。' +--- + +`gcron` 支持日志功能,并可设置日志输出的文件以及级别。默认情况下仅会输出 `LEVEL_WARN | LEVEL_ERRO | LEVEL_CRIT` 错误级别的日志(包括定时任务运行异常日志),运行日志以 `LEVEL_DEBUG` 的级别进行记录,因此默认不会记录。 `gcron` 组件使用了 `goframe` 框架统一的日志组件,因此可以复用日志组件的所有特性。相关方法: + +```go +func SetLogger(logger glog.ILogger) +func GetLogger() glog.ILogger +``` +:::tip +日志组件特性请参考 [日志组件](../../../核心组件/日志组件/日志组件.md) 章节。 +::: +使用示例: + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gcron" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "time" +) + +func main() { + var ( + err error + ctx = gctx.New() + logger = glog.New() + ) + logger.SetLevel(glog.LEVEL_ALL) + gcron.SetLogger(logger) + _, err = gcron.Add(ctx, "* * * * * ?", func(ctx context.Context) { + g.Log().Info(ctx, "test") + }) + if err != nil { + panic(err) + } + time.Sleep(3 * time.Second) +} +``` + +执行后,终端输出结果为: + +![](/markdown/673cee2f61375b3979a03c30934fd8d8.png) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 00000000000..ae334472a7d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\344\273\273\345\212\241-gcron/\345\256\232\346\227\266\344\273\273\345\212\241-\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,106 @@ +--- +slug: '/docs/components/os-gcron-pattern' +title: '定时任务-表达式' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,定时任务,cron表达式,Linux Crontab,时间调度,特殊字符,时间间隔,表达式示例,预定义格式,秒级定时] +description: 'GoFrame框架中定时任务的cron表达式及其使用技巧。cron表达式由六个字段组成,可实现从秒到周的时间调度。讲解了特殊字符的意义及其在表达式中的应用,通过多种预定义格式和间隔配置,使任务调度更加灵活可靠。' +--- + +## 基本介绍 + +`cron表达式` 表示一组时间,使用 `6` 个空格分隔的字段。 + +``` +Seconds Minutes Hours Day Month Week +``` + +> 即 `秒 分 时  日 月 周` + +每个字段的含义如下: + +``` +Field name | Allowed values | Allowed special characters +---------- | -------------- | -------------------------- +Seconds | 0-59 | * / , - # +Minutes | 0-59 | * / , - +Hours | 0-23 | * / , - +Day | 1-31 | * / , - ? +Month | 1-12 or JAN-DEC | * / , - +Week | 0-6 or SUN-SAT | * / , - ? +``` +:::warning +月份和星期字段值英文不区分大小写。 例如传递 `SUN`、 `Sun` 和 `sun` 同样被接受。 +::: +## 特殊字符 + +#### 星号( `*`) + +星号表示 `cron` 表达式将匹配所有的值。例如,在第五个字段( `Month`)中使用星号表示每个月。 + +#### 斜线( `/`) + +斜杠用于描述范围的增量。例如:第二个字段使用 `3-59/15` 表示每小时的第 `3` 分钟开始到第 `59` 分钟,每隔 `15` 分钟执行。 + +#### 逗号( `,`) + +逗号用于分隔列表的项目。例如,第五个字段使用 `MON,WED,FRI` 将指每周一,周三和周五执行。 + +#### 连字符( `-`) + +连字符用于定义范围。例如,第三个字段使用 `9-17` 表示每天上午 `9` 点至下午 `5` 点(含)。 + +#### 忽略号( `#`) + +忽略号表示 `cron` 表达式将忽略这个字段的使用,目前仅秒字段支持该符号,用于将 `6` 段 `cron pattern` 无缝转换为 `5` 段 `linux crontab pattern`。 + +#### 问号( `?`) + +可以使用 `问号` 而不是 `*` 来让 `Day` 或 `Week` 字段为空。 + +#### 预定义格式 + +您可以使用几个预定义的时间来代替 `cron` 表达式。 + +``` +Entry | Description | Equivalent To +----- | ----------- | ------------- +@yearly (or @annually) | Run once a year, midnight, Jan. 1st | 0 0 0 1 1 * +@monthly | Run once a month, midnight, first of month | 0 0 0 1 * * +@weekly | Run once a week, midnight between Sat/Sun | 0 0 0 * * 0 +@daily (or @midnight) | Run once a day, midnight | 0 0 0 * * * +@hourly | Run once an hour, beginning of hour | 0 0 * * * * +``` + +#### 间隔 + +您还可以定义任务以固定的时间间隔执行,从添加时开始运行。这可以通过格式化 `cron` 规范来支持,如下所示: + +``` +@every +``` + +其中 `duration` 是 `time.ParseDuration` 接受的字符串 ( [http://golang.org/pkg/time/#ParseDuration](http://golang.org/pkg/time/#ParseDuration))。 + +例如, `@every 1h30m10s` 将表示添加任务之后每隔 `1小时30分10秒` 执行。 +:::warning +间隔不会考虑任务的执行开销时间。例如,如果一项工作需要 `3` 分钟才能执行完成,并且计划每隔 `5` 分钟运行一次,那么每次任务之间只有 `2` 分钟的空闲时间。 +::: +## 表达式示例 + +| 表达式示例 | 表达式说明 | +| --- | --- | +| `* * * * * *` | 每秒执行 | +| `# * * * * *` | 每分钟执行,每一次执行至少间隔 `60` 秒 | +| `2 * * * * *` | 每分钟的第 `2` 秒执行 | +| `*/5 * * * * *` | 每 `5` 秒执行一次 | +| `# */30 * * * *` | 每 `30` 分钟执行一次 | +| `# 0 2 * * *` | 每天凌晨 `2` 点执行 | +| `# */30 9-18 * * *` | 每天 `9` 点到 `18` 点,每隔 `30` 分钟执行一次 | +| `# 0 9 * * MON,FRI` | 每 `周一` 和 `周五` 在 `9` 点执行一次 | + +## 注意事项🔥 + +所有开发语言级别 `6` 段式的 `cron pattern` 设计从实践上来说,由于底层定时器的不准确性,因此都是有一定的设计缺陷。由于 `cron pattern` 精确到秒级,当延 **迟达到了秒级**,那么任务可能会存在丢失情况。而当 `golang` 引擎调度比较慢的时候,那么延迟很容易达到秒级引发程序逻辑问题。 + +考虑大部分的场景其实并不需要如此精准的定时任务粒度控制,因此从框架 `v2.7` 版本开始,我们对秒字段提供了忽略符号 `#`,用于将 `6` 段式的 `cron pattern` 转换为 `5` 段式的 `linux crontab pattern`,更加稳健。如果是秒级粒度的定时任务场景,请考虑使用 `gtimer` 定时器,但同时也需要注意,任何的定时器都是不准确的,不能完全依赖底层的系统时间。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" new file mode 100644 index 00000000000..991ad6434b7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-gtimer.md" @@ -0,0 +1,71 @@ +--- +slug: '/docs/components/os-gtimer' +title: '定时器-gtimer' +sidebar_position: 0 +hide_title: true +keywords: [定时器,并发安全,高性能,GoFrame,任务调度,延迟任务,超时控制,频率控制,任务管理,单例模式] +description: 'gtimer是一个并发安全的高性能定时器,适用于大批量定时任务和延迟任务场景,支持超时控制和频率控制。gtimer提供多种任务管理方法,包括添加单例和运行一次的任务,并可自定义定时器参数。' +--- + +## 基本介绍 + +`gtimer` 是一个并发安全的高性能定时器,类似于 `Java` 的 `Timer`。 `gtimer` 底层采用了 **优先级队列**( `PriorityQueue`)实现。 + +**使用场景**: + +任何定时任务场景,大批量定时任务/延迟任务的场景,超时控制/频率控制的业务场景,对于定时时间准确度要求不高的业务场景。 + +**使用方式:** + +```go +import "github.com/gogf/gf/v2/os/gtimer" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtimer](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtimer) + +**简要说明:** + +1. `New` 方法用于创建自定义的任务定时器对象,并可在创建时`TimerOptions`传入参数,其中: + - `Interval` 指定定时器的最小 `tick` 时间间隔。 + - `Quick` 指定定时器是否启动时就执行一次(默认为 `false`)。 +2. `Add` 方法用于添加定时任务,其中: + - `interval` 参数用于指定方法的执行的时间间隔。 + - `job` 参数为需要执行的任务方法。 +3. `AddEntry` 方法添加定时任务,支持更多参数的控制。 +4. `AddSingleton` 方法用于添加 **单例** 定时任务,即 **同时只能有一个该任务正在运行**。 +5. `AddOnce` 方法用于添加只运行一次的定时任务,当运行一次数后该定时任务自动销毁。 +6. `AddTimes` 方法用于添加运行指定次数的定时任务,当运行 `times` 次数后该定时任务自动销毁。 +7. `Search` 方法用于根据名称进行定时任务搜索(返回定时任务 `*Entry` 对象指针)。 +8. `Start` 方法用于启动定时器(使用 `New` 创建定时器时会自动启动)。 +9. `Stop` 方法用于停止定时器。 +10. `Close` 方法用于关闭定时器。 + +## 默认定时器 + +大部分的场景下使用默认的定时器即可。使用 `gtimer` 的默认定时器时,默认的定时检测间隔时间为 `100ms`,因此理论的时间间隔误差范围为 `0~100ms`。可以使用以下两种方式修改默认的定时器参数: + +1. 使用启动参数 + - `gf.gtimer.interval=50`: 修改默认的时间刻度为 `50毫秒` +2. 使用环境变量 + - `GF_GTIMER_INTERVAL=50` +:::warning +需要注意,定时器默认检测间隔时间越短,对 `CPU` 的使用量会越大。 +::: +## 注意事项🔥 + +1. 由于现代的计算机都是使用软件实现的定时器, **任何的定时器都是存在误差的**,不会完全精准,可能延迟,也甚至会提前,但是不会不执行。并且在定时间隔比较大,或者并发量大,负载较高的系统中尤其明显。参考链接: [https://github.com/golang/go/issues/14410](https://github.com/golang/go/issues/14410) +2. 由于误差是不可避免的,任何定时器的实现(不仅是框架的定时器,标准库的定时器亦是如此) **都不会使用系统时间**,而是使用一种固定的 `tick` 间隔。不要在定时器任务逻辑中使用系统时间来判断间隔做逻辑运算,这个时间的判断没有任何意义。 +3. 在不考虑误差的前提下,定时间隔不会考虑任务的执行时间。例如,如果一项工作需要 `3` 分钟才能执行完成,并且计划每隔 `5` 分钟运行一次,那么每次任务之间只有 `2` 分钟的空闲时间。 +4. 需要注意的是 **单例模式** 运行的定时任务,任务的执行时间会影响该任务下一次执行的 **开始时间**。例如:一个每间隔 `1` 秒执行的任务,运行耗时为 `1` 秒,那么在 **第1秒** 开始运行后,下一次任务将会在 **第3秒** 开始执行。因为中间有一次运行检查时发现有当前任务正在进行,因此退出等待下一次执行检查。 + +## 定时器与 `gcron` 区别 + +具体请查看章节 [定时任务-gcron与gtimer](../定时任务-gcron/定时任务-gcron与gtimer.md) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..f20d2d0bf09 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,222 @@ +--- +slug: '/docs/components/os-gtimer-example' +title: '定时器-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [定时器,基本使用,单例任务,延迟任务,SetTimeout,SetInterval,任务退出,GoFrame,GoFrame框架,gtimer] +description: '在GoFrame框架中使用定时器组件,包括基本使用、单例任务、延迟任务以及通过SetTimeout和SetInterval方法进行定时操作。详细讲解了这些定时任务的实现方式和执行结果,并展示了如何使用Exit方法退出定时任务。' +--- + +## 基本示例 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + now = time.Now() + ) + gtimer.AddTimes(ctx, time.Second, 10, func(ctx context.Context) { + fmt.Println(gtime.Now(), time.Duration(time.Now().UnixNano()-now.UnixNano())) + now = time.Now() + }) + + select {} +} + +``` + +执行后,输出结果为: + +```html +2021-05-27 13:28:19 1.004516s +2021-05-27 13:28:20 997.262ms +2021-05-27 13:28:21 999.972ms +2021-05-27 13:28:22 1.00112s +2021-05-27 13:28:23 998.773ms +2021-05-27 13:28:24 999.957ms +2021-05-27 13:28:25 1.002468s +2021-05-27 13:28:26 997.468ms +2021-05-27 13:28:27 999.981ms +2021-05-27 13:28:28 1.002504s +``` + +## 单例任务 + +```go +package main + +import ( + "context" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + interval = time.Second + ) + + gtimer.AddSingleton(ctx, interval, func(ctx context.Context) { + glog.Print(ctx, "doing") + time.Sleep(5 * time.Second) + }) + + select {} +} +``` + +执行后,输出结果为: + +```html +2021-11-14 11:50:42.192 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:50:48.190 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:50:54.192 {189cwi9mo40cfp73guzhugo100tnuedg} doing +2021-11-14 11:51:00.189 {189cwi9mo40cfp73guzhugo100tnuedg} doing +... +``` + +## 延迟任务 + +延迟任务是指在指定时间后生效的定时任务。我们可以通过 `DelayAdd*` 相关方法实现延迟任务的创建。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + delay = time.Second + interval = time.Second + ) + fmt.Println("Start:", gtime.Now()) + gtimer.DelayAdd( + ctx, + delay, + interval, + func(ctx context.Context) { + fmt.Println("Running:", gtime.Now()) + }, + ) + select {} +} +``` + +执行后,终端输出: + +``` +Start: 2021-05-27 13:26:02 +Running: 2021-05-27 13:26:04 +Running: 2021-05-27 13:26:05 +Running: 2021-05-27 13:26:06 +Running: 2021-05-27 13:26:07 +Running: 2021-05-27 13:26:08 +Running: 2021-05-27 13:26:09 +Running: 2021-05-27 13:26:10 +Running: 2021-05-27 13:26:11 +... +``` + +## `SetTimeout` 与 `SetInterval` + +这两个方法来源于 `Javascript` 常用定时方法。其中 `SetTimeout` 用于创建只执行一次的定时任务,不过可以通过递归调用 `SetTimeout` 来实现无限间隔执行。 `SetIterval` 用于创建间隔执行不退出的定时任务。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + timeout = time.Second + interval = time.Second + ) + gtimer.SetTimeout(ctx, timeout, func(ctx context.Context) { + fmt.Println("SetTimeout:", gtime.Now()) + }) + gtimer.SetInterval(ctx, interval, func(ctx context.Context) { + fmt.Println("SetInterval:", gtime.Now()) + }) + select {} +} +``` + +执行后,终端输出: + +``` +SetInterval: 2021-05-27 13:20:50 +SetTimeout: 2021-05-27 13:20:50 +SetInterval: 2021-05-27 13:20:51 +SetInterval: 2021-05-27 13:20:52 +SetInterval: 2021-05-27 13:20:53 +SetInterval: 2021-05-27 13:20:54 +SetInterval: 2021-05-27 13:20:55 +SetInterval: 2021-05-27 13:20:56 +SetInterval: 2021-05-27 13:20:57 +SetInterval: 2021-05-27 13:20:58 +... +``` + +## `Exit` 退出 + +我们可以在定时任务中通过 `Exit` 方法强制退出定时任务的继续执行,该定时任务将会被从定时器中移除。 + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "time" +) + +func main() { + var ( + ctx = gctx.New() + ) + gtimer.SetInterval(ctx, time.Second, func(ctx context.Context) { + fmt.Println("exit:", gtime.Now()) + gtimer.Exit() + }) + select {} +} +``` + +执行后,终端输出: + +``` +exit: 2021-05-27 13:31:24 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..3d083e35a79 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\256\232\346\227\266\345\231\250-gtimer/\345\256\232\346\227\266\345\231\250-\346\200\247\350\203\275\346\265\213\350\257\225.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/os-gtimer-benchmark' +title: '定时器-性能测试' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,定时器,性能测试,Benchmark,Go,gtimer,linux,amd64,github] +description: '使用GoFrame框架在Linux环境下进行定时器性能测试的详细信息。通过对Benchmark_Add和Benchmark_StartStop的对比,能够更清晰地了解到定时器在不同操作下的效率和资源分配情况。测试结果显示出Go语言在执行定时器操作时的高效性能指标,从而为开发者在使用GoFrame中提供更好的参考。' +--- + +## 性能测试 + +``` +goos: linux +goarch: amd64 +pkg: github.com/gogf/gf/v2/os/gtimer +Benchmark_Add-12 4048776 291.9 ns/op 249 B/op 6 allocs/op +Benchmark_StartStop-12 100000000 10.96 ns/op 0 B/op 0 allocs/op +PASS +ok command-line-arguments 6.602s +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" new file mode 100644 index 00000000000..8ee26b626ca --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\345\257\271\350\261\241\344\277\241\346\201\257-gstructs.md" @@ -0,0 +1,140 @@ +--- +slug: '/docs/components/os-gstructs' +title: '对象信息-gstructs' +sidebar_position: 18 +hide_title: true +keywords: [GoFrame,GoFrame框架,gstructs,结构体信息,字段检索,TagMapName,Fields方法,GoFrame框架,中间件编写,底层组件] +description: 'gstructs组件是GoFrame框架中用于获取结构体信息的底层工具。主要用于框架、基础库和中间件编写,支持Fields方法获取结构体字段信息以及TagMapName方法通过标签检索字段。适合开发者在构建Go应用时利用此组件进行字段操作和检索。' +--- + +## 基本介绍 + +`gstructs` 组件用于方便获取结构体的相关信息。 + +这是一个偏底层组件,一般业务上很少会用到,在框架、基础库、中间件编写中用到。 + +使用方式: + +```go +import "github.com/gogf/gf/v2/os/gstructs" +``` + +接口文档: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gstructs](https://pkg.go.dev/github.com/gogf/gf/v2/os/gstructs) + +## 常用方法 + +### `Fields` + +- 说明:`Fields ` 将输入参数 `in` 的 `Pointer ` 属性的字段以 `Field` 切片的形式返回。 + +- 格式: + +```go +Fields(in FieldsInput) ([]Field, error) +``` + +- 示例: + +```go +func main() { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user *User + fields, _ := gstructs.Fields(gstructs.FieldsInput{ + Pointer: user, + RecursiveOption: 0, + }) + + g.Dump(fields) +} + +// Output: +[ + { + Value: "", + Field: { + Name: "Id", + PkgPath: "", + Type: "int", + Tag: "", + Offset: 0, + Index: [ + 0, + ], + Anonymous: false, + }, + TagValue: "", + }, + { + Value: {}, + Field: { + Name: "Name", + PkgPath: "", + Type: "string", + Tag: "params:\"name\"", + Offset: 8, + Index: [ + 1, + ], + Anonymous: false, + }, + TagValue: "", + }, + { + Value: {}, + Field: { + Name: "Pass", + PkgPath: "", + Type: "string", + Tag: "my-tag1:\"pass1\" my-tag2:\"pass2\" params:\"pass\"", + Offset: 24, + Index: [ + 2, + ], + Anonymous: false, + }, + TagValue: "", + }, +] +``` + + +### `TagMapName` + +- 说明:`TagMapName` 从参数 `pointer` 中检索 `tag`,并以 `map[string]string` 的形式返回。 + +- 注意: + - 参数 `pointer` 的类型应该是 `struct/*struct`。 + - 只会返回可导出的字段(首字母大写的字段)。 +- 格式: + +```go +TagMapName(pointer interface{}, priority []string) (map[string]string, error) +``` + +- 示例: + +```go +func main() { + type User struct { + Id int + Name string `params:"name"` + Pass string `my-tag1:"pass1" my-tag2:"pass2" params:"pass"` + } + var user User + m, _ := gstructs.TagMapName(user, []string{"params"}) + + g.Dump(m) +} + +// Output: +{ + "name": "Name", + "pass": "Pass", +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" new file mode 100644 index 00000000000..11154d40763 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify.md" @@ -0,0 +1,34 @@ +--- +slug: '/docs/components/os-gfsnotify' +title: '文件监控-gfsnotify' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame,GoFrame框架,gfsnotify,文件监控,Go框架,文件操作,监控模块,Go开发,系统监控,目录监控] +description: '使用GoFrame框架中gfsnotify模块来实现文件和目录的监控。gfsnotify能够检测文件的增加、删除、修改、重命名等变动操作,并提供方便的接口函数如Add和Remove进行监控和取消监控操作。适用于*nix系统的inotify机制,在使用时会受到系统内核参数的限制。通过实例代码展示如何设置、移除监控及进行文件操作监控。' +--- + + +## 基本介绍 + +`gfsnotify` 能监控指定文件/目录的改变,如文件的增加、删除、修改、重命名等操作。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gfsnotify" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gfsnotify](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfsnotify) + +推荐使用 `gfsnotify` 模块提供的 `Add` 和 `Remove` 模块方法,用于添加监控和取消监控。推荐原因见随后章节说明。 + +此外也可能通过 `New` 方法创建一个监控管理对象之后再进行监控管理。其中,添加监控的时候需要给定触发监控时的回调函数,参数类型为 `*gfsnotify.Event` 对象指针。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" new file mode 100644 index 00000000000..d9c27f6eed8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\346\267\273\345\212\240\347\233\221\346\216\247.md" @@ -0,0 +1,66 @@ +--- +slug: '/docs/components/os-gfsnotify-add' +title: '文件监控-添加监控' +sidebar_position: 0 +hide_title: true +keywords: [添加监控,文件监控,GoFrame框架,gfsnotify,递归监控,文件修改,目录监控,文件事件,文件变化,监控选项] +description: '定义与实现文件监控功能,利用GoFrame框架中的gfsnotify库实现对指定目录下文件的创建、写入、删除、重命名和权限修改等事件进行监控。支持递归监控,自动检测目录及子目录中文件的变化,并灵活设置监控选项,实时输出与目录相关的文件事件信息。' +--- + + +## 添加监控 + +```go +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" +) + +func main() { + var ( + path = "/home/john/temp" + ctx = context.Background() + logger = g.Log() + callback = func(event *gfsnotify.Event) { + if event.IsCreate() { + logger.Debug(ctx, "创建文件 : ", event.Path) + } + if event.IsWrite() { + logger.Debug(ctx, "写入文件 : ", event.Path) + } + if event.IsRemove() { + logger.Debug(ctx, "删除文件 : ", event.Path) + } + if event.IsRename() { + logger.Debug(ctx, "重命名文件 : ", event.Path) + } + if event.IsChmod() { + logger.Debug(ctx, "修改权限 : ", event.Path) + } + logger.Debug(ctx, event) + } + ) + _, err := gfsnotify.Add(path, callback, gfsnotify.WatchOption{}) + if err != nil { + logger.Fatal(ctx, err) + } else { + select {} + } +} +``` + +其中 `/home/john` 参数为一个目录,当我们在 `/home/john` 目录下创建/删除/修改文件时,可以看到 `gfsnotify` 监控到了文件的修改并输出了对应的事件信息。 + +## 递归监控 + +我们可以通过`gfsnotify.WatchOption`来设置监控的一些选项,例如是否递归监控。在默认情况下,`Add`方法会执行递归监控,也就是说当目录下的文件(包括子目录下的文件)发生变化时,也会收到文件监控信息回调。 + +如果在监控目录下创建新的目录,并且新的目录中继续创建新的目录或文件,以此类推,新创建的目录或文件也将自动被监控。 + + + + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" new file mode 100644 index 00000000000..d5e01f37227 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\247\273\351\231\244\347\233\221\346\216\247.md" @@ -0,0 +1,97 @@ +--- +slug: '/docs/components/os-gfsnotify-remove' +title: '文件监控-移除监控' +sidebar_position: 1 +hide_title: true +keywords: [文件监控,移除监控,GoFrame,Remove方法,RemoveCallback,文件回调,目录监控,gfsnotify,回调移除,GoFrame框架] +description: '本文档详细介绍如何在GoFrame框架中使用Remove方法和RemoveCallback方法来移除对文件和目录的监控回调功能,通过示例代码说明了如何添加和移除监控回调,从而提高系统资源的利用效率,确保文件操作监控的灵活性和可控性。' +--- + +移除监控我们可以使用 `Remove` 方法,会移除对整个文件/目录的监控。 + +当对同一个文件/目录存在多个监控回调时,我们可以通过 `RemoveCallback` 方法移除指定的回调。方法参数 `callbackId` 是在添加监控时返回的 `Callback` 对象的唯一ID。 + +## 使用示例1 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + ) + c1, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback1") + }) + if err != nil { + panic(err) + } + c2, err := gfsnotify.Add("/home/john/temp/log", func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback2") + }) + if err != nil { + panic(err) + } + // 5秒后移除c1的回调函数注册,仅剩c2 + gtimer.SetTimeout(ctx, 5*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(c1.Id) + logger.Debug(ctx, "remove callback c1", err) + }) + // 10秒后移除c2的回调函数注册,所有的回调都移除,不再有任何打印信息输出 + gtimer.SetTimeout(ctx, 10*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(c2.Id) + logger.Debug(ctx, "remove callback c2", err) + }) + + select {} +} +``` + +## 使用示例2 + +```go +package main + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gfsnotify" + "github.com/gogf/gf/v2/os/gtimer" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + callback = func(event *gfsnotify.Event) { + logger.Debug(ctx, "callback") + } + ) + cb, err := gfsnotify.Add("/home/john/temp", callback) + if err != nil { + panic(err) + } + + // 在此期间创建文件、目录、修改文件、删除文件 + + // 20秒后移除回调函数注册,所有的回调都移除,不再有任何打印信息输出 + gtimer.SetTimeout(ctx, 20*time.Second, func(ctx context.Context) { + err = gfsnotify.RemoveCallback(cb.Id) + logger.Debug(ctx, "remove callback", err) + }) + + select {} +} +``` diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..768cc1e240c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\233\221\346\216\247-gfsnotify/\346\226\207\344\273\266\347\233\221\346\216\247-\347\263\273\347\273\237\345\217\202\346\225\260.md" @@ -0,0 +1,39 @@ +--- +slug: '/docs/components/os-gfsnotify-system-variables' +title: '文件监控-系统参数' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gfsnotify,文件监控,inotify,系统参数,linux,监控实例,用户实例限制,文件队列大小] +description: '在Linux系统下gfsnotify模块通过inotify特性实现文件和目录监控,受限于系统内核参数如fs.inotify.max_user_instances和fs.inotify.max_user_watches,通过命令行可以查看和修改这些参数以适应不同的监控需求。' +--- + +## 系统参数影响 + +在 `linux` 系统下, `gfsnotify` 模块使用的是系统的 `inotify` 特性来实现的文件/目录监控,因此该功能在使用时会受到系统的两个内核参数限制: + +- `fs.inotify.max_user_instances`:表示当前用户可创建的 `inotify` 监控实例数量,即 `gfsnotify.New` 方法创建的 `Watcher` 对象数量,一个 `Watcher` 对象对应系统的一个 `inotify` 实例,系统默认数量为: `128`。 + +- `fs.inotify.max_user_watches`:表示一个 `inotify` 实例可添加的监控文件队列大小,往同一个 `inotify` 添加的监控文件超过该数量限制则会失败,并且会有系统错误日志,系统默认数量往往为: `8192`(有的系统该数值会比较大一些)。 + + +## 查看与修改 + +以`fs.inotify.max_user_instances`为例,在`linux`系统下,可以通过以下命令查看`fs.inotify.max_user_instances`的当前值: +```bash +cat /proc/sys/fs/inotify/max_user_instances +``` + +如果需要修改该值,可以使用以下命令(例如将值修改为 `1024`): +```bash +sudo sysctl -w fs.inotify.max_user_instances=1024 +``` + +要永久修改该值,可以将以下内容添加到`/etc/sysctl.conf`文件中: +```bash +fs.inotify.max_user_instances=1024 +``` +然后执行以下命令使更改生效: +```bash +sudo sysctl -p +``` + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" new file mode 100644 index 00000000000..8608060d18d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\226\207\344\273\266\347\256\241\347\220\206-gfile.md" @@ -0,0 +1,2501 @@ +--- +slug: '/docs/components/os-gfile' +title: '文件管理-gfile' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,gfile,文件管理,缓存机制,文件操作,目录扫描,文件复制,权限设置,路径操作,内容替换] +description: 'gfile组件为GoFrame框架提供丰富的文件和目录操作功能,包括文件内容读取、缓存机制、文件复制与移动、目录扫描及文件权限设置等。支持灵活的路径操作与内容替换,优化文件管理与处理效率,是开发者进行文件操作的优选库。' +--- + +## 基本介绍 + +`gfile` 文件管理组件提供了更加丰富的文件/目录操作能力。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gfile" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile) +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile](https://pkg.go.dev/github.com/gogf/gf/v2/os/gfile) +::: +## 内容管理 + +### `GetContents` + +- 说明:读取指定路径文件内容,以字符串形式返回。 +- 格式: + +```go +func GetContents(path string) string +``` + +- 示例: + +```go +func ExampleGetContents() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads and returns the file content as string. + // It returns empty string if it fails reading, for example, with permission or IO error. + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `GetContentsWithCache` + +- 说明:带缓存获取文件内容,可设置缓存超时,文件发生变化自动清除缓存。 +- 格式: + +```go +func GetContentsWithCache(path string, duration ...time.Duration) string +``` + +- 示例: + +```go +func ExampleGetContentsWithCache() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_cache") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads the file content with cache duration of one minute, + // which means it reads from cache after then without any IO operations within on minute. + fmt.Println(gfile.GetContentsWithCache(tempFile, time.Minute)) + + // write new contents will clear its cache + gfile.PutContents(tempFile, "new goframe example content") + + // There's some delay for cache clearing after file content change. + time.Sleep(time.Second * 1) + + // read contents + fmt.Println(gfile.GetContentsWithCache(tempFile)) + + // May Output: + // goframe example content + // new goframe example content +} +``` + + +### `GetBytesWithCache` + +- 说明:带缓存获取文件内容,可设置缓存超时,文件发生变化自动清除缓存,返回\[\]byte。 +- 格式: + +```go +func GetBytesWithCache(path string, duration ...time.Duration) []byte +``` + +- 示例: + +```go +func ExampleGetBytesWithCache() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_cache") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads the file content with cache duration of one minute, + // which means it reads from cache after then without any IO operations within on minute. + fmt.Println(gfile.GetBytesWithCache(tempFile, time.Minute)) + + // write new contents will clear its cache + gfile.PutContents(tempFile, "new goframe example content") + + // There's some delay for cache clearing after file content change. + time.Sleep(time.Second * 1) + + // read contents + fmt.Println(gfile.GetBytesWithCache(tempFile)) + + // Output: + // [103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] + // [110 101 119 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `GetBytes` + +- 说明:读取指定路径文件内容,以字节形式返回。 +- 格式: + +```go +func GetBytes(path string) []byte +``` + +- 示例: + +```go +func ExampleGetBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // It reads and returns the file content as []byte. + // It returns nil if it fails reading, for example, with permission or IO error. + fmt.Println(gfile.GetBytes(tempFile)) + + // Output: + // [103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `GetBytesTilChar` + +- 说明:以某个字符定位截取指定长度的文件内容以字节形式返回 +- 格式: + +```go +func GetBytesTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64) +``` + +- 示例: + +```go +func ExampleGetBytesTilChar() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, _ := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, gfile.DefaultPermOpen) + + // GetBytesTilChar returns the contents of the file as []byte + // until the next specified byte `char` position. + char, i := gfile.GetBytesTilChar(f, 'f', 0) + fmt.Println(char) + fmt.Println(i) + + // Output: + // [103 111 102] + // 2 +} +``` + + +### `GetBytesByTwoOffsets` + +- 说明:以指定的区间读取文件内容 +- 格式: + +```go +func GetBytesByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte +``` + +- 示例: + +```go +func ExampleGetBytesByTwoOffsets() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, _ := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, gfile.DefaultPermOpen) + + // GetBytesTilChar returns the contents of the file as []byte + // until the next specified byte `char` position. + char := gfile.GetBytesByTwoOffsets(f, 0, 3) + fmt.Println(char) + + // Output: + // [103 111 102] +} +``` + + +### `PutContents` + +- 说明:往指定路径文件添加字符串内容。如果文件不存在将会递归的形式自动创建。 +- 格式: + +```go +func putContents(path string, data []byte, flag int, perm os.FileMode) error +``` + +- 示例: + +```go +func ExamplePutContents() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // It creates and puts content string into specifies file path. + // It automatically creates directory recursively if it does not exist. + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `PutBytes` + +- 说明:以字节形式写入指定文件,如果文件不存在将会递归的形式自动创建 +- 格式: + +```go +func PutBytes(path string, content []byte) error +``` + +- 示例: + +```go +func ExamplePutBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutBytes(tempFile, []byte("goframe example content")) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content +} +``` + + +### `PutContentsAppend` + +- 说明:追加字符串内容到指定文件,如果文件不存在将会递归的形式自动创建。 +- 格式: + +```go +func PutContentsAppend(path string, content string) error +``` + +- 示例: + +```go +func ExamplePutContentsAppend() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It creates and append content string into specifies file path. + // It automatically creates directory recursively if it does not exist. + gfile.PutContentsAppend(tempFile, " append content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example content append content +} +``` + + +### `PutBytesAppend` + +- 说明:追加字节内容到指定文件。如果文件不存在将会递归的形式自动创建。 +- 格式: + +```go +func PutBytesAppend(path string, content []byte) error +``` + +- 示例: + +```go +func ExamplePutBytesAppend() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // write contents + gfile.PutBytesAppend(tempFile, []byte(" append")) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example content append +} +``` + + +### `GetNextCharOffset` + +- 说明:从某个偏移量开始,获取文件中指定字符所在下标 +- 格式: + +```go +func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 +``` + +- 示例: + +```go +func ExampleGetNextCharOffset() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + f, err := gfile.OpenWithFlagPerm(tempFile, os.O_RDONLY, DefaultPermOpen) + defer f.Close() + + // read contents + index := gfile.GetNextCharOffset(f, 'f', 0) + fmt.Println(index) + + // Output: + // 2 +} +``` + + +### `GetNextCharOffsetByPath` + +- 说明:从某个偏移量开始,获取文件中指定字符所在下标 +- 格式: + +```go +func GetNextCharOffsetByPath(path string, char byte, start int64) int64 +``` + +- 示例: + +```go +func ExampleGetNextCharOffsetByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + index := gfile.GetNextCharOffsetByPath(tempFile, 'f', 0) + fmt.Println(index) + + // Output: + // 2 +} +``` + + +### `GetBytesTilCharByPath` + +- 说明:以某个字符定位截取指定长度的文件内容以字节形式返回 +- 格式: + +```go +func GetBytesTilCharByPath(path string, char byte, start int64) ([]byte, int64) +``` + +- 示例: + +```go +func ExampleGetBytesTilCharByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetBytesTilCharByPath(tempFile, 'f', 0)) + + // Output: + // [103 111 102] 2 +} +``` + + +### `GetBytesByTwoOffsetsByPath` + +- 说明:用两个偏移量截取指定文件的内容以字节形式返回 +- 格式: + +```go +func GetBytesByTwoOffsetsByPath(path string, start int64, end int64) []byte +``` + +- 示例: + +```go +func ExampleGetBytesByTwoOffsetsByPath() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetBytesByTwoOffsetsByPath(tempFile, 0, 7)) + + // Output: + // [103 111 102 114 97 109 101] +} +``` + + +### `ReadLines` + +- 说明:以字符串形式逐行读取文件内容 +- 格式: + +```go +func ReadLines(file string, callback func(text string) error) error +``` + +- 示例: + +```go +func ExampleReadLines() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content") + + // read contents + gfile.ReadLines(tempFile, func(text string) error { + // Process each line + fmt.Println(text) + return nil + }) + + // Output: + // L1 goframe example content + // L2 goframe example content +} +``` + + +### `ReadLinesBytes` + +- 说明:以字节形式逐行读取文件内容 +- 格式: + +```go +func ReadLinesBytes(file string, callback func(bytes []byte) error) error +``` + +- 示例: + +```go +func ExampleReadLinesBytes() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_content") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "L1 goframe example content\nL2 goframe example content") + + // read contents + gfile.ReadLinesBytes(tempFile, func(bytes []byte) error { + // Process each line + fmt.Println(bytes) + return nil + }) + + // Output: + // [76 49 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] + // [76 50 32 103 111 102 114 97 109 101 32 101 120 97 109 112 108 101 32 99 111 110 116 101 110 116] +} +``` + + +### `Truncate` + +- 说明:裁剪文件为指定大小 +- 注意:如果给定文件路径是软链,将会修改源文件 +- 格式: + +```go +func Truncate(path string, size int) error +``` + +- 示例: + +```go +func ExampleTruncate(){ + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Check whether the `path` size + stat, _ := gfile.Stat(path) + fmt.Println(stat.Size()) + + // Truncate file + gfile.Truncate(path, 0) + + // Check whether the `path` size + stat, _ = gfile.Stat(path) + fmt.Println(stat.Size()) + + // Output: + // 13 + // 0 +} +``` + + +## 内容替换 + +### `ReplaceFile` + +- 说明:替换指定文件的指定内容为新内容 +- 格式: + +```go +func ReplaceFile(search, replace, path string) error +``` + +- 示例: + +```go +func ExampleReplaceFile() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content directly by file path. + gfile.ReplaceFile("content", "replace word", tempFile) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example replace word +} +``` + + +### `ReplaceFileFunc` + +- 说明:使用自定义函数替换指定文件内容 +- 格式: + +```go +func ReplaceFileFunc(f func(path, content string) string, path string) error +``` + +- 示例: + +```go +func ExampleReplaceFileFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example 123") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content directly by file path and callback function. + gfile.ReplaceFileFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempFile) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example 123 + // goframe example [num] +} +``` + + +### `ReplaceDir` + +- 说明:扫描指定目录,替换符合条件的文件的指定内容为新内容 +- 格式: + +```go +func ReplaceDir(search, replace, path, pattern string, recursive ...bool) error +``` + +- 示例: + +```go +func ExampleReplaceDir() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content of all files under specified directory recursively. + gfile.ReplaceDir("content", "replace word", tempDir, "gfile_example.txt", true) + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example content + // goframe example replace word +} +``` + + +### `ReplaceDirFunc` + +- 说明:扫描指定目录,使用自定义函数替换符合条件的文件的指定内容为新内容 +- 格式: + +```go +func ReplaceDirFunc(f func(path, content string) string, path, pattern string, recursive ...bool) error +``` + +- 示例: + +```go +func ExampleReplaceDirFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_replace") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example 123") + + // read contents + fmt.Println(gfile.GetContents(tempFile)) + + // It replaces content of all files under specified directory with custom callback function recursively. + gfile.ReplaceDirFunc(func(path, content string) string { + // Replace with regular match + reg, _ := regexp.Compile(`\d{3}`) + return reg.ReplaceAllString(content, "[num]") + }, tempDir, "gfile_example.txt", true) + + fmt.Println(gfile.GetContents(tempFile)) + + // Output: + // goframe example 123 + // goframe example [num] + +} +``` + + +## 文件时间 + +### `MTime` + +- 说明:获取路径修改时间 +- 格式: + +```go +func MTime(path string) time.Time +``` + +- 示例: + +```go +func ExampleMTime() { + t := gfile.MTime(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 2021-11-02 15:18:43.901141 +0800 CST +} +``` + + +### `MTimestamp` + +- 说明:获取路径修改时间戳(秒) +- 格式: + +```go +func MTimestamp(path string) int64 +``` + +- 示例: + +```go +func ExampleMTimestamp() { + t := gfile.MTimestamp(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 1635838398 +} +``` + + +### `MTimestampMilli` + +- 说明:获取路径修改时间戳(毫秒) +- 格式: + +```go +func MTimestampMilli(path string) int64 +``` + +- 示例: + +```go +func ExampleMTimestampMilli() { + t := gfile.MTimestampMilli(gfile.TempDir()) + fmt.Println(t) + + // May Output: + // 1635838529330 +} +``` + + +## 文件大小 + +### `Size` + +- 说明:获取路径大小,不进行格式化 +- 格式: + +```go +func Size(path string) int64 +``` + +- 示例: + +```go +func ExampleSize() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "0123456789") + fmt.Println(gfile.Size(tempFile)) + + // Output: + // 10 +} +``` + + +### `SizeFormat` + +- 说明:获取路径大小,并格式化成硬盘容量 +- 格式: + +```go +func SizeFormat(path string) string +``` + +- 示例: + +```go +func ExampleSizeFormat() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "0123456789") + fmt.Println(gfile.SizeFormat(tempFile)) + + // Output: + // 10.00B +} +``` + + +### `ReadableSize` + +- 说明:获取给定路径容量大小,并格式化人类易读的硬盘容量格式 +- 格式: + +```go +func ReadableSize(path string) string +``` + +- 示例: + +```go +func ExampleReadableSize() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_size") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "01234567899876543210") + fmt.Println(gfile.ReadableSize(tempFile)) + + // Output: + // 20.00B +} +``` + + +### `StrToSize` + +- 说明:硬盘容量大小字符串转换为大小整形 +- 格式: + +```go +func StrToSize(sizeStr string) int64 +``` + +- 示例: + +```go +func ExampleStrToSize() { + size := gfile.StrToSize("100MB") + fmt.Println(size) + + // Output: + // 104857600 +} +``` + + +### `FormatSize` + +- 说明:大小整形转换为硬盘容量大小字符串\`K、m、g、t、p、e、b\` +- 格式: + +```go +func FormatSize(raw int64) string +``` + +- 示例: + +```go +func ExampleFormatSize() { + sizeStr := gfile.FormatSize(104857600) + fmt.Println(sizeStr) + sizeStr0 := gfile.FormatSize(1024) + fmt.Println(sizeStr0) + sizeStr1 := gfile.FormatSize(999999999999999999) + fmt.Println(sizeStr1) + + // Output: + // 100.00M + // 1.00K + // 888.18P +} +``` + + +## 文件排序 + +### `SortFiles` + +- 说明:排序多个路径,按首字母进行排序,数字优先。 +- 格式: + +```go +func SortFiles(files []string) []string +``` + +- 示例: + +```go +func ExampleSortFiles() { + files := []string{ + "/aaa/bbb/ccc.txt", + "/aaa/bbb/", + "/aaa/", + "/aaa", + "/aaa/ccc/ddd.txt", + "/bbb", + "/0123", + "/ddd", + "/ccc", + } + sortOut := gfile.SortFiles(files) + fmt.Println(sortOut) + + // Output: + // [/0123 /aaa /aaa/ /aaa/bbb/ /aaa/bbb/ccc.txt /aaa/ccc/ddd.txt /bbb /ccc /ddd] +} +``` + + +## 文件检索 + +### `Search` + +- 说明:在指定目录(默认包含当前目录、运行目录、主函数目录;不会递归子目录)中搜索文件并返回真实路径。 +- 格式: + +```go +func Search(name string, prioritySearchPaths ...string) (realPath string, err error) +``` + +- 示例: + +```go +func ExampleSearch() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_search") + tempFile = gfile.Join(tempDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + + // search file + realPath, _ := gfile.Search(fileName, tempDir) + fmt.Println(gfile.Basename(realPath)) + + // Output: + // gfile_example.txt +} +``` + + +## 目录扫描 + +### `ScanDir` + +- 说明:扫描指定目录,可扫描文件或目录,支持递归扫描。 +- 格式: + +```go +func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) +``` + +- 示例: + +```go +func ExampleScanDir() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively + list, _ := gfile.ScanDir(tempDir, "*", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // sub_dir + // gfile_example.txt +} +``` + + +### `ScanDirFile` + +- 说明:扫描指定目录的文件,支持递归扫描 +- 格式: + +```go +func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, error) +``` + +- 示例: + +```go +func ExampleScanDirFile() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_file") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFile(tempDir, "*.txt", true) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example.txt +} +``` + + +### `ScanDirFunc` + +- 说明:扫描指定目录(自定义过滤方法),可扫描文件或目录,支持递归扫描 +- 格式: + +```go +func ScanDirFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) +``` + +- 示例: + +```go +func ExampleScanDirFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_func") + tempFile = gfile.Join(tempDir, fileName) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively + list, _ := gfile.ScanDirFunc(tempDir, "*", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "gfile_example.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // sub_dir +} +``` + + +### `ScanDirFileFunc` + +- 说明:扫描指定目录的文件(自定义过滤方法),支持递归扫描。 +- 格式: + +```go +func ScanDirFileFunc(path string, pattern string, recursive bool, handler func(path string) string) ([]string, error) +``` + +- 示例: + +```go +func ExampleScanDirFileFunc() { + // init + var ( + fileName = "gfile_example.txt" + tempDir = gfile.TempDir("gfile_example_scan_dir_file_func") + tempFile = gfile.Join(tempDir, fileName) + + fileName1 = "gfile_example_ignores.txt" + tempFile1 = gfile.Join(tempDir, fileName1) + + tempSubDir = gfile.Join(tempDir, "sub_dir") + tempSubFile = gfile.Join(tempSubDir, fileName) + ) + + // write contents + gfile.PutContents(tempFile, "goframe example content") + gfile.PutContents(tempFile1, "goframe example content") + gfile.PutContents(tempSubFile, "goframe example content") + + // scans directory recursively exclusive of directories + list, _ := gfile.ScanDirFileFunc(tempDir, "*.txt", true, func(path string) string { + // ignores some files + if gfile.Basename(path) == "gfile_example_ignores.txt" { + return "" + } + return path + }) + for _, v := range list { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example.txt +} +``` + + +## 常用目录 + +### `Pwd` + +- 说明:获取当前工作路径。 +- 格式: + +```go +func Pwd() string +``` + +- 示例: + +```go +func ExamplePwd() { + // Get absolute path of current working directory. + fmt.Println(gfile.Pwd()) + + // May Output: + // xxx/gf/os/gfile +} +``` + + +### `Home` + +- 说明:获取运行用户的主目录 +- 格式: + +```go +func Home(names ...string) (string, error) +``` + +- 示例: + +```go +func ExampleHome() { + // user's home directory + homePath, _ := gfile.Home() + fmt.Println(homePath) + + // May Output: + // C:\Users\hailaz +} +``` + + +### `Temp` + +- 说明:获取拼接系统临时路径后的绝对地址。 + +- 格式: + +```go +func Temp(names ...string) string +``` + +- 示例: + +```go +func ExampleTempDir() { + // init + var ( + fileName = "gfile_example_basic_dir" + ) + + // fetch an absolute representation of path. + path := gfile.Temp(fileName) + + fmt.Println(path) + + // Output: + // /tmp/gfile_example_basic_dir +} +``` + + +### `SelfPath` + +- 说明:获取当前运行程序的绝对路径。 + +- 格式: + +```go +func SelfPath() string +``` + +- 示例: + +```go +func ExampleSelfPath() { + + // Get absolute file path of current running process + fmt.Println(gfile.SelfPath()) + + // May Output: + // xxx/___github_com_gogf_gf_v2_os_gfile__ExampleSelfPath +} +``` + + +## 类型判断 + +### `IsDir` + +- 说明:检查给定的路径是否是文件夹。 +- 格式: + +```go +func IsDir(path string) bool +``` + +- 示例: + +```go +func ExampleIsDir() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + filePath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Checks whether given `path` a directory. + fmt.Println(gfile.IsDir(path)) + fmt.Println(gfile.IsDir(filePath)) + + // Output: + // true + // false +} +``` + + +### `IsFile` + +- 说明:检查给定的路径是否是文件。 +- 格式: + +```go +func IsFile(path string) bool +``` + +- 示例: + +```go +func ExampleIsFile() { + // init + var ( + filePath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dirPath = gfile.TempDir("gfile_example_basic_dir") + ) + // Checks whether given `path` a file, which means it's not a directory. + fmt.Println(gfile.IsFile(filePath)) + fmt.Println(gfile.IsFile(dirPath)) + + // Output: + // true + // false +} +``` + + +## 权限操作 + +### `IsReadable` + +- 说明:检查给定的路径是否可读。 + +- 格式: + +```go +func IsReadable(path string) bool +``` + +- 示例: + +```go +func ExampleIsReadable() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Checks whether given `path` is readable. + fmt.Println(gfile.IsReadable(path)) + + // Output: + // true +} +``` + + +### `IsWritable` + +- 说明:检查指定路径是否可写,如果路径是目录,则会创建临时文件检查是否可写,如果是文件则判断是否可以打开 + +- 格式: + +```go +func IsWritable(path string) bool +``` + +- 示例: + +```go +func ExampleIsWritable() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Checks whether given `path` is writable. + fmt.Println(gfile.IsWritable(path)) + + // Output: + // true +} +``` + + +### `Chmod` + +- 说明:使用指定的权限,更改指定路径的文件权限。 + +- 格式: + +```go +func Chmod(path string, mode os.FileMode) error +``` + +- 示例: + +```go +func ExampleChmod() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get a FileInfo describing the named file. + stat, err := gfile.Stat(path) + if err != nil { + fmt.Println(err.Error()) + } + // Show original mode + fmt.Println(stat.Mode()) + + // Change file model + gfile.Chmod(path, gfile.DefaultPermCopy) + + // Get a FileInfo describing the named file. + stat, _ = gfile.Stat(path) + // Show the modified mode + fmt.Println(stat.Mode()) + + // Output: + // -rw-r--r-- + // -rwxrwxrwx +} +``` + + +## 文件/目录操作 + +### `Mkdir` + +- 说明:创建文件夹,支持递归创建(建议采用绝对路径),创建后的文件夹权限为: `drwxr-xr-x`。 +- 格式: + +```go +func Mkdir(path string) error +``` + +- 示例: + +```go +func ExampleMkdir() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + ) + + // Creates directory + gfile.Mkdir(path) + + // Check if directory exists + fmt.Println(gfile.IsDir(path)) + + // Output: + // true +} +``` + + +### `Create` + +- 说明:创建文件/文件夹,如果传入的路径中的文件夹不存在,则会自动创建文件夹以及文件,其中创建的文件权限为 `-rw-r–r–`。 +- 注意:如果需要创建文件的已存在,则会清空该文件的内容! +- 格式: + +```go +func Create(path string) (*os.File, error) +``` + +- 示例: + +```go +func ExampleCreate() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 50) + ) + // Check whether the file exists + isFile := gfile.IsFile(path) + + fmt.Println(isFile) + + // Creates file with given `path` recursively + fileHandle, _ := gfile.Create(path) + defer fileHandle.Close() + + // Write some content to file + n, _ := fileHandle.WriteString("hello goframe") + + // Check whether the file exists + isFile = gfile.IsFile(path) + + fmt.Println(isFile) + + // Reset file uintptr + unix.Seek(int(fileHandle.Fd()), 0, 0) + // Reads len(b) bytes from the File + fileHandle.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // false + // true + // hello goframe +} +``` + + +### `Open` + +- 说明:以只读的方式打开文件/文件夹。 +- 格式: + +```go +func Open(path string) (*os.File, error) +``` + +- 示例: + +```go +func ExampleOpen() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + // Open file or directory with READONLY model + file, _ := gfile.Open(path) + defer file.Close() + + // Read data + n, _ := file.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // hello goframe +} +``` + + +### `OpenFile` + +- 说明:以指定\`flag\`以及\`perm\`的方式打开文件/文件夹。 +- 格式: + +```go +func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) +``` + +- 示例: + +```go +func ExampleOpenFile() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + // Opens file/directory with custom `flag` and `perm` + // Create if file does not exist,it is created in a readable and writable mode,prem 0777 + openFile, _ := gfile.OpenFile(path, os.O_CREATE|os.O_RDWR, gfile.DefaultPermCopy) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 28 + // hello goframe test open file +} +``` + + +### `OpenWithFalg` + +- 说明:以指定\`flag\`的方式打开文件/文件夹。 +- 格式: + +```go +func OpenWithFlag(path string, flag int) (*os.File, error) +``` + +- 示例: + +```go +func ExampleOpenWithFlag() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + + // Opens file/directory with custom `flag` + // Create if file does not exist,it is created in a readable and writable mode with default `perm` is 0666 + openFile, _ := gfile.OpenWithFlag(path, os.O_CREATE|os.O_RDWR) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file with flag") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 38 + // hello goframe test open file with flag +} +``` + + +### `OpenWithFalgPerm` + +- 说明:以指定\`flag\`以及\`perm\`的方式打开文件/文件夹。 +- 格式: + +```go +func OpenWithFlagPerm(path string, flag int, perm os.FileMode) (*os.File, error) +``` + +- 示例: + +```go +func ExampleOpenWithFlagPerm() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dataByte = make([]byte, 4096) + ) + + // Opens file/directory with custom `flag` and `perm` + // Create if file does not exist,it is created in a readable and writable mode with `perm` is 0777 + openFile, _ := gfile.OpenWithFlagPerm(path, os.O_CREATE|os.O_RDWR, gfile.DefaultPermCopy) + defer openFile.Close() + + // Write some content to file + writeLength, _ := openFile.WriteString("hello goframe test open file with flag and perm") + + fmt.Println(writeLength) + + // Read data + unix.Seek(int(openFile.Fd()), 0, 0) + n, _ := openFile.Read(dataByte) + + fmt.Println(string(dataByte[:n])) + + // Output: + // 38 + // hello goframe test open file with flag +} +``` + + +### `Stat` + +- 说明:获取给定路径的文件详情。 +- 格式: + +```go +func Stat(path string) (os.FileInfo, error) +``` + +- 示例: + +```go +func ExampleStat() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Get a FileInfo describing the named file. + stat, _ := gfile.Stat(path) + + fmt.Println(stat.Name()) + fmt.Println(stat.IsDir()) + fmt.Println(stat.Mode()) + fmt.Println(stat.ModTime()) + fmt.Println(stat.Size()) + fmt.Println(stat.Sys()) + + // May Output: + // file1 + // false + // -rwxr-xr-x + // 2021-12-02 11:01:27.261441694 +0800 CST + // &{16777220 33261 1 8597857090 501 20 0 [0 0 0 0] {1638414088 192363490} {1638414087 261441694} {1638414087 261441694} {1638413480 485068275} 38 8 4096 0 0 0 [0 0]} +} +``` + + +### `Copy` + +- 说明:支持复制文件或目录 +- 格式: + +```go +func Copy(src string, dst string) error +``` + +- 示例: + +```go +func ExampleCopy() { + // init + var ( + srcFileName = "gfile_example.txt" + srcTempDir = gfile.TempDir("gfile_example_copy_src") + srcTempFile = gfile.Join(srcTempDir, srcFileName) + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + + // copy dir + dstTempDir = gfile.TempDir("gfile_example_copy_dst") + ) + + // write contents + gfile.PutContents(srcTempFile, "goframe example copy") + + // copy file + gfile.Copy(srcTempFile, dstTempFile) + + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // copy dir + gfile.Copy(srcTempDir, dstTempDir) + + // list copy dir file + fList, _ := gfile.ScanDir(dstTempDir, "*", false) + for _, v := range fList { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // goframe example copy + // gfile_example.txt + // gfile_example_copy.txt +} +``` + + +### `CopyFile` + +- 说明:复制文件 +- 格式: + +```go +func CopyFile(src, dst string) (err error) +``` + +- 示例: + +```go +func ExampleCopyFile() { + // init + var ( + srcFileName = "gfile_example.txt" + srcTempDir = gfile.TempDir("gfile_example_copy_src") + srcTempFile = gfile.Join(srcTempDir, srcFileName) + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + ) + + // write contents + gfile.PutContents(srcTempFile, "goframe example copy") + + // copy file + gfile.CopyFile(srcTempFile, dstTempFile) + + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // Output: + // goframe example copy +} +``` + + +### `CopyDir` + +- 说明:支持复制文件或目录 +- 格式: + +```go +func CopyDir(src string, dst string) error +``` + +- 示例: + +```go +func ExampleCopyDir() { + // init + var ( + srcTempDir = gfile.TempDir("gfile_example_copy_src") + + // copy file + dstFileName = "gfile_example_copy.txt" + dstTempFile = gfile.Join(srcTempDir, dstFileName) + + // copy dir + dstTempDir = gfile.TempDir("gfile_example_copy_dst") + ) + // read contents after copy file + fmt.Println(gfile.GetContents(dstTempFile)) + + // copy dir + gfile.CopyDir(srcTempDir, dstTempDir) + + // list copy dir file + fList, _ := gfile.ScanDir(dstTempDir, "*", false) + for _, v := range fList { + fmt.Println(gfile.Basename(v)) + } + + // Output: + // gfile_example.txt + // gfile_example_copy.txt +} +``` + + +### `Move` + +- 说明:将 `src` 重命名为 `dst`。 + +- 注意:如果 `dst` 已经存在并且是文件,将会被替换造成数据丢失! +- 格式: + +```go +func Move(src string, dst string) error +``` + +- 示例: + +```go +func ExampleMove() { + // init + var ( + srcPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + dstPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file2") + ) + // Check is file + fmt.Println(gfile.IsFile(dstPath)) + + // Moves `src` to `dst` path. + // If `dst` already exists and is not a directory, it'll be replaced. + gfile.Move(srcPath, dstPath) + + fmt.Println(gfile.IsFile(srcPath)) + fmt.Println(gfile.IsFile(dstPath)) + + // Output: + // false + // false + // true +} +``` + + +### `Rename` + +- 说明: `Move` 的别名,将 `src` 重命名为 `dst`。 + +- 注意:如果 `dst` 已经存在并且是文件,将会被替换造成数据丢失! +- 格式: + +```go +func Rename(src string, dst string) error +``` + +- 示例: + +```go +func ExampleRename() { + // init + var ( + srcPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file2") + dstPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Check is file + fmt.Println(gfile.IsFile(dstPath)) + + // renames (moves) `src` to `dst` path. + // If `dst` already exists and is not a directory, it'll be replaced. + gfile.Rename(srcPath, dstPath) + + fmt.Println(gfile.IsFile(srcPath)) + fmt.Println(gfile.IsFile(dstPath)) + + // Output: + // false + // false + // true +} +``` + + +### `Remove` + +- 说明:删除给定路径的文件或文件夹。 + +- 格式: + +```go +func Remove(path string) error +``` + +- 示例: + +```go +func ExampleRemove() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Checks whether given `path` a file, which means it's not a directory. + fmt.Println(gfile.IsFile(path)) + + // deletes all file/directory with `path` parameter. + gfile.Remove(path) + + // Check again + fmt.Println(gfile.IsFile(path)) + + // Output: + // true + // false +} +``` + + +### `IsEmpty` + +- 说明:检查给定的路径,如果是文件夹则检查是否包含文件,如果是文件则检查文件大小是否为空。 + +- 格式: + +```go +func IsEmpty(path string) bool +``` + +- 示例: + +```go +func ExampleIsEmpty() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Check whether the `path` is empty + fmt.Println(gfile.IsEmpty(path)) + + // Truncate file + gfile.Truncate(path, 0) + + // Check whether the `path` is empty + fmt.Println(gfile.IsEmpty(path)) + + // Output: + // false + // true +} +``` + + +### `DirNames` + +- 说明:获取给定路径下的文件列表,返回的是一个切片。 + +- 格式: + +```go +func DirNames(path string) ([]string, error) +``` + +- 示例: + +```go +func ExampleDirNames() { + // init + var ( + path = gfile.TempDir("gfile_example_basic_dir") + ) + // Get sub-file names of given directory `path`. + dirNames, _ := gfile.DirNames(path) + + fmt.Println(dirNames) + + // May Output: + // [file1] +} +``` + + +### `Glob` + +- 说明:模糊搜索给定路径下的文件列表,支持正则,第二个参数控制返回的结果是否带上绝对路径。 + +- 格式: + +```go +func Glob(pattern string, onlyNames ...bool) ([]string, error) +``` + +- 示例: + +```go +func ExampleGlob() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "*_example_basic_test.go" + ) + // Get sub-file names of given directory `path`. + // Only show file name + matchNames, _ := gfile.Glob(path, true) + + fmt.Println(matchNames) + + // Show full path of the file + matchNames, _ = gfile.Glob(path, false) + + fmt.Println(matchNames) + + // May Output: + // [gfile_z_example_basic_test.go] + // [xxx/gf/os/gfile/gfile_z_example_basic_test.go] +} +``` + + +### `Exists` + +- 说明:检查给定的路径是否存在 。 +- 格式: + +```go +func Exists(path string) bool +``` + +- 示例: + +```go +func ExampleExists() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Checks whether given `path` exist. + fmt.Println(gfile.Exists(path)) + + // Output: + // true +} +``` + + +### `Chdir` + +- 说明:使用给定的路径,更改当前的工作路径。 +- 格式: + +```go +func Chdir(dir string) error +``` + +- 示例: + +```go +func ExampleChdir() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + // Get current working directory + fmt.Println(gfile.Pwd()) + + // Changes the current working directory to the named directory. + gfile.Chdir(path) + + // Get current working directory + fmt.Println(gfile.Pwd()) + + // May Output: + // xxx/gf/os/gfile + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +## 路径操作 + +### `Join` + +- 说明:将多个字符串路径通过\`/\`进行连接。 +- 格式: + +```go +func Join(paths ...string) string +``` + +- 示例: + +```go +func ExampleJoin() { + // init + var ( + dirPath = gfile.TempDir("gfile_example_basic_dir") + filePath = "file1" + ) + + // Joins string array paths with file separator of current system. + joinString := gfile.Join(dirPath, filePath) + + fmt.Println(joinString) + + // Output: + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +### `Abs` + +- 说明:返回路径的绝对路径。 + +- 格式: + +```go +func Abs(path string) string +``` + +- 示例: + +```go +func ExampleAbs() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get an absolute representation of path. + fmt.Println(gfile.Abs(path)) + + // Output: + // /tmp/gfile_example_basic_dir/file1 +} +``` + + +### `RealPath` + +- 说明:获取给定路径的绝对路径地址。 + +- 注意:如果文件不存在则返回空。 + +- 格式: + +```go +func RealPath(path string) string +``` + +- 示例: + +```go +func ExampleRealPath() { + // init + var ( + realPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + worryPath = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "worryFile") + ) + + // fetch an absolute representation of path. + fmt.Println(gfile.RealPath(realPath)) + fmt.Println(gfile.RealPath(worryPath)) + + // Output: + // /tmp/gfile_example_basic_dir/file1 + // +} +``` + + +### `SelfName` + +- 说明:获取当前运行程序的名称。 + +- 格式: + +```go +func SelfName() string +``` + +- 示例: + +```go +func ExampleSelfName() { + + // Get file name of current running process + fmt.Println(gfile.SelfName()) + + // May Output: + // ___github_com_gogf_gf_v2_os_gfile__ExampleSelfName +} +``` + + +### `Basename` + +- 说明:获取给定路径中的最后一个元素,包含扩展名。 + +- 格式: + +```go +func Basename(path string) string +``` + +- 示例: + +```go +func ExampleBasename() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the last element of path, which contains file extension. + fmt.Println(gfile.Basename(path)) + + // Output: + // file.log +} +``` + + +### `Name` + +- 说明:获取给定路径中的最后一个元素,不包含扩展名。 + +- 格式: + +```go +func Name(path string) string +``` + +- 示例: + +```go +func ExampleName() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the last element of path without file extension. + fmt.Println(gfile.Name(path)) + + // Output: + // file +} +``` + + +### `Dir` + +- 说明:获取给定路径的目录部分,排除最后的元素。 + +- 格式: + +```go +func Dir(path string) string +``` + +- 示例: + +```go +func ExampleDir() { + // init + var ( + path = gfile.Join(gfile.TempDir("gfile_example_basic_dir"), "file1") + ) + + // Get all but the last element of path, typically the path's directory. + fmt.Println(gfile.Dir(path)) + + // Output: + // /tmp/gfile_example_basic_dir +} +``` + + +### `Ext` + +- 说明:获取给定路径的扩展名,包含\`.\`。 + +- 格式: + +```go +func Ext(path string) string +``` + +- 示例: + +```go +func ExampleExt() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the file name extension used by path. + fmt.Println(gfile.Ext(path)) + + // Output: + // .log +} +``` + + +### `ExtName` + +- 说明:获取给定路径的扩展名,不包含\`.\`。 + +- 格式: + +```go +func ExtName(path string) string +``` + +- 示例: + +```go +func ExampleExtName() { + // init + var ( + path = gfile.Pwd() + gfile.Separator + "testdata/readline/file.log" + ) + + // Get the file name extension used by path but the result does not contains symbol '.'. + fmt.Println(gfile.ExtName(path)) + + // Output: + // log +} +``` + + +### `MainPkgPath` + +- 说明:获取main文件(主入口)所在的绝对路径,。 + +- 注意: + - `该方法仅在开发环境中可用,同时仅在源代码开发环境中有效,build二进制后将显示源代码的路径地址。` + - `第一次调用该方法时,如果处于异步的goroutine中,可能会无法获取主包的路径` +- 格式: + +```go +func MainPkgPath() string +``` + +- 示例: + +```go +func Test() { + fmt.Println("main pkg path on main :", gfile.MainPkgPath()) + char := make(chan int, 1) + go func() { + fmt.Println("main pkg path on goroutine :", gfile.MainPkgPath()) + char <- 1 + }() + select { + case <-char: + } + // Output: + // /xxx/xx/xxx/xx + // /xxx/xx/xxx/xx +} +// 二进制包 +$ ./testDemo +main pkg path on main : /xxx/xx/xxx/xx +main pkg path on goroutine : /xxx/xx/xxx/xx + + +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" new file mode 100644 index 00000000000..1e43c3a77fa --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\245\345\277\227\347\256\241\347\220\206-glog.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-glog' +title: '日志管理-glog' +sidebar_position: 5 +hide_title: true +description: '通过GoFrame框架的glog模块实现日志管理功能,帮助用户掌握使用GoFrame框架进行高效日志处理的方法与技巧。详细了解模块化设计与日志记录的使用方式。' +keywords: [GoFrame,GoFrame框架,glog模块,日志管理,日志功能,日志组件,日志记录,日志处理,开发框架,模块化设计] +--- + +日志管理功能由 `glog` 模块实现,具体请参考 [日志组件](../../核心组件/日志组件/日志组件.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" new file mode 100644 index 00000000000..5d491227797 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-gtime.md" @@ -0,0 +1,30 @@ +--- +slug: '/docs/components/os-gtime' +title: '时间管理-gtime' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,时间管理,gtime,日期格式化,PHP date,通用时间模块,时间扩展,时间日期方法,自定义格式] +description: 'gtime模块是GoFrame框架的通用时间管理模块,拓展了Golang标准库time的功能,提供了自定义的日期格式化语法,对PHP的date函数格式有良好兼容性,使PHP开发者在Go中实现时间管理时更加便利。' +--- + +## 基本介绍 + +通用时间管理模块,封装了常用的时间/日期相关的方法,作为标准库 `time` 的功能性扩展,提供了更多的功能特性。支持自定义的日期格式化语法,格式化语法灵感来源于 `PHP` 的 `date` 函数语法 ( [http://php.net/manual/zh/function.date.php](http://php.net/manual/zh/function.date.php) )。 +:::tip +`gtime` 的时间格式语法对于熟悉 `PHP` 的开发者来说非常友好。 +::: +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gtime" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..b9074aa3e4a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,209 @@ +--- +slug: '/docs/components/os-gtime-common-funcs' +title: '时间管理-工具方法' +sidebar_position: 2 +hide_title: true +keywords: [时间管理,GoFrame,时间戳,日期处理,全球时区,时间格式解析,gtime时间对象,工具方法,时间转换,时间输出示例] +description: '使用GoFrame框架进行时间管理工具方法,包括获取当前时间戳、日期和时间设置的方法,如Timestamp、Date和SetTimeZone,以及如何通过StrToTime解析常见时间格式字符串为gtime.Time对象。通过GoFrame框架,开发者可以便利地进行多种时间格式转换和时区设置。' +--- + +接口文档: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) + +方法比较简单,比较常用的是以下几个方法: + +1. `Timestamp` 用于获得当前时间戳, `TimestampMilli`、 `TimestampMicro` 及 `TimestampNano` 用于获得当前的毫秒、微秒和纳秒值。 +2. `Date` 和 `Datetime` 用于获得当前日期及当前日期时间。 +3. `SetTimeZone` 用于设置当前进程的全局时区。 +4. 其他方法说明请查看接口文档。 + +### 示例1,基本使用 + +创建时间对象及获取当前时间戳。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + fmt.Println("Date :", gtime.Date()) + fmt.Println("Datetime :", gtime.Datetime()) + fmt.Println("Second :", gtime.Timestamp()) + fmt.Println("Millisecond:", gtime.TimestampMilli()) + fmt.Println("Microsecond:", gtime.TimestampMicro()) + fmt.Println("Nanosecond :", gtime.TimestampNano()) +} +``` + +执行后,输出结果为: + +``` +Date : 2018-07-22 +Datetime : 2018-07-22 11:52:22 +Second : 1532231542 +Millisecond: 1532231542688 +Microsecond: 1532231542688688 +Nanosecond : 1532231542688690259 +``` + +### 示例2, `StrToTime` + +除了通过 `New` 方法外,也可以通过 `StrToTime` 根据常见的时间字符串解析生成 `gtime.Time` 对象,常见的时间字符串如下: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-14 04:51:34 +0805 LMT +2006-01-02T15:04:05Z07:00 +2014-01-17T01:19:15+08:00 +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17.897 +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018/10/31 - 16:38:46 +2018-02-09 +2018.02.09 + +01-Nov-2018 11:50:28 +01/Nov/2018 11:50:28 +01.Nov.2018 11:50:28 +01.Nov.2018:11:50:28 +日期连接符号支持'-'、'/'、'.' +``` + +使用示例: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + array := []string{ + "2017-12-14 04:51:34 +0805 LMT", + "2006-01-02T15:04:05Z07:00", + "2014-01-17T01:19:15+08:00", + "2018-02-09T20:46:17.897Z", + "2018-02-09 20:46:17.897", + "2018-02-09T20:46:17Z", + "2018-02-09 20:46:17", + "2018.02.09 20:46:17", + "2018-02-09", + "2017/12/14 04:51:34 +0805 LMT", + "2018/02/09 12:00:15", + "01/Nov/2018:13:28:13 +0800", + "01-Nov-2018 11:50:28 +0805 LMT", + "01-Nov-2018T15:04:05Z07:00", + "01-Nov-2018T01:19:15+08:00", + "01-Nov-2018 11:50:28 +0805 LMT", + "01/Nov/2018 11:50:28", + "01/Nov/2018:11:50:28", + "01.Nov.2018:11:50:28", + "01/Nov/2018", + } + cstLocal, _ := time.LoadLocation("Asia/Shanghai") + for _, s := range array { + if t, err := gtime.StrToTime(s); err == nil { + fmt.Println(s) + fmt.Println(t.UTC().String()) + fmt.Println(t.In(cstLocal).String()) + } else { + glog.Error(s, err) + } + fmt.Println() + } +} +``` + +在这个示例中,将部分时间格式串使用 `StrToTime` 方法转换为 `gtime.Time` 对象,并输出该事件的 `UTC` 时间和 `CST` 时间(上海时区时间)。 执行后,输出结果为: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-13 20:46:34 +2017-12-14 04:46:34 +0800 CST + +2006-01-02T15:04:05Z07:00 +2006-01-02 22:04:05 +2006-01-03 06:04:05 +0800 CST + +2014-01-17T01:19:15+08:00 +2014-01-16 17:19:15 +2014-01-17 01:19:15 +0800 CST + +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17 +2018-02-10 04:46:17.897 +0800 CST + +2018-02-09 20:46:17.897 +2018-02-09 12:46:17 +2018-02-09 20:46:17.897 +0800 CST + +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018-02-10 04:46:17 +0800 CST + +2018-02-09 20:46:17 +2018-02-09 12:46:17 +2018-02-09 20:46:17 +0800 CST + +2018.02.09 20:46:17 +2018-02-09 12:46:17 +2018-02-09 20:46:17 +0800 CST + +2018-02-09 +2018-02-08 16:00:00 +2018-02-09 00:00:00 +0800 CST + +2017/12/14 04:51:34 +0805 LMT +2017-12-13 20:46:34 +2017-12-14 04:46:34 +0800 CST + +2018/02/09 12:00:15 +2018-02-09 04:00:15 +2018-02-09 12:00:15 +0800 CST + +01/Nov/2018:13:28:13 +0800 +2018-11-01 05:28:13 +2018-11-01 13:28:13 +0800 CST + +01-Nov-2018 11:50:28 +0805 LMT +2018-11-01 03:45:28 +2018-11-01 11:45:28 +0800 CST + +01-Nov-2018T15:04:05Z07:00 +2018-11-01 22:04:05 +2018-11-02 06:04:05 +0800 CST + +01-Nov-2018T01:19:15+08:00 +2018-10-31 17:19:15 +2018-11-01 01:19:15 +0800 CST + +01-Nov-2018 11:50:28 +0805 LMT +2018-11-01 03:45:28 +2018-11-01 11:45:28 +0800 CST + +01/Nov/2018 11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01/Nov/2018:11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01.Nov.2018:11:50:28 +2018-11-01 03:50:28 +2018-11-01 11:50:28 +0800 CST + +01/Nov/2018 +2018-10-31 16:00:00 +2018-11-01 00:00:00 +0800 CST +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..6b5f9457dc8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,722 @@ +--- +slug: '/docs/components/os-gtime-funcs' +title: '时间管理-方法介绍' +sidebar_position: 4 +hide_title: true +keywords: [时间管理,GoFrame,时间对象,时间格式,时区设置,时间戳,时间操作,时间比较,闰年判断,日期时间] +description: 'GoFrame框架中与时间管理相关的方法,包括如何创建时间对象、格式化时间、设置时区与获取时间戳等多种功能的实现,可以帮助开发者更加方便地进行时间操作与管理。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime](https://pkg.go.dev/github.com/gogf/gf/v2/os/gtime) +::: +## `New` + +- 说明: `New` 创建并返回一个具有给定参数的 `Time` 对象。 +- 格式: + +```go +func New(param ...interface{}) *Time +``` + + +- 示例:创建时间对象。 + +```go +func ExampleNew() { + t1 := gtime.New(time.Now()) + t2 := gtime.New("2018-08-08 08:08:08") + t3 := gtime.New(1533686888) + + fmt.Println(t1) + fmt.Println(t2) + fmt.Println(t3) + + // Output: + // 2021-11-18 14:18:27 + // 2018-08-08 08:08:08 + // 2018-08-08 08:08:08 +``` + + +## `Now` + +- 说明: `Now` 创建并返回一个当前时间对象。 +- 格式: + +```go +func Now() *Time +``` + +- 示例:获取当前时间对象。 + +```go +func ExampleNow() { + t := gtime.Now() + fmt.Println(t) + + // Output: + // 2021-11-06 13:41:08 +} +``` + + +## `Format` + +- 说明: 格式化输出时间 +- 格式: + +```go +func (t *Time) Format(format string) string +``` + +- 示例:格式化输出时间。完整的时间格式可查阅 [时间管理-时间格式](时间管理-时间格式.md)。 + +```go +func ExampleTime_Format() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Format("Y-m-d")) + fmt.Println(gt1.Format("l")) + fmt.Println(gt1.Format("F j, Y, g:i a")) + fmt.Println(gt1.Format("j, n, Y")) + fmt.Println(gt1.Format("h-i-s, j-m-y, it is w Day z")) + fmt.Println(gt1.Format("D M j G:i:s T Y")) + + // Output: + // 2018-08-08 + // Wednesday + // August 8, 2018, 8:08 am + // 8, 8, 2018 + // 08-08-08, 8-08-18, 0831 0808 3 Wedam18 219 + // Wed Aug 8 8:08:08 CST 2018 +} +``` + + +## `String` + +- 说明:  输出字符串 +- 格式: + +```go +func (t *Time) String() string +``` + +- 示例:输出字符串类型。 + +```go +func ExampleTime_String() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.String() + + fmt.Println(t1) + fmt.Println(reflect.TypeOf(t1)) + + // Output: + // 2018-08-08 08:08:08 + // string +} +``` + + +## `Timestamp` + +- 说明:  获取当前对象的秒时间戳。相应的还有 `TimestampMicro/TimestampMilli/TimestampNano`。 +- 格式: + +```go +func (t *Time) Timestamp() int64 +func Timestamp() int64 +``` + +- 示例:获取当前对象的秒时间戳。 + +```go +func ExampleTime_Timestamp() { + t := gtime.Now() + + fmt.Println(t.Timestamp()) + fmt.Println(gtime.Timestamp()) + fmt.Println(t.TimestampMicro()) + fmt.Println(t.TimestampMilli()) + fmt.Println(t.TimestampNano()) + + // Output: + // 1533686888 + // 1533686888 + // 1533686888000 + // 1533686888000000 + // 1533686888000000000 +} +``` + + +## `ToZone` + +- 说明:  设置时区。 +- 格式: + +```go +func (t *Time) ToZone(zone string) (*Time, error) +``` + +- 示例:获取当前对象的秒时间戳。 + +```go +func ExampleTime_ToZone() { + gt1 := gtime.Now() + gt2, _ := gt1.ToZone("Asia/Shanghai") + gt3, _ := gt1.ToZone("Asia/Tokyo") + + fmt.Println(gt2) + fmt.Println(gt3) + + // May Output: + // 2021-11-11 17:10:10 + // 2021-11-11 18:10:10 +} +``` + + +## `SetTimeZone` + +- 说明:  设置时区。 +- 格式: + +```go +func SetTimeZone(zone string) error +``` + +- 示例: 设置时区。 + +```go +func ExampleSetTimeZone() { + gtime.SetTimeZone("Asia/Shanghai") + fmt.Println(gtime.Datetime()) + + gtime.SetTimeZone("Asia/Tokyo") + fmt.Println(gtime.Datetime()) + // May Output: + // 2018-08-08 08:08:08 + // 2018-08-08 09:08:08 +} +``` + + +## `StrToTime` + +- 说明:  时间字符串转成时间对象。 +- 格式: + +```go +func StrToTime(str string, format ...string) (*Time, error) +``` + +- 示例: 时间字符串转成时间对象。 + +```go +func ExampleStrToTime() { + res, _ := gtime.StrToTime("2006-01-02T15:04:05-07:00", "Y-m-d H:i:s") + fmt.Println(res) + + // May Output: + // 2006-01-02 15:04:05 +} +``` + + +## `Add` + +- 说明:  在当前时间对象上增加时间。 +- 格式: + +```go +func (t *Time) Add(d time.Duration) *Time +``` + +- 示例: 在当前时间对象上增加时间。 + +```go +func ExampleTime_Add() { + gt := gtime.New("2018-08-08 08:08:08") + gt1 := gt.Add(time.Duration(10) * time.Second) + + fmt.Println(gt1) + + // Output: + // 2018-08-08 08:08:18 +} +``` + + +## `StartOfDay` + +- 说明:  返回今天最开始的时间对象。类似的还有 `StartOfHalf/StartOfHour/StartOfMonth/StartOfMinute/StartOfQuarter` 等。 +- 格式: + +```go +func (t *Time) StartOfDay() *Time +``` + +- 示例: 返回今天最开始的时间对象。 + +```go +func ExampleTime_StartOfDay() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.StartOfDay()) + + // Output: + // 2018-08-08 00:00:00 +} +``` + + +## `EndOfDay` + +- 说明:  返回今天结束的时间对象。类似的还有 `EndOfHalf/EndOfHour/EndOfMonth/EndOfMinute/EndOfQuarter` 等。 +- 格式: + +```go +func (t *Time) EndOfDay() *Time +``` + +- 示例: 返回今天结束的时间对象。 + +```go +func ExampleTime_EndOfDay() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.EndOfDay()) + + // Output: + // 2018-08-08 23:59:59 +} +``` + + +## `Month` + +- 说明:  返回这个月处于全年的索引号。例如,1月对应的是1。 +- 格式: + +```go +func (t *Time) Month() int +``` + +- 示例: 返回这个月处于全年的索引号。 + +```go +func ExampleTime_Month() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.Month() + + fmt.Println(t1) + + // Output: + // 8 +} +``` + + +## `Second` + +- 说明:  返回当前分钟的秒数。例如,10:10:08 对应的秒数为8。 +- 格式: + +```go +func (t *Time) Second() int +``` + +- 示例: 返回这个月处于全年的索引号。 + +```go +func ExampleTime_Second() { + gt := gtime.New("2018-08-08 08:08:08") + t1 := gt.Second() + + fmt.Println(t1) + + // Output: + // 8 +} +``` + + +## `IsZero` + +- 说明:  判断时间是否等于 `0001-01-01 00:00:00`。注意不代表时间戳为0, 时间戳为0是 `1970-01-01 08:00:00` +- 格式: + +```go +func (t *Time) IsZero() bool +``` + +- 示例: 返回这个月处于全年的索引号。 + +```go +func ExampleTime_IsZero() { + gt := gtime.New("0-0-0") + + fmt.Println(gt.IsZero()) + + // Output: + // true +} +``` + + +## `AddDate` + +- 说明:  在当前时间对象上增加指定年月日。 +- 格式: + +```go +func (t *Time) AddDate(years int, months int, days int) *Time +``` + +- 示例: 在当前时间对象上增加指定年月日。 + +```go +func ExampleTime_AddDate() { + var ( + year = 1 + month = 2 + day = 3 + ) + gt := gtime.New("2018-08-08 08:08:08") + gt = gt.AddDate(year, month, day) + + fmt.Println(gt) + + // Output: + // 2019-10-11 08:08:08 +} +``` + + +## `Equal` + +- 说明:  判断两个时间对象是否相等。 +- 格式: + +```go +func (t *Time) Equal(u *Time) bool +``` + +- 示例: 判断两个时间对象是否相等。 + +```go +func ExampleTime_Equal() { + gt1 := gtime.New("2018-08-08 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Equal(gt2)) + + // Output: + // true +} +``` + + +## `Before` + +- 说明:  判断两个时间对象前后顺序。 +- 格式: + +```go +func (t *Time) Before(u *Time) bool +``` + +- 示例: 判断两个时间对象前后顺序。 + +```go +func ExampleTime_Before() { + gt1 := gtime.New("2018-08-07 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Before(gt2)) + + // Output: + // true +} +``` + + +## `After` + +- 说明:  判断两个时间对象前后顺序。 +- 格式: + +```go +func (t *Time) After(u *Time) bool +``` + +- 示例: 判断两个时间对象前后顺序。 + +```go +func ExampleTime_After() { + gt1 := gtime.New("2018-08-07 08:08:08") + gt2 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.After(gt2)) + + // Output: + // false +} +``` + + +## `Layout` + +- 说明:  格式化输出时间。 +- 格式: + +```go +func (t *Time) Layout(layout string) string +``` + +- 示例: 格式化输出时间。 + +```go +func ExampleTime_Layout() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.Layout("2006-01-02")) + + // Output: + // 2018-08-08 +} +``` + + +## `IsLeapYear` + +- 说明:  是否闰年。 +- 格式: + +```go +func (t *Time) IsLeapYear() bool +``` + +- 示例: 是否闰年。 + +```go +func ExampleTime_IsLeapYear() { + gt1 := gtime.New("2018-08-08 08:08:08") + + fmt.Println(gt1.IsLeapYear()) + + // Output: + // false +} +``` + + +## `Date` + +- 说明:  获取日期。 +- 格式: + +```go +func Date() string +``` + +- 示例: 获取日期。 + +```go +func ExampleDate() { + fmt.Println(gtime.Date()) + + // May Output: + // 2006-01-02 +} +``` + + +## `Datetime` + +- 说明:  获取日期时间。 +- 格式: + +```go +func Datetime() string +``` + +- 示例: 获取日期。 + +```go +func ExampleDatetime() { + fmt.Println(gtime.Datetime()) + + // May Output: + // 2006-01-02 15:04:05 +} +``` + + +## `ISO8601` + +- 说明:  以ISO8601的格式返回时间 +- 格式: + +```go +func ISO8601() string +``` + +- 示例: + +```go +func ExampleISO8601() { + fmt.Println(gtime.ISO8601()) + + // May Output: + // 2006-01-02T15:04:05-07:00 +} +``` + + +## `RFC822` + +- 说明:  以RFC822的格式返回时间 +- 格式: + +```go +func RFC822() string +``` + +- 示例: + +```go +func ExampleRFC822() { + fmt.Println(gtime.RFC822()) + + // May Output: + // Mon, 02 Jan 06 15:04 MST +} +``` + + +## `StrToTimeFormat` + +- 说明:  `StrToTimeFormat` 根据传入的时间字符串以及格式返回时间对象 +- 格式: + +```go +func StrToTimeFormat(str string, format string) (*Time, error) +``` + +- 示例: + +```go +func ExampleStrToTimeFormat() { + res, _ := gtime.StrToTimeFormat("2006-01-02 15:04:05", "Y-m-d H:i:s") + fmt.Println(res) + + // Output: + // 2006-01-02 15:04:05 +} +``` + + +## `StrToTimeLayout` + +- 说明:  `StrToTimeLayout` 根据传入的时间字符串以及格式返回时间对象 +- 格式: + +```go +func StrToTimeLayout(str string, layout string) (*Time, error) +``` + +- 示例: + +```go +func ExampleStrToTimeLayout() { + res, _ := gtime.StrToTimeLayout("2018-08-08", "2006-01-02") + fmt.Println(res) + + // Output: + // 2018-08-08 00:00:00 +} +``` + + +## `MarshalJSON` + +- 说明:  `MarshalJSON` 重载 `json.Marshal` 中的方法。 +- 格式: + +```go +func (t *Time) MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleTime_MarshalJSON() { + type Person struct { + Name string `json:"name"` + Birthday *gtime.Time `json:"birthday"` + } + p := new(Person) + p.Name = "goframe" + p.Birthday = gtime.New("2018-08-08 08:08:08") + j, _ := json.Marshal(p) + fmt.Println(string(j)) + + // Output: + // {"name":"xiaoming","birthday":"2018-08-08 08:08:08"} +} +``` + + +## `UnmarshalJSON` + +- 说明:  `UnmarshalJSON` 重载 `json.Unmarshal` 中的方法。 +- 格式: + +```go +func (t *Time) UnmarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleTime_MarshalJSON() { + type Person struct { + Name string `json:"name"` + Birthday *gtime.Time `json:"birthday"` + } + p := new(Person) + p.Name = "goframe" + p.Birthday = gtime.New("2018-08-08 08:08:08") + j, _ := json.Marshal(p) + fmt.Println(string(j)) + + // Output: + // {"name":"xiaoming","birthday":"2018-08-08 08:08:08"} +} +``` + + +## `WeekOfYear` + +- 说明:  `WeekOfYear` 返回当前周处于全年第几周,从1开始计算。类似的还有 `DayOfYear/DaysInMonth` +- 格式: + +```go +func (t *Time) WeeksOfYear() int +``` + +- 示例: + +```go +func ExampleTime_WeeksOfYear() { + gt1 := gtime.New("2018-01-08 08:08:08") + + fmt.Println(gt1.WeeksOfYear()) + + // Output: + // 2 +}D +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" new file mode 100644 index 00000000000..fa0ffc1a94c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\345\214\272\350\256\276\347\275\256.md" @@ -0,0 +1,115 @@ +--- +slug: '/docs/components/os-gtime-timezone' +title: '时间管理-时区设置' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,时区设置,SetTimeZone,时间管理,gtime,标准库,时间转换,日志输出,全局设置,编程指南] +description: '使用GoFrame框架中的gtime组件进行全局时区设置,解释SetTimeZone方法的使用限制及注意事项,并提供代码示例展示程序中如何正确管理和转换时间,尤其是在涉及多个时区的业务场景中。' +--- + +## `SetTimeZone` 设置全局时区 + +```go +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // 设置进程全局时区 + err := gtime.SetTimeZone("Asia/Tokyo") + if err != nil { + panic(err) + } + + // 使用gtime获取当前时间 + fmt.Println(gtime.Now().String()) + + // 使用标准库获取当前时间 + fmt.Println(time.Now().String()) +} +``` + +执行后,输出结果为: + +```html +2023-01-06 15:27:38 +2023-01-06 15:27:38.753909 +0900 JST m=+0.002758145 +``` + +## 时区设置注意事项 + +### `SetTimeZone` 方法多次调用报错 + +`SetTimeZone` 方法只允许全局设置一次时区,如果多次调用,并且设置的时区不一样,后续调用将会失败,并返回 `error`。 + +### 业务项目中, `time` 包初始化问题 + +程序时区的全局设置必须要在标准库的 `time` 包 `import` 之前调用,因为标准库的 `time` 包在 `import` 时会执行初始化,之后无法再全局修改程序时区,只能通过 `ToLocation` 方法(或者标准库 `In` 方法)对特定时间对象执行时区转换。对时间对象使用 `ToLocation` 转换的例子: + +```go +package main + +import ( + "fmt" + "time" + + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // 设置进程全局时区 + err := gtime.SetTimeZone("Asia/Tokyo") + if err != nil { + panic(err) + } + + // 使用gtime获取当前时间 + fmt.Println(gtime.Now()) + + // 使用标准库获取当前时间 + fmt.Println(time.Now()) + + // 对特定时间对象执行时区转换 + local, err := time.LoadLocation("Asia/Shanghai") + if err != nil { + panic(err) + } + fmt.Println(gtime.Now().ToLocation(local)) +} +``` + +执行后,终端输出: + +```html +2023-01-06 15:37:38 +2023-01-06 15:37:38.753909 +0900 JST m=+0.002758145 +2023-01-06 14:37:38 +``` + +在业务项目中,往往在 `main` 包之前会有很多业务的包 `import`,会引起 `time` 包的初始化问题。因此,如果需要全局设置时区,建议通过一个独立的包来调用 `SetTimeZone` 方法,并且在 `main` 包的最开头执行 `import` 规避 `time` 包初始化的问题。例如: + +相关参考链接: [https://stackoverflow.com/questions/54363451/setting-timezone-globally-in-golang](https://stackoverflow.com/questions/54363451/setting-timezone-globally-in-golang) + +```go +package main + +import ( + _ "boot/time" + + "fmt" + "time" +) + +func main() { + // 使用gtime获取当前时间 + fmt.Println(gtime.Now().String()) + + // 使用标准库获取当前时间 + fmt.Println(time.Now().String()) +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..715889e033d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\345\257\271\350\261\241.md" @@ -0,0 +1,166 @@ +--- +slug: '/docs/components/os-gtime-time' +title: '时间管理-时间对象' +sidebar_position: 1 +hide_title: true +keywords: [时间管理,时间对象,GoFrame,时间戳,格式化,标准库,自定义时间,时间字符串,链式操作,gtime] +description: '通过GoFrame创建和管理时间对象,包括通过标准库time.Time对象、Unix时间戳、时间字符串等创建gtime.Time对象的方法。同时讲解了如何使用自定义和标准库格式对时间进行格式化,并展示了时间对象的链式操作示例,帮助开发者更高效地处理时间数据。' +--- + +## 时间对象 + +创建 `gtime.Time` 对象可以通过标准库 `time.Time` 对象、Unix时间戳、时间字符串(如: `2018-07-18 12:01:00`)、自定义时间字符串(需要给定格式,支持自定义格式及标准库格式)。 + +## 创建对象 + +可以通过 `gtime.New` 方法创建 `gtime.Time` 对象,该方法支持 `time.Time`、时间戳、时间字符串创建对象。其中时间戳支持到纳秒的时间整型长度。 例如: + +```go +// 通过时间字符串创建 +gtime.New("2020-10-24 12:00:00") +// 通过time.Time对象创建 +gtime.New(time.Now()) +// 通过时间戳(秒)创建 +gtime.New(1603710586) +// 通过时间戳(纳秒)创建 +gtime.New(1603710586660409000) +``` + +此外,时间字符串支持常见时间类型,例如: + +```html +2017-12-14 04:51:34 +0805 LMT +2017-12-14 04:51:34 +0805 LMT +2006-01-02T15:04:05Z07:00 +2014-01-17T01:19:15+08:00 +2018-02-09T20:46:17.897Z +2018-02-09 20:46:17.897 +2018-02-09T20:46:17Z +2018-02-09 20:46:17 +2018/10/31 - 16:38:46 +2018-02-09 +2018.02.09 + +01-Nov-2018 11:50:28 +01/Nov/2018 11:50:28 +01.Nov.2018 11:50:28 +01.Nov.2018:11:50:28 +日期连接符号支持'-'、'/'、'.' +``` + +## 使用示例 + +### 示例1,自定义格式化语法 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + formats := []string{ + "Y-m-d H:i:s.u", + "D M d H:i:s T O Y", + "\\T\\i\\m\\e \\i\\s: h:i:s a", + "2006-01-02T15:04:05.000000000Z07:00", + } + t := gtime.Now() + for _, f := range formats { + fmt.Println(t.Format(f)) + } +} +``` + +在该示例中,我们给定了四种 `format` 格式,并将当前时间用这四种格式转换后打印出来。执行后,输出结果如下: + +```html +2018-07-22 11:17:13.797 +Sun Jul 22 11:17:13 CST +0800 2018 +Time is: 11:17:13 am +2006-01-02CST15:04:05.000000000Z07:00 +``` + +可以看到,这个示例演示了几个需要注意的地方: + +1. 如果使用的字母与格式化字符冲突时,可以使用 `\` 符号转义该字符,这样时间格式解析器会认为该字符不是格式化字符,而是普通字母。因此这里的第三个字符串示例输出为: `Time is: 11:17:13 am` +2. 使用 `Format` 方法接收的是自定义的时间格式化语法(如: `Y-m-d H:i:s`),而非标准库的时间格式语法(如: `2006-01-02 15:04:05`),因此在这里的第四个字符串示例中原样输出参数值; + +### 示例2,标准库格式化语法 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + formats := []string{ + "2006-01-02 15:04:05.000", + "Mon Jan _2 15:04:05 MST 2006", + "Time is: 03:04:05 PM", + "2006-01-02T15:04:05.000000000Z07:00 MST", + } + t := gtime.Now() + for _, f := range formats { + fmt.Println(t.Layout(f)) + } +} +``` + +在该示例中,我们使用四种标准库的时间格式化语法格式化当前的时间并输出结果到终端。执行后,输出结果为: + +```html +2018-07-22 11:28:13.945 +Sun Jul 22 11:28:13 CST 2018 +Time is: 11:28:13 AM +2018-07-22T11:28:13.945153275+08:00 CST +``` + +有几个需要说明的地方: + +1. 自定义时间格式化语法与标准库时间格式化语法并不冲突,前者使用 `Format` 方法,后者使用 `Layout` 方法行格式化,相互独立,互不冲突,无法混用; +2. 标准库的时间格式化语法自有特点,是不是感觉有点复杂; + +### 示例3,时间对象链式操作 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gtime" + "time" +) + +func main() { + // 去年今日,系统时间 + fmt.Println(gtime.Now().AddDate(-1, 0, 0).Format("Y-m-d")) + + // 去年今日,UTC时间 + fmt.Println(gtime.Now().AddDate(-1, 0, 0).Format("Y-m-d H:i:s T")) + fmt.Println(gtime.Now().AddDate(-1, 0, 0).UTC().Format("Y-m-d H:i:s T")) + + // 下个月1号凌晨0点整 + fmt.Println(gtime.Now().AddDate(0, 1, 0).Format("Y-m-01 00:00:00")) + + // 1个小时前 + fmt.Println(gtime.Now().Add(-time.Hour).Format("Y-m-d H:i:s")) +} +``` + +执行后,输出结果为: + +``` +2020-09-19 +2020-09-19 15:51:48 CST +2020-09-19 07:51:48 UTC +2021-10-01 00:00:00 +2021-09-19 14:51:48 +``` + +该示例比较简单,便不多赘述。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" new file mode 100644 index 00000000000..5511aa69925 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\227\266\351\227\264\347\256\241\347\220\206-gtime/\346\227\266\351\227\264\347\256\241\347\220\206-\346\227\266\351\227\264\346\240\274\345\274\217.md" @@ -0,0 +1,53 @@ +--- +slug: '/docs/components/os-gtime-format' +title: '时间管理-时间格式' +sidebar_position: 0 +hide_title: true +keywords: [时间管理,时间格式,GoFrame,gtime,时间转换,自定义格式,日期格式,时间语法,时间戳,时区] +description: 'GoFrame框架中的时间管理模块,重点讲解gtime.Time对象的时间格式自定义功能。通过对比标准库time.Time的Format方法,gtime提供了Layout方法以实现标准格式日期转换,同时列出了gtime模块支持的各种时间格式语法的细节,帮助开发者高效管理与转换日期时间。' +--- +:::warning +注意事项: `gtime.Time` 对象通过 `Format` 方法来实现自定义格式的时间日期转换,该方法与标准库 `time.Time` 的 `Format` 方法冲突。在 `gtime.Time` 对象中,通过 `Layout` 方法实现标准库 `time.Time` 的 `Format` 格式,例如: ``t.Layout(`2006-01-02 15:04:05`)``。 +::: +以下是 `gtime` 模块支持的时间格式语法列表: + +| 格式 | 说明 | 返回值示例 | +| --- | --- | --- | +| **日** | -- | -- | +| `d` | 月份中的第几天,有前导零的 2 位数字 | 01 到 31 | +| `D` | 星期中的第几天,文本表示,3 个字母 | Mon 到 Sun | +| `N` | ISO-8601 格式数字表示的星期中的第几天 | 1(表示星期一)到 7(表示星期天) | +| `j` | 月份中的第几天,没有前导零 | 1 到 31 | +| `l` | ("L"的小写字母)星期几,完整的文本格式 | Sunday 到 Saturday | +| `S` | 每月天数后面的英文后缀,2 个字符 | st,nd,rd 或者 th。可以和 j 一起用 | +| `w` | 星期中的第几天,数字表示 | 0(表示星期天)到 6(表示星期六) | +| `z` | 年份中的第几天 | 0 到 365 | +| **周** | -- | -- | +| `W` | ISO-8601 格式年份中的第几周,每周从星期一开始 | 例如:42(当年的第 42 周) | +| **月** | -- | -- | +| `F` | 月份,完整的文本格式 | January 到 December | +| `m` | 数字表示的月份,有前导零 | 01 到 12 | +| `M` | 三个字母缩写表示的月份 | Jan 到 Dec | +| `n` | 数字表示的月份,没有前导零 | 1 到 12 | +| `t` | 指定的月份有几天 | 28 到 31 | +| **年** | -- | -- | +| `Y` | 4 位数字完整表示的年份 | 例如:1999 或 2003 | +| `y` | 2 位数字表示的年份 | 例如:99 或 03 | +| **时间** | -- | -- | +| `a` | 小写的上午和下午值 | am 或 pm | +| `A` | 大写的上午和下午值 | AM 或 PM | +| `g` | 小时,12 小时格式,没有前导零 | 1 到 12 | +| `G` | 小时,24 小时格式,没有前导零 | 0 到 23 | +| `h` | 小时,12 小时格式,有前导零 | 01 到 12 | +| `H` | 小时,24 小时格式,有前导零 | 00 到 23 | +| `i` | 有前导零的分钟数 | 00 到 59 | +| `s` | 秒数,有前导零 | 00 到 59 | +| `u` | 毫秒数(3位) | 例如:000, 123, 239 | +| `U` | UNIX时间戳(秒) | 例如:1559648183 | +| **时区** | -- | -- | +| `O` | 与UTC相差的小时数 | 例如:+0200 | +| `P` | 与UTC的差别,小时和分钟之间有冒号分隔 | 例如:+02:00 | +| `T` | 时区缩写 | 例如:UTC,GMT,CST | +| **日期** | -- | -- | +| `c` | ISO 8601 格式的日期 | 例如:2004-02-12T15:19:21+00:00 | +| `r` | RFC 822 格式的日期 | 例如:Thu, 21 Dec 2000 16:01:07 +0200 | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" new file mode 100644 index 00000000000..70ed345de4c --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\236\204\345\273\272\344\277\241\346\201\257-gbuild.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/os-gbuild' +title: '构建信息-gbuild' +sidebar_position: 17 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf build,构建信息,gbuild,程序构建,Go语言,接口文档,软件开发,代码导入] +description: '使用GoFrame框架中的gbuild模块获取程序构建信息。通过gf build命令构建程序后,您可以通过gbuild模块访问这些构建信息。本模块是GoFrame框架的一部分,支持Go语言的软件开发者快速检索和利用构建数据,提供了简单易用的接口文档。' +--- + +用于获取采用 `gf build` 命令构建程序的构建信息。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gbuild" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gbuild](https://pkg.go.dev/github.com/gogf/gf/v2/os/gbuild) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" new file mode 100644 index 00000000000..cbe4d89554e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\346\250\241\346\235\277\345\274\225\346\223\216-gview.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gview' +title: '模板引擎-gview' +sidebar_position: 15 +hide_title: true +keywords: [GoFrame,GoFrame框架,模板引擎,gview,gview模块,核心组件,模板引擎实现,模块实现,gview使用,gview功能] +description: 'GoFrame框架中的模板引擎组件gview,详细说明了gview模块的功能和使用方法。通过对核心组件模板引擎的讲解,帮助用户理解和应用gview模块实现复杂的模板引擎功能。' +--- + +模板引擎由 `gview` 模块实现,具体请参考 [模板引擎](../../核心组件/模板引擎/模板引擎.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" new file mode 100644 index 00000000000..f162763632f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\216\257\345\242\203\345\217\230\351\207\217-genv.md" @@ -0,0 +1,113 @@ +--- +slug: '/docs/components/os-genv' +title: '环境变量-genv' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame,环境变量管理,genv,SetMap,GetWithCmd,GoFrame框架,环境变量设置,命令行选项,删除环境变量,批量设置环境变量] +description: 'GoFrame框架中的genv环境变量管理组件,包括如何批量设置环境变量,以及如何通过命令行选项获取环境变量。当某个环境变量不存在时,支持从命令行选项读取。此外,还涵盖了环境变量的添加、删除、及其命名转换规则等内容。' +--- + +环境变量管理组件。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/genv" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/genv](https://pkg.go.dev/github.com/gogf/gf/v2/os/genv) + +## `SetMap` + +```go +func SetMap(m map[string]string) error +``` + +该方法用于批量设置环境变量。使用示例: + +``` +genv.SetMap(g.MapStrStr{ + "APPID": "order", + "THREAD": "16", + "ENDPOINTS": "127.0.0.1:6379", +}) +``` + +## `GetWithCmd` + +```go +func GetWithCmd(key string, def ...interface{}) *gvar.Var +``` + +该方法用于获取环境变量中指定的选项数值,如果该环境变量不存在时,则从命令行选项中读取。但是两者的名称规则会不一样。例如: `genv.GetWithCmd("gf.debug")` 将会优先去读取 `GF_DEBUG` 环境变量的值,当不存在时则去命令行中的 `gf.debug` 选项。 + +需要注意的是参数命名转换规则: + +- 环境变量会将名称转换为大写,名称中的 `.` 字符转换为 `_` 字符。 +- 命令行中会将名称转换为小写,名称中的 `_` 字符转换为 `.` 字符。 + +## `All` + +```go +func All() []string +``` + +该方法表示返回环境变量中的字符串,并且以\` `key=value` \`的形式返回。 + +## `Map` + +```go +func Map() map[string]string +``` + +该方法表示返回环境变量中的字符串,并且以\` `map` \`的形式返回。 + +## `Get` + +```go +func Get(key string, def ...interface{}) *gvar.Var +``` + +该方法用于创建返回一个泛型类型的环境变量,如果给定的 `key` 不存在则返回一个默认的泛型类型的环境变量。 + +## `Set` + +```go +func Set(key, value string) error +``` + +该方法是通过存放 `key` 和 `value` 的环境变量,如果有报错则返回一个 `Error` 类型。 + +## `SetMap` + +```go +func SetMap(m map[string]string) error +``` + +该方法通过 `map` 类型的参数存放环境变量。 + +## `Contains` + +```go +func Contains(key string) bool +``` + +该方法通过检查环境变量中是否存在 `key`。 + +## `Remove` + +```go +func Remove(key ...string) error +``` + +该方法可以删除一个或者多个环境变量。 + +## `Build` + +```go +func Build(m map[string]string) []string +``` + +该方法将 `map` 的参数以数组的形式构建并且返回。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" new file mode 100644 index 00000000000..1d9200d9a08 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\263\273\347\273\237\347\233\270\345\205\263.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/os' +title: '系统相关' +sidebar_position: 1 +hide_title: true +keywords: [系统管理,操作系统,GoFrame,GoFrame框架,系统组件,平台支持,系统工具,软件开发,技术文档,框架使用] +description: 'GoFrame框架中与操作系统相关的模块,包括如何在不同平台上管理和使用操作系统功能,提供系统级别的工具和组件,帮助开发者更有效地进行软件开发。' +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" new file mode 100644 index 00000000000..ee447f719a9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\347\274\223\345\255\230\347\256\241\347\220\206-gcache.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcache' +title: '缓存管理-gcache' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcache,缓存管理,Web开发,后端框架,开源,高性能,Go语言,组件] +description: '通过GoFrame框架中的gcache模块实现缓存管理。了解如何在您的Web开发项目中使用该模块以提高性能和效率,并获取有关缓存策略、配置和最佳实践的深入指导。' +--- + +缓存管理由 `gcache` 模块实现,具体请参考 [缓存管理](../../核心组件/缓存管理/缓存管理.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" new file mode 100644 index 00000000000..6e1d6b5ded5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\265\204\346\272\220\347\256\241\347\220\206-gres.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gres' +title: '资源管理-gres' +sidebar_position: 10 +hide_title: true +description: 'GoFrame框架中的资源管理技巧,详细讨论了gres模块的使用。通过参考相关核心组件文档,用户可以优化站点资源管理,提高网站整体性能和表现。' +keywords: [资源管理,GoFrame,gres模块,组件文档,网站表现,站点资源,核心组件,模块实现,框架技术,性能优化] +--- + +资源管理由 `gres` 模块实现,具体请参考 [资源管理](../../核心组件/资源管理/资源管理.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" new file mode 100644 index 00000000000..f93f6f10932 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-gproc.md" @@ -0,0 +1,39 @@ +--- +slug: '/docs/components/os-gproc' +title: '进程管理-gproc' +sidebar_position: 14 +hide_title: true +keywords: [进程管理,进程间通信,本地socket,gproc模块,GoFrame,Shell指令,异步执行,子进程管理,gogf,进程资源] +description: '通过GoFrame框架的gproc模块实现进程管理和进程间通信的方法。gproc使用本地socket机制进行通信,提供了多种接口如Shell、ShellExec、ShellRun以不同方式执行Shell命令,可以借助goroutine实现异步执行。在本文档中,您将了解到如何使用Manager对象进行多子进程管理,以及如何获取和控制特定的进程资源。' +--- + + +## 基本介绍 +进程管理以及进程间的通信是通过 `gproc` 模块实现的,其中进程间通信采用的是本地socket通信机制。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/os/gproc" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/os/gproc](https://pkg.go.dev/github.com/gogf/gf/v2/os/gproc) + +**简要说明:** + +1. `Manager` 对象为进程管理对象,可以同时管理多个子进程(当前执行进程为父进程); +2. `Process` 为进程对象,表示特定执行或者获取的一个进程资源; +3. 我们可以通过 `Shell`、 `ShellExec`、 `ShellRun` 来执行Shell指令: + - `Shell` 表示一个原生的Shell指令执行方式,带自定义的输入和输出控制; + - `ShellExec` 执行命令后将会返回输出的结果内容; + - `ShellRun` 执行命令后将会直接将返回内容输出到标准输出; + - 我们可以使用 `goroutine` 来实现异步的执行,如: `go ShellRun(...)` 等等; + + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" new file mode 100644 index 00000000000..135ca7fe041 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\344\277\241\345\217\267\347\233\221\345\220\254.md" @@ -0,0 +1,156 @@ +--- +slug: '/docs/components/os-gproc-signal' +title: '进程管理-信号监听' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,信号监听,进程管理,gproc组件,GoFrame框架,信号处理,程序平滑退出,Go语言,信号回调,AddSigHandler] +description: '使用GoFrame框架中的gproc组件实现信号监听和处理,从而解决多个组件冗余的信号处理逻辑及程序无法平滑退出的问题。通过统一的信号注册和回调处理,确保各组件能够有效接收到退出信号并进行析构,使信号处理逻辑更加严谨。' +--- + +## 基本介绍 + +`gproc` 组件提供了统一的信号监听和回调处理功能,目的是解决在程序中多个不同组件冗余的信号处理逻辑,以及接收退出信号后无法平滑析构的痛点。在没有统一退出信号监听的组件下,当多个组件通过 `goroutine` 异步监听信号,主 `goroutine` 接收到退出信号往往会直接退出,或者等待一段无法预测的时间退出,造成程序其实无法平滑退出,可能引起一些不可预料的问题。 `gproc` 通过统一的信号注册和回调处理,使得各个组件能够有效地接收到退出信号并做相应析构处理,使得程序的信号处理逻辑更加严谨。 + +相关方法: + +```go +// AddSigHandler adds custom signal handler for custom one or more signals. +func AddSigHandler(handler SigHandler, signals ...os.Signal) + +// AddSigHandlerShutdown adds custom signal handler for shutdown signals: +// syscall.SIGINT, +// syscall.SIGQUIT, +// syscall.SIGKILL, +// syscall.SIGTERM, +// syscall.SIGABRT. +func AddSigHandlerShutdown(handler ...SigHandler) + +// Listen blocks and does signal listening and handling. +func Listen() +``` + +简要介绍: + +- `AddSigHanlder` 方法用于添加对指定信号的监听和对应回调函数注册。 +- `AddSigHandlerShutdown` 方法用于添加对常见进程退出信号的监听和对应回调函数注册,可以注册多个 `SigHandler`。 +- `Listen` 方法用于阻塞监听信号并自动执行回调函数调用。 + +我们来看两个示例。 + +## 示例1,使用标准库信号监听 + +使用标准库信号监听机制的常见代码逻辑如下: + +```go +package main + +import ( + "fmt" + "os" + "os/signal" + "syscall" + "time" +) + +func signalHandlerForMQ() { + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") + return + } +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + go signalHandlerForMQ() + + var ( + sig os.Signal + receivedChan = make(chan os.Signal) + ) + signal.Notify( + receivedChan, + syscall.SIGINT, + syscall.SIGQUIT, + syscall.SIGKILL, + syscall.SIGTERM, + syscall.SIGABRT, + ) + for { + sig = <-receivedChan + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) + return + } +} +``` + +我们通过 `go run` 命令来执行一下,随后通过 `Ctrl+C` 快捷键退出( `Mac` 用户通过 `Command+C`)。 + +```bash +$ go run signal_handler.go +Process start, pid: 21928 +^CMainProcess is shutting down due to signal: interrupt +MQ is shutting down due to signal: interrupt +``` + +可以看到,好可惜,那个 `MQ` 的 `goroutine` 还没完全退出进程即被强行关闭。 + +## 示例2,使用 `gproc` 信号监听 + +使用 `gproc` 组件改进后的信号监听机制如下: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/os/gproc" + "os" + "time" +) + +func signalHandlerForMQ(sig os.Signal) { + fmt.Println("MQ is shutting down due to signal:", sig.String()) + time.Sleep(time.Second) + fmt.Println("MQ is shut down smoothly") +} + +func signalHandlerForMain(sig os.Signal) { + fmt.Println("MainProcess is shutting down due to signal:", sig.String()) +} + +func main() { + fmt.Println("Process start, pid:", os.Getpid()) + gproc.AddSigHandlerShutdown( + signalHandlerForMQ, + signalHandlerForMain, + ) + gproc.Listen() +} +``` + +我们通过 `go run` 命令来执行一下,随后通过 `Ctrl+C` 快捷键退出( `Mac` 用户通过 `Command+C`)。 + +```bash +$ go run signal_handler_gproc.go +Process start, pid: 22876 +^CMQ is shutting down due to signal: interrupt +MainProcess is shutting down due to signal: interrupt +MQ is shut down smoothly +``` + +看到差别了吧!所有的信号监听函数都正常结束后,随后进程平滑退出。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..59a244bd94e --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\345\237\272\346\234\254\344\275\277\347\224\250.md" @@ -0,0 +1,119 @@ +--- +slug: '/docs/components/os-gproc-example' +title: '进程管理-基本使用' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,进程管理,Shell命令,主进程,子进程,gproc,多进程管理,golang,编程示例] +description: '在GoFrame框架下进行进程管理,包括如何执行Shell命令、判断主进程与子进程的关系,以及多进程管理的基本用法。通过示例代码演示了gproc包的使用,如创建子进程、管理现有进程,并在Linux环境中实现对特定进程的监控和控制。' +--- + +## 执行Shell命令 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + r, err := gproc.ShellExec(gctx.New(), `sleep 3; echo "hello gf!";`) + fmt.Println("result:", r) + fmt.Println(err) +} +``` + +执行后,可以看到程序等待了3秒之后,输出结果为: + +``` +result: hello gf! + + +``` + +## 主进程与子进程 + +由 `gproc.Manager` 对象创建的进程都默认带子进程标识,在子进程程序中可以通过 `gproc.IsChild()` 方法来判断自身是否为子进程。 + +```go +package main + +import ( + "os" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + var ctx = gctx.New() + if gproc.IsChild() { + g.Log().Printf(ctx, "%d: Hi, I am child, waiting 3 seconds to die", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 1", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 2", gproc.Pid()) + time.Sleep(time.Second) + g.Log().Printf(ctx, "%d: 3", gproc.Pid()) + } else { + m := gproc.NewManager() + p := m.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Start(ctx) + p.Wait() + g.Log().Printf(ctx, "%d: child died", gproc.Pid()) + } +} +``` + +执行后,终端打印结果如下: + +```html +2018-05-18 14:35:41.360 28285: Hi, I am child, waiting 3 seconds to die +2018-05-18 14:35:42.361 28285: 1 +2018-05-18 14:35:43.361 28285: 2 +2018-05-18 14:35:44.361 28285: 3 +2018-05-18 14:35:44.362 28278: child died +``` + +## 多进程管理 + +`gproc` 除了能够创建子进程,管理子进程之外,也能管理非自身创建的其他进程。 `gproc` 可以同时管理多个进程,这里以单个进程为例来演示对进程的管理功能。 + +1. 我们使用 `gedit` 软件(Linux下常用的文本编辑器)随意打开一个文件,在进程当中我们看到该gedit的进程ID为 `28536` + +```shell + $ ps aux | grep gedit + john 28536 3.6 0.6 946208 56412 ? Sl 14:39 0:00 gedit /home/john/Documents/text +``` + +2. 我们的程序如下: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + pid := 28536 + m := gproc.NewManager() + m.AddProcess(pid) + m.KillAll() + m.WaitAll() + fmt.Printf("%d was killed\n", pid) +} +``` + +执行后, `gedit` 被关闭,终端输出信息为: + +``` +28536 was killed +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" new file mode 100644 index 00000000000..f2b7404ce61 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\350\277\233\347\250\213\351\200\232\344\277\241.md" @@ -0,0 +1,84 @@ +--- +slug: '/docs/components/os-gproc-communication-between-processes' +title: '进程管理-进程通信' +sidebar_position: 1 +hide_title: true +keywords: [进程管理,进程通信,GoFrame,gproc,Socket通信,共享内存,信号,管道,共享文件,GoFrame框架] +description: 'GoFrame框架中的gproc组件用于进程通信的机制,包括常见的进程通信方式如信号、管道、共享内存等。重点说明了gproc通过Socket实现稳定和通用的进程通信方式,解析了使用Send和Receive方法进行消息传递的基本使用示例。' +--- +:::danger +目前 `gproc` 组件提供的进程通信特性属于实验性特性! +::: +> 不要通过共享内存来通信,而应该通过通信来共享内存。 + +常见的进程通信方式有 `5` 种: `管道/信号/共享内存/共享文件/Socket`,这几种方式通常有着各自比较擅长的使用场景。 + +- `信号`:信号常用在 `*nix` 系统中,跨平台性比较差,信息传递方式和内容单一。 +- `管道`:包括 **普通管道** 与 **命名管道**,这种方式在子父进程通信场景比较常用,跨不同关系进程之间通信不太适用。 +- `共享内存/共享文件`:按照并发架构的设计来讲,我们尽可能地少用 `锁机制`,包括共享内存(内存锁)/共享文件(文件锁)其实都是需要依靠锁机制才能保证数据流的正确性,因为锁机制带来的维护复杂度往往会比其带来的收益更高。 + +`gproc` 实现的进程通信主要机制采用的是 `Socket`,这种机制优点是功能比较稳定、使用场景比较通用。 + +`gproc` 的进程通信API非常简便,只需通过以下两个方法实现: + +```go +func Send(pid int, data []byte) error +func Receive() *Msg +``` + +我们通过 `Send` 方法向指定的进程发送数据(每调用一次相当于发送一条消息),在指定的进程中可以通过 `Receive` 方法获得数据。其中, `Receive` 方法提供了类似消息队列的形式来收取其他进程传递的数据,当队列为空时,该方法将会 `阻塞` 等待。 + +我们来看一个进程间通信的基本使用示例: + +```go +package main + +import ( + "context" + "fmt" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/v2/os/gtimer" + "os" + "time" +) + +var ( + ctx = gctx.New() +) + +func main() { + fmt.Printf("%d: I am child? %v\n", gproc.Pid(), gproc.IsChild()) + if gproc.IsChild() { + gtimer.SetInterval(ctx, time.Second, func(ctx context.Context) { + err := gproc.Send(gproc.PPid(), []byte(gtime.Datetime())) + if err != nil { + return + } + }) + select {} + } else { + m := gproc.NewManager() + p := m.NewProcess(os.Args[0], os.Args, os.Environ()) + p.Start(ctx) + for { + msg := gproc.Receive() + fmt.Printf("receive from %d, data: %s\n", msg.SenderPid, string(msg.Data)) + } + } +} +``` + +该示例中,我们的主进程启动时创建了一个子进程,该子进程每隔1秒钟向主进程发送当前的时间,主进程收取到子进程发送的参数后输出到终端上。执行后,终端输出的内容如下: + +``` +29978: I am child? false +29984: I am child? true +receive from 29984, data: 2018-05-18 15:01:00 +receive from 29984, data: 2018-05-18 15:01:01 +receive from 29984, data: 2018-05-18 15:01:02 +receive from 29984, data: 2018-05-18 15:01:03 +receive from 29984, data: 2018-05-18 15:01:04 +... +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" new file mode 100644 index 00000000000..7d279cd0055 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\350\277\233\347\250\213\347\256\241\347\220\206-gproc/\350\277\233\347\250\213\347\256\241\347\220\206-\351\223\276\350\267\257\350\267\237\350\270\252.md" @@ -0,0 +1,66 @@ +--- +slug: '/docs/components/os-gproc-tracing' +title: '进程管理-链路跟踪' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,进程管理,链路跟踪,OpenTelemetry,跨进程,主进程,子进程,gproc,os-gproc-tracing] +description: '使用GoFrame框架进行进程管理和链路跟踪的方法。通过使用OpenTelemetry规范,支持跨进程的链路跟踪特性,非常适用于临时运行的进程。示例代码展示了如何在主进程中启动子进程并进行链路信息传递,展示了GoFrame框架在进程管理中的强大功能。' +--- + +## 基本介绍 + +进程管理组件支持跨进程的链路跟踪特性,特别是对于一些临时运行的进程特别有用。框架整体的链路跟踪都是采用的 `OpenTelemetry` 规范。 + +## 使用示例 + +### 主进程 + +`main.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/gproc" +) + +func main() { + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is main process`) + if err := gproc.ShellRun(ctx, `go run sub.go`); err != nil { + panic(err) + } +} +``` + +### 子进程 + +`sub.go` + +```go +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" +) + +func main() { + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is sub process`) +} +``` + +### 执行结果 + +执行后,终端输出如下: + +```bash +$ go run main.go +2022-06-21 20:35:06.196 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is main process +2022-06-21 20:35:07.482 [DEBU] {00698a61e2a2fa1661da5d7993d72e8c} this is sub process +``` + +可以看到,链路信息已经自动传递给了子进程。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" new file mode 100644 index 00000000000..738f7c6a821 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\263\273\347\273\237\347\233\270\345\205\263/\351\205\215\347\275\256\347\256\241\347\220\206-gcfg.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/os-gcfg' +title: '配置管理-gcfg' +sidebar_position: 11 +hide_title: true +keywords: [配置管理,GoFrame,gcfg模块,核心组件,应用配置,GoFrame框架,灵活配置,开发框架,系统配置,配置文件] +description: '使用GoFrame框架中的gcfg模块进行配置管理。gcfg模块支持灵活的应用配置和系统配置,帮助开发者更加高效地管理和组织配置文件,确保应用程序的稳定性与灵活性。' +--- + +配置管理由 `gcfg` 模块实现,具体请参考 [配置管理](../../核心组件/配置管理/配置管理.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" new file mode 100644 index 00000000000..d6d113fad02 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\273\204\344\273\266\345\210\227\350\241\250.md" @@ -0,0 +1,26 @@ +--- +slug: '/docs/components' +title: '组件列表' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,核心模块,社区模块,gf主仓库,模块列表,轻量级框架,模块维护,gogf空间,模块文档] +description: '此文档介绍了GoFrame框架中的模块列表,包括核心模块和社区模块。核心模块由gf主仓库维护,简单易用,社区模块由社区贡献,存放于gogf空间下。详细信息请参阅源代码README文件和相关的核心模块文档。' +--- + +模块列表包含绝大部分框架的核心模块以及社区模块介绍文档。 + +## 核心模块 + +`GoFrame` 提供了一些基础的、常用的模块,简单、易用和轻量级,并保持极少的外部依赖,这些模块由 `gf` 主仓库细致维护。 + +## 社区模块 + +社区模块主要由社区贡献并维护,大部分也是由 `gf` 主仓库的贡献者提供及维护,存放于 `gogf` 空间下,与 `gf` 主仓库处于同一级别。有的社区模块是从 `gf` 主仓库中剥离出来单独维护的模块,这些模块并不是特别常用,或者对外部依赖较重。 + +目前社区模块的文档未收录,社区模块使用方法请具体查看源码 `README.MD` 文件,由于社区模块大都是核心模块的具体接口具体,因此也可以查看相关联的核心模块文档。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" new file mode 100644 index 00000000000..084c63b7231 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/BASE64\347\274\226\350\247\243\347\240\201-gbase64.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gbase64' +title: 'BASE64编解码-gbase64' +sidebar_position: 2 +hide_title: true +keywords: [BASE64,GoFrame,编码,解码,gbase64,GoFrame框架,接口文档,编码解析,Go语言,软件开发] +description: '使用BASE64编解码功能,基于GoFrame框架的gbase64包,提供编码和解码方法。通过GitHub链接和接口文档了解更多,适用于Go语言开发者的实用组件。' +--- + +`BASE64` 编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gbase64" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbase64](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbase64) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" new file mode 100644 index 00000000000..60931b452b7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/HTML\347\274\226\350\247\243\347\240\201-ghtml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-ghtml' +title: 'HTML编解码-ghtml' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,HTML编解码,ghtml,Go语言,编码解析,接口文档,Web开发,软件开发,编程] +description: '在使用GoFrame框架构建的项目中进行HTML编码和解析。通过引入相关包,可以轻松对HTML内容进行处理。文末提供了官方接口文档的链接,进一步帮助开发者理解和使用。' +--- + +HTML编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/ghtml" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghtml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghtml) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" new file mode 100644 index 00000000000..1e307cb90dd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/INI\347\274\226\350\247\243\347\240\201-gini.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gini' +title: 'INI编解码-gini' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,INI,数据格式,编码,解析,gini,接口文档,使用方式,pkg.go.dev] +description: '使用GoFrame框架进行INI数据格式的编码和解析的方法。详细解释了如何通过GoFrame框架中的gini包进行INI的编解码,并提供了相关的接口文档链接,方便开发者查阅。' +--- + +INI数据格式编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gini" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gini](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gini) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" new file mode 100644 index 00000000000..3c3d394aea6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/TOML\347\274\226\350\247\243\347\240\201-gtoml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gtoml' +title: 'TOML编解码-gtoml' +sidebar_position: 9 +hide_title: true +keywords: [TOML,编解码,gtoml,GoFrame,数据格式,GoFrame框架,接口文档,编码解析,gogf,导入] +description: '使用GoFrame框架进行TOML数据格式的编码解析的方法。通过引入gogf的gtoml包,可以方便地对TOML格式的数据进行编解码操作。文章还提供了接口文档的链接,帮助开发者更深入了解gtoml的使用细节。' +--- + +`TOML` 数据格式编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gtoml" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gtoml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gtoml) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" new file mode 100644 index 00000000000..e2a891322f9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/URL\347\274\226\350\247\243\347\240\201-gurl.md" @@ -0,0 +1,132 @@ +--- +slug: '/docs/components/encoding-gurl' +title: 'URL编解码-gurl' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,URL编解码,gurl,URL参数构建,URL参数编码,URL参数解码,URL解析,编码解析,Go语言] +description: '使用GoFrame框架中的gurl包进行URL编解码操作,包括如何构建URL参数、对URL参数进行编码和解码,以及如何解析URL以获取其不同组件。这些功能对于在Go语言中进行网络编程和数据传输时非常有用,适合有类似需求的开发者参考。' +--- + +`URL` 编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gurl" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gurl](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gurl) + +## `URL` 参数构建 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "net/url" +) + +func main() { + // 构建url参数 + values := url.Values{} + values.Add("name", "gopher") + values.Add("limit", "20") + values.Add("page", "7") + + // 生成URL编码查询字符串 limit=20&name=gopher&page=7 + urlStr := gurl.BuildQuery(values) + fmt.Println(urlStr) +} +``` + +执行后,输出结果为: + +``` +limit=20&name=gopher&page=7 +``` + +## `URL` 参数编码与解码 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "log" +) + +func main() { + // 编码对字符串进行转义,以便可以将其安全地放置在URL查询中。 + encodeStr := gurl.Encode("limit=20&name=gopher&page=7") + fmt.Println(encodeStr) + + // 进行URL解码 + decodeStr, err := gurl.Decode("limit%3D20%26name%3Dgopher%26page%3D7") + if err != nil { + log.Fatal(err) + } + fmt.Println(decodeStr) +} +``` + +执行后,输出结果为: + +``` +limit%3D20%26name%3Dgopher%26page%3D7 +limit=20&name=gopher&page=7 +``` + +## 解析 `URL` + +`component` 参数值可选项: + +| 参数值 | 说明 | +| --- | --- | +| -1 | all | +| 1 | scheme | +| 2 | host | +| 4 | port | +| 8 | user | +| 16 | pass | +| 32 | path | +| 64 | query | +| 128 | fragment | + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gurl" + "log" +) + +func main() { + // 解析URL并返回其组件 + data, err := gurl.ParseURL("http://127.0.0.1:8199/goods?limit=20&name=gopher&page=7", -1) + if err != nil { + log.Fatal(err) + } + fmt.Println(data) + fmt.Println(data["host"]) + fmt.Println(data["query"]) + fmt.Println(data["path"]) + fmt.Println(data["scheme"]) + fmt.Println(data["fragment"]) +} +``` + +执行后,输出结果为: + +``` +map[fragment: host:127.0.0.1 pass: path:/goods port:8199 query:limit=20&name=gopher&page=7 scheme:http user:] +127.0.0.1 +limit=20&name=gopher&page=7 +/goods +http +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" new file mode 100644 index 00000000000..680c11e61a7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/XML\347\274\226\350\247\243\347\240\201-gxml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gxml' +title: 'XML编解码-gxml' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,XML,XML编解码,数据格式,gxml,编码解析,Go语言,编程,接口文档] +description: '使用GoFrame框架中的gxml进行XML数据格式的编码和解析。提供了gxml库的基本使用方法以及相关接口文档的链接,帮助开发者在Go语言项目中轻松处理XML数据。具体实现包括import语句与gxml库的调用示例。' +--- + +XML数据格式编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gxml" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gxml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gxml) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" new file mode 100644 index 00000000000..92785209bdb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/YAML\347\274\226\350\247\243\347\240\201-gyaml.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gyaml' +title: 'YAML编解码-gyaml' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,YAML,编码,解码,gyaml,数据格式,Go语言,接口文档,编码解析] +description: 'YAML数据格式的编解码方法,使用GoFrame框架下的gyaml库进行编码解析。通过导入github.com/gogf/gf/v2/encoding/gyaml包,可以方便地处理YAML格式的数据。此外,还提供了接口文档的链接供用户参考。' +--- + +`YAML` 数据格式编码解析。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gyaml" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gyaml](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gyaml) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" new file mode 100644 index 00000000000..5454b413fa6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\344\272\214\350\277\233\345\210\266\347\274\226\350\247\243\347\240\201-gbinary.md" @@ -0,0 +1,182 @@ +--- +slug: '/docs/components/encoding-gbinary' +title: '二进制编解码-gbinary' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gbinary,二进制编解码,数据转换,网络通信,数据编码,解码,整型处理,位操作] +description: 'GoFrame框架中的gbinary包提供了用于二进制数据与各种数据类型之间转换的编解码功能,广泛应用于网络通信和数据文件操作。支持对整型数据进行按位精准处理,并提供了一系列编码和解码接口,以确保数据在不同类型和平台间的高效转换。' +--- + +## 基本介绍 + +`GoFrame` 框架提供了独立的二进制数据操作包 `gbinary`,主要用于各种数据类型与 `[]byte` 二进制类型之间的相互转换;以及针对于整型数据进行精准按位处理的功能。常用于网络通信时数据编码/解码,以及数据文件操作时的编码/解码。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gbinary" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbinary](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gbinary) + +用于二进制数据结构转换处理的接口文档如下: + +```go +func Encode(vs ...interface{}) ([]byte, error) +func EncodeInt(i int) []byte +func EncodeInt8(i int8) []byte +func EncodeInt16(i int16) []byte +func EncodeInt32(i int32) []byte +func EncodeInt64(i int64) []byte +func EncodeUint(i uint) []byte +func EncodeUint8(i uint8) []byte +func EncodeUint16(i uint16) []byte +func EncodeUint32(i uint32) []byte +func EncodeUint64(i uint64) []byte +func EncodeBool(b bool) []byte +func EncodeFloat32(f float32) []byte +func EncodeFloat64(f float64) []byte +func EncodeString(s string) []byte + +func Decode(b []byte, vs ...interface{}) error +func DecodeToInt(b []byte) int +func DecodeToInt8(b []byte) int8 +func DecodeToInt16(b []byte) int16 +func DecodeToInt32(b []byte) int32 +func DecodeToInt64(b []byte) int64 +func DecodeToUint(b []byte) uint +func DecodeToUint8(b []byte) uint8 +func DecodeToUint16(b []byte) uint16 +func DecodeToUint32(b []byte) uint32 +func DecodeToUint64(b []byte) uint64 +func DecodeToBool(b []byte) bool +func DecodeToFloat32(b []byte) float32 +func DecodeToFloat64(b []byte) float64 +func DecodeToString(b []byte) string +``` + +支持按位处理的接口文档如下: + +```go +func EncodeBits(bits []Bit, i int, l int) []Bit +func EncodeBitsWithUint(bits []Bit, ui uint, l int) []Bit +func EncodeBitsToBytes(bits []Bit) []byte +func DecodeBits(bits []Bit) uint +func DecodeBitsToUint(bits []Bit) uint +func DecodeBytesToBits(bs []byte) []Bit +``` + +其中的Bit类型表示一个二进制数字(0或1),其定义如下: + +```go +type Bit int8 +``` + +## 使用示例 + +我们来看一个比较完整的二进制操作示例,基本演示了绝大部分的二进制转换操作。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/encoding/gbinary" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/os/glog" +) + +func main() { + // 使用gbinary.Encoded对基本数据类型进行二进制打包 + if buffer := gbinary.Encode(18, 300, 1.01); buffer != nil { + // glog.Error(err) + } else { + fmt.Println(buffer) + } + + // 使用gbinary.Decode对整形二进制解包,注意第二个及其后参数为字长确定的整形变量的指针地址,字长确定的类型, + // 例如:int8/16/32/64、uint8/16/32/64、float32/64 + // 这里的1.01默认为float64类型(64位系统下) + if buffer := gbinary.Encode(18, 300, 1.01); buffer != nil { + //glog.Error(err) + } else { + var i1 int8 + var i2 int16 + var f3 float64 + if err := gbinary.Decode(buffer, &i1, &i2, &f3); err != nil { + glog.Error(gctx.New(), err) + } else { + fmt.Println(i1, i2, f3) + } + } + + // 编码/解析 int,自动识别变量长度 + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(1))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(300))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(70000))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(2000000000))) + fmt.Println(gbinary.DecodeToInt(gbinary.EncodeInt(500000000000))) + + // 编码/解析 uint,自动识别变量长度 + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(1))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(300))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(70000))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(2000000000))) + fmt.Println(gbinary.DecodeToUint(gbinary.EncodeUint(500000000000))) + + // 编码/解析 int8/16/32/64 + fmt.Println(gbinary.DecodeToInt8(gbinary.EncodeInt8(int8(100)))) + fmt.Println(gbinary.DecodeToInt16(gbinary.EncodeInt16(int16(100)))) + fmt.Println(gbinary.DecodeToInt32(gbinary.EncodeInt32(int32(100)))) + fmt.Println(gbinary.DecodeToInt64(gbinary.EncodeInt64(int64(100)))) + + // 编码/解析 uint8/16/32/64 + fmt.Println(gbinary.DecodeToUint8(gbinary.EncodeUint8(uint8(100)))) + fmt.Println(gbinary.DecodeToUint16(gbinary.EncodeUint16(uint16(100)))) + fmt.Println(gbinary.DecodeToUint32(gbinary.EncodeUint32(uint32(100)))) + fmt.Println(gbinary.DecodeToUint64(gbinary.EncodeUint64(uint64(100)))) + + // 编码/解析 string + fmt.Println(gbinary.DecodeToString(gbinary.EncodeString("I'm string!"))) +} +``` + +以上程序执行结果为: + +``` +[18 44 1 41 92 143 194 245 40 240 63] +18 300 1.01 +1 +300 +70000 +2000000000 +500000000000 +1 +300 +70000 +2000000000 +500000000000 +100 +100 +100 +100 +100 +100 +100 +100 +I'm string! +``` + +1. **编码** + +`gbinary.Encode` 方法是一个非常强大灵活的方法,可以将所有的基本类型转换为二进制类型 `([ ]byte`)。在 `gbinary.Encode` 方法内部,会自动对变量进行长度计算,采用最小二进制长度来存放该变量的二进制值。例如,针对 `int` 类型值为 `1` 的变量, `gbinary.Encode` 将只会用 `1` 个 `byte` 来存储,而 `int` 类型值为 `300` 的变量,将会使用 `2` 个 `byte` 来存储,尽量减少二进制结果的存储空间。因此,在解析的时候要非常注意 `[ ]byte` 的长度,建议能够确定变量长度的地方,在进行二进制编码/解码时,尽量采用形如 `int8/16/32/64` 的定长基本类型来存储变量,这样解析的时候也能够采用对应的变量形式进行解析,不易产生错误。 + +`gbinary` 包也提供了一系列 `gbinary.Encode*` 的方法,用于将基本数据类型转换为二进制。其中, `gbinary.EncodeInt/gbinary.EncodeUint` 也是会在内部自动识别变量值大小,返回不定长度的 `[ ]byte` 值,长度范围 `1/2/4/8`。 + +2. **解码** + +在二进制类型的解析操作中,二进制的长度( `[ ]byte` 的长度)是非常重要的,只有给定正确的长度才能执行正确的解析,因此 `gbinary.Decode` 方法给定的变量长度必须为确定长度类型的变量,例如: `int8/16/32/64`、 `uint8/16/32/64`、 `float32/64`,而如果给定的第二个变量地址对应的变量类型为 `int/uint`,无法确定长度,因此解析会失败。 + +此外, `gbinary` 包也提供了一系列 `gbinary.DecodeTo*` 的方法,用于将二进制转换为特定的数据类型。其中, `gbinary.DecodeToInt/gbinary.DecodeToUint` 方法会对二进制长度进行自动识别解析,支持的二进制参数长度范围 `1-8`。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" new file mode 100644 index 00000000000..d840c4933ec --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\216\213\347\274\251\350\247\243\345\216\213-gcompress.md" @@ -0,0 +1,20 @@ +--- +slug: '/docs/components/encoding-gcompress' +title: '压缩/解压-gcompress' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcompress,压缩,解压,二进制数据,Zlib,GZip,接口文档,GoFrame编码] +description: '二进制数据的压缩和解压缩功能,特别是Zlib和GZip算法的使用方法。通过GoFrame框架,用户可以轻松实现数据压缩解压,具体实现请参考接口文档。本页面提供详细的调用示例和相关技术文档链接,帮助开发者快速上手。' +--- + +二进制数据压缩/解压,支持 `Zlib`/ `GZip` 算法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gcompress" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcompress](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcompress) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" new file mode 100644 index 00000000000..85795aa6a30 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\345\255\227\347\254\246\351\233\206\350\275\254\346\215\242-gcharset.md" @@ -0,0 +1,61 @@ +--- +slug: '/docs/components/encoding-gcharset' +title: '字符集转换-gcharset' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,字符编码转换,gcharset,GBK,UTF-8,字符集转换,编码转换模块,中文字符集,程序示例] +description: 'GoFrame框架中的字符编码转换模块gcharset,支持如GBK、UTF-8等常见字符集的转换,为开发者提供了灵活的字符集兼容性。通过导入相关包,开发者可以实现不同字符集之间的转换,从而满足多语言、多地区用户的需求,提升应用程序的国际化与本地化能力。' +--- + +`GoFrame` 框架提供了强大的字符编码转换模块 `gcharset`,支持常见字符集的相互转换。 + +支持的字符集: + +| 编码 | 字符集 | +| --- | --- | +| **中文** | `GBK/GB18030/GB2312/Big5` | +| **日文** | `EUCJP/ISO2022JP/ShiftJIS` | +| **韩文** | `EUCKR` | +| **Unicode** | `UTF-8/UTF-16/UTF-16BE/UTF-16LE` | +| **其他编码** | `macintosh/IBM*/Windows*/ISO-*` | + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gcharset" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcharset](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gcharset) + +**使用示例**: + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/encoding/gcharset" +) + +func main() { + var ( + src = "~{;(F#,6@WCN^O`GW!#" + srcCharset = "GB2312" + dstCharset = "UTF-8" + ) + str, err := gcharset.Convert(dstCharset, srcCharset, src) + if err != nil { + panic(err) + } + fmt.Println(str) +} +``` + +执行后,终端输出结果为: + +``` +花间一壶酒,独酌无相亲。 +``` diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" new file mode 100644 index 00000000000..1cf9fcde52f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\273\217\345\205\270\345\223\210\345\270\214\345\207\275\346\225\260-ghash.md" @@ -0,0 +1,110 @@ +--- +slug: '/docs/components/encoding-ghash' +title: '经典哈希函数-ghash' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,哈希函数,Go语言,ghash,编码,基准测试,重复测试,uint32,uint64] +description: '经典哈希函数在Go语言中的实现,并提供了用于uint32及uint64类型的哈希函数的使用方式。通过GoFrame框架,用户可以更高效地实现哈希功能,文档中包括了详细的接口文档和基准测试结果,帮助用户优化和理解编码性能。同时,通过简单的重复性测试,展示了不同哈希函数的特性和表现。' +--- + +## 基本介绍 + +常用经典哈希函数Go语言实现,提供 `uint32` 及 `uint64` 类型的哈希函数。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/ghash" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghash](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/ghash) + +## 基准测试 + +``` +goos: darwin +goarch: amd64 +pkg: github.com/gogf/gf/v2/encoding/ghash +cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz +Benchmark_BKDR +Benchmark_BKDR-12 39315165 26.88 ns/op +Benchmark_BKDR64 +Benchmark_BKDR64-12 62891215 22.61 ns/op +Benchmark_SDBM +Benchmark_SDBM-12 49689925 25.40 ns/op +Benchmark_SDBM64 +Benchmark_SDBM64-12 48860472 24.38 ns/op +Benchmark_RS +Benchmark_RS-12 39463418 25.52 ns/op +Benchmark_RS64 +Benchmark_RS64-12 53318370 19.45 ns/op +Benchmark_JS +Benchmark_JS-12 53751033 23.20 ns/op +Benchmark_JS64 +Benchmark_JS64-12 62440287 19.25 ns/op +Benchmark_PJW +Benchmark_PJW-12 42439626 27.85 ns/op +Benchmark_PJW64 +Benchmark_PJW64-12 37491696 33.28 ns/op +Benchmark_ELF +Benchmark_ELF-12 38034584 31.74 ns/op +Benchmark_ELF64 +Benchmark_ELF64-12 44047201 27.58 ns/op +Benchmark_DJB +Benchmark_DJB-12 46994352 22.60 ns/op +Benchmark_DJB64 +Benchmark_DJB64-12 61980186 19.19 ns/op +Benchmark_AP +Benchmark_AP-12 29544234 40.58 ns/op +Benchmark_AP64 +Benchmark_AP64-12 28123783 42.48 ns/op +``` + +## 重复测试 + +测试结果与测试内容有关联性和随机性,我这里通过 `uint64` 数值的范围遍历来进行简单的重复性测试,本身不够严谨,因此仅供趣味性参考。 + +```go +package main + +import ( + "encoding/binary" + "fmt" + "math" + + "github.com/gogf/gf/v2/encoding/ghash" +) + +func main() { + var ( + m = make(map[uint64]struct{}) + b = make([]byte, 8) + ok bool + hash uint64 + ) + for i := uint64(0); i < math.MaxUint64; i++ { + binary.LittleEndian.PutUint64(b, i) + hash = ghash.PJW64(b) + if _, ok = m[hash]; ok { + fmt.Println("repeated found:", i) + break + } + m[hash] = struct{}{} + } +} +``` + +测试结果如下: + +| 哈希函数 | 重复位置 | 备注 | +| --- | --- | --- | +| `AP64` | `8388640` | | +| `BKDR64` | `33536` | | +| `DJB64` | `8448` | | +| `ELF64` | `4096` | | +| `JS64` | `734` | | +| `PJW64` | `2` | | +| `RS64` | - | 32G Memory OOM | +| `SDBM64` | - | 32G Memory OOM | \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" new file mode 100644 index 00000000000..e5ea1be41b0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\347\274\226\347\240\201\350\247\243\347\240\201.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/encoding' +title: '编码解码' +sidebar_position: 3 +hide_title: true +keywords: [编码,解码,数据转换,信息处理,编程技术,字符编码,GoFrame,GoFrame框架,编码标准,数据压缩] +description: '编码和解码的基本概念及应用,探讨如何利用GoFrame框架实现高效的数据转换与信息处理。涵盖了各种编码标准及其在编程技术中的实际应用,帮助开发者掌握字符编码和数据压缩的技巧。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" new file mode 100644 index 00000000000..6d56a094c38 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-FAQ.md" @@ -0,0 +1,62 @@ +--- +slug: '/docs/components/encoding-gjson-faq' +title: '通用编解码-FAQ' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,GoFrame框架,JSON,编码,解码,大数字精度,gjson,Go语言,FAQ,问题解决] +description: '在使用GoFrame框架处理JSON数据时可能遇到的大数字精度丢失问题,并提供了具体的解决方案示例代码。通过调整gjson选项,可以避免精度丢失,确保数据的准确性。同时,文中还提供了相关链接供进一步参考。' +--- + +## JSON中的大数字精度丢失问题 + +### 问题描述 + +```go +package main + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + str := `{"Id":1492404095703580672,"Name":"Jason"}` + strJson := gjson.New(str) + g.Dump(strJson) +} +``` + +执行后输出为: + +``` +"{\"Id\":1492404095703580700,\"Name\":\"Jason\"}" +``` + +### 解决方案 + +```go +package main + +import ( + "github.com/gogf/gf/v2/encoding/gjson" + "github.com/gogf/gf/v2/frame/g" +) + +func main() { + str := `{"Id":1492404095703580672,"Name":"Jason"}` + strJson := gjson.NewWithOptions(str, gjson.Options{ + StrNumber: true, + }) + g.Dump(strJson) +} +``` + +执行后输出为: + +``` +"{\"Id\":1492404095703580672,\"Name\":\"Jason\"}" +``` + +### 相关连接 + +- [https://github.com/gogf/gf/issues/1603](https://github.com/gogf/gf/issues/1603) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..a2207846453 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-Struct\350\275\254\346\215\242.md" @@ -0,0 +1,37 @@ +--- +slug: '/docs/components/encoding-gjson-struct-converting' +title: '通用编解码-Struct转换' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame框架,Struct转换,JSON解析,数据格式转换,对象转换,gjson库,编解码,Go语言,数据扫描,用户结构] +description: '使用GoFrame框架中的Struct方法将JSON数据转换为指定的数据格式或对象。示例展示了如何解析JSON数据并使用gjson库将其扫描为自定义用户结构体。这种数据格式转换在处理复杂数据结构时非常有用,特别是在Go语言编程中。' +--- + +## `Struct` 转换 + +`Struct` 方法用于将整个 `Json` 包含的数据内容转换为指定的数据格式或者对象。 + +``` +data := + ` +{ + "count" : 1, + "array" : ["John", "Ming"] +}` +if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) +} else { + type Users struct { + Count int + Array []string + } + users := new(Users) + if err := j.Scan(users); err != nil { + panic(err) + } + fmt.Printf(`%+v`, users) +} + +// Output: +// &{Count:1 Array:[John Ming]} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" new file mode 100644 index 00000000000..25bf1a0d008 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson.md" @@ -0,0 +1,35 @@ +--- +slug: '/docs/components/encoding-gjson' +title: '通用编解码-gjson' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gjson,数据编码,数据解析,数据格式转换,JSON,XML,动态创建,数据层级检索] +description: 'GoFrame框架中的gjson模块,它提供了强大的数据编码和解析功能,支持多种数据格式的相互转换,包括JSON、XML、INI、YAML、TOML等。gjson模块特别适合需要进行数据层级检索和动态创建或修改数据对象的场景,同时它还支持运行时的数据修改功能,是构建网站时的理想选择。' +--- + +`gjson` 模块实现了强大的数据编码/解析功能,支持数据层级检索、动态创建修改 `Json` 对象,并支持常见数据格式的解析和转换等特点。 + +特点: + +1. 支持数据层级检索; +2. 支持运行时数据修改; +3. 支持动态创建层级数据结构,并转换为支持的数据格式; +4. 支持 `JSON`、 `XML`、 `INI`、 `YAML/YML`、 `TOML`、 `PROPERTIES`、 `Struct` 数据格式相互转换; +:::info +需要注意 `gjson` 包支持多种数据格式的读取、写入和转换,不仅仅针对 `json` 格式。 +::: +## **使用方式**: + +```go +import "github.com/gogf/gf/v2/encoding/gjson" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" new file mode 100644 index 00000000000..5b6990ccbfd --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\212\250\346\200\201\345\210\233\345\273\272\344\277\256\346\224\271.md" @@ -0,0 +1,77 @@ +--- +slug: '/docs/components/encoding-gjson-dynamic-creating-and-editing' +title: '通用编解码-动态创建修改' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,gjson,动态创建,动态修改,数据结构,JSON解析,编码,解码,Go语言] +description: '使用GoFrame框架中的gjson进行数据的动态创建和修改。gjson不仅可以灵活地解析和检索未知的数据结构,还能够动态地创建和编辑数据内容。通过具体示例,展示了设置数据、创建数组和修改JSON内容的方法,使数据结构的编码和解析更加灵活方便。' +--- + +`gjson` 除了能够灵活解析、检索未知数据结构内容,还能够动态创建和修改数据结构内容。 + +## 动态创建 + +### 示例1,简单使用 + +```go +func main() { + j := gjson.New(nil) + j.Set("name", "John") + j.Set("score", 99.5) + fmt.Printf( + "Name: %s, Score: %v\n", + j.Get("name").String(), + j.Get("score").Float32(), + ) + fmt.Println(j.MustToJsonString()) + + // Output: + // Name: John, Score: 99.5 + // {"name":"John","score":99.5} +} +``` + +### 示例2,创建数组 + +```go +func main() { + j := gjson.New(nil) + for i := 0; i < 5; i++ { + j.Set(fmt.Sprintf(`%d.id`, i), i) + j.Set(fmt.Sprintf(`%d.name`, i), fmt.Sprintf(`student-%d`, i)) + } + fmt.Println(j.MustToJsonString()) + + // Output: + // [{"id":0,"name":"student-0"},{"id":1,"name":"student-1"},{"id":2,"name":"student-2"},{"id":3,"name":"student-3"},{"id":4,"name":"student-4"}] +} +``` + +## 动态修改 + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 59} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.Set("users.list.1.score", 100) + fmt.Println("John Score:", j.Get("users.list.1.score").Float32()) + fmt.Println(j.MustToJsonString()) + } + // Output: + // John Score: 100 + // {"users":{"count":2,"list":[{"name":"Ming","score":60},{"name":"John","score":100}]}} +} +``` + +`JSON` 数据通过 `gjson` 包读取后,可以通过 `Set` 方法改变内部变量的内容,当然也可以 `新增/删除` 内容,当需要删除内容时,设定的值为 `nil` 即可。 `gjson` 包的数据运行时修改特性非常强大,在该特性的支持下,各种数据结构的编码/解析显得异常的灵活方便。 diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" new file mode 100644 index 00000000000..b21b93ce260 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\257\271\350\261\241\345\210\233\345\273\272.md" @@ -0,0 +1,120 @@ +--- +slug: '/docs/components/encoding-gjson-creation' +title: '通用编解码-对象创建' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gjson,对象创建,JSON,XML,数据格式,结构体对象,Go开发,数据解析] +description: '使用GoFrame框架的gjson模块创建Json对象。支持多种数据格式如JSON、XML等,并提供了New和Load*方法供使用者调用。详细展示了通过JSON、XML和结构体对象创建Json对象的方法,并提供了示例代码帮助开发者理解和应用。' +--- + +`gjson` 模块除了最基础支持的 `JSON` 数据格式创建 `Json` 对象,还支持常用的数据格式内容创建 `Json` 对象。支持的数据格式为: `JSON`, `XML`, `INI`, `YAML`, `TOML`, `PROPERTIES`。此外,也支持直接通过 `struct` 对象创建 `Json` 对象。 + +对象创建常用 `New` 和 `Load*` 方法,更多的方法请查看接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +### 使用 `New` 方法创建 + +#### 通过 `JSON` 数据创建 + +```go +jsonContent := `{"name":"john", "score":"100"}` +j := gjson.New(jsonContent) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` + +#### 通过 `XML` 数据创建 + +```go +jsonContent := `john100` +j := gjson.New(jsonContent) +// Note that there's root node in the XML content. +fmt.Println(j.Get("doc.name")) +fmt.Println(j.Get("doc.score")) +// Output: +// john +// 100 +``` + +#### 通过 `Strcut` 对象创建 + +```go +type Me struct { + Name string `json:"name"` + Score int `json:"score"` +} +me := Me{ + Name: "john", + Score: 100, +} +j := gjson.New(me) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` + +#### 自定义 `Struct` 转换标签 + +```go +type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string +} +me := Me{ + Name: "john", + Score: 100, + Title: "engineer", +} +// The parameter specifies custom priority tags for struct conversion to map, +// multiple tags joined with char ','. +j := gjson.NewWithTag(me, "tag") +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +fmt.Println(j.Get("Title")) +// Output: +// john +// 100 +// engineer +``` + +### 使用 `Load*` 方法创建 + +最常用的是 `Load` 和 `LoadContent` 方法,前者通过文件路径读取,后者通过给定内容创建 `Json` 对象。方法内部会自动识别数据格式,并自动解析转换为 `Json` 对象。 + +#### 通过 `Load` 方法创建 + +1. `JSON` 文件 + +```go + jsonFilePath := gtest.DataPath("json", "data1.json") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) +``` + +2. `XML` 文件 + +```go + jsonFilePath := gtest.DataPath("xml", "data1.xml") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("doc.name")) + fmt.Println(j.Get("doc.score")) +``` + + +#### 通过 `LoadContent` 创建 + +```go +jsonContent := `{"name":"john", "score":"100"}` +j, _ := gjson.LoadContent(jsonContent) +fmt.Println(j.Get("name")) +fmt.Println(j.Get("score")) +// Output: +// john +// 100 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" new file mode 100644 index 00000000000..8a7cd1c2893 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\345\261\202\347\272\247\350\256\277\351\227\256.md" @@ -0,0 +1,129 @@ +--- +slug: '/docs/components/encoding-gjson-nested-visiting' +title: '通用编解码-层级访问' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gjson,数据层级访问,JSON解码,Go语言,编码,层级分隔符,冲突检测,性能优化] +description: '在GoFrame框架中使用gjson进行层级访问,通过灵活的层级分隔符访问未知数据结构。包括设置自定义分隔符号,以及处理键名本身带有层级符号的情况。此外,讨论了涉及Go语言中的引用类型变量修改及其对底层数据的影响。' +--- + +## 层级访问 + +`gjson` 支持对数据内容进行层级检索访问,层级分隔符号默认为” `.`“。该特性使得开发者可以灵活访问未知的数据结构内容变得非常简便。 + +### 示例1,基本使用 + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + fmt.Println("John Score:", j.Get("users.list.1.score")) + } + // Output: + // John Score: 99.5 +} +``` + +可以看到, `gjson.Json` 对象可以通过非常灵活的层级筛选功能( `j.GetFloat32("users.list.1.score")`)检索到对应的变量信息。 + +### 示例2,自定义层级分隔符号 + +```go +func main() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } +}` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.SetSplitChar('#') + fmt.Println("John Score:", j.Get("users#list#1#score")) + } + // Output: + // John Score: 99.5 +} +``` + +可以看到,我们可以通过 `SetSplitChar` 方法设置我们自定义的分隔符号。 + +### 示例3,处理键名本身带有层级符号” `.`“的情况 + +```go +func main() { + data := + `{ + "users" : { + "count" : 100 + }, + "users.count" : 101 + }` + if j, err := gjson.DecodeToJson(data); err != nil { + glog.Error(gctx.New(), err) + } else { + j.SetViolenceCheck(true) + fmt.Println("Users Count:", j.Get("users.count")) + } + // Output: + // Users Count: 101 +} +``` + +运行之后打印出的结果为 `101`。当键名存在” `.`“号时,我们可以通过 `SetViolenceCheck` 设置冲突检测,随后检索优先级将会按照:键名->层级,便并不会引起歧义。但是当冲突检测开关开启时,检索效率将会变低,默认为关闭状态。 + +## 注意事项 + +大家都知道,在 `Golang` 里面, `map/slice` 类型其实是一个”引用类型”(也叫”指针类型”),因此当你对这种类型的变量 键值对/索引项 进行修改时,会同时修改到其对应的底层数据。 + +从效率上考虑, `gjson` 包某些获取方法返回的数据类型为 `map/slice` 时,没有对齐做值拷贝,因此当你对返回的数据进行修改时,会同时修改 `gjson` 对应的底层数据。 + +例如: + +```go +func main() { + jsonContent := `{"map":{"key":"value"}, "slice":[59,90]}` + j, _ := gjson.LoadJson(jsonContent) + m := j.Get("map") + mMap := m.Map() + fmt.Println(mMap) + + // Change the key-value pair. + mMap["key"] = "john" + + // It changes the underlying key-value pair. + fmt.Println(j.Get("map").Map()) + + s := j.Get("slice") + sArray := s.Array() + fmt.Println(sArray) + + // Change the value of specified index. + sArray[0] = 100 + + // It changes the underlying slice. + fmt.Println(j.Get("slice").Array()) + + // output: + // map[key:value] + // map[key:john] + // [59 90] + // [100 90] +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" new file mode 100644 index 00000000000..c034388560d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\225\260\346\215\256\346\240\274\345\274\217\350\275\254\346\215\242.md" @@ -0,0 +1,64 @@ +--- +slug: '/docs/components/encoding-gjson-format-converting' +title: '通用编解码-数据格式转换' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,数据格式转换,gjson,JSON,XML,YAML,TOML,编解码,接口文档] +description: '使用GoFrame框架进行数据格式转换,包括JSON、XML、YAML、TOML等多种格式的相互转换,并提供一个示例代码。使用gjson库,可以利用Must*方法确保数据格式的安全转换。' +--- + +数据格式转换有很多方法,具体请查看接口文档: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson) + +这里需要注意的是,有一些 `Must*` 转换方法,这些方法保证必须转换为指定的数据格式,否则直接 `panic`。 + +我们就来一个例子说明即可。 + +``` +data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } +}` +if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) +} else { + fmt.Println("JSON:") + fmt.Println(j.MustToJsonString()) + fmt.Println("======================") + + fmt.Println("XML:") + fmt.Println(j.MustToXmlString()) + fmt.Println("======================") + + fmt.Println("YAML:") + fmt.Println(j.MustToYamlString()) + fmt.Println("======================") + + fmt.Println("TOML:") + fmt.Println(j.MustToTomlString()) +} + +// Output: +// JSON: +// {"users":{"array":["John","Ming"],"count":1}} +// ====================== +// XML: +// JohnMing1 +// ====================== +// YAML: +// users: +// array: +// - John +// - Ming +// count: 1 +// +// ====================== +// TOML: +// [users] +// array = ["John", "Ming"] +// count = 1.0 +``` + +`gjson` 支持将 `JSON` 转换为其他常见的数据格式,目前支持: `JSON`、 `XML`、 `INI`、 `YAML/YML`、 `TOML`、 `PROPERTIES`、 `Struct` 数据格式之间的相互转换。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..ea1f78c09e6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\274\226\347\240\201\350\247\243\347\240\201/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-gjson/\351\200\232\347\224\250\347\274\226\350\247\243\347\240\201-\346\226\271\346\263\225\344\273\213\347\273\215.md" @@ -0,0 +1,2730 @@ +--- +slug: '/docs/components/encoding-gjson-funcs' +title: '通用编解码-方法介绍' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,Json对象,编解码,gjson方法,GoFrame框架,数据层级访问,并发安全,NewWithTag,代码示例,内容格式] +description: '使用GoFrame框架进行通用编解码的多种方法,包括Json对象的创建、数据访问和格式转换等。提供了包括New、Load、Encode、Decode等方法的详细说明和示例代码,有助于理解如何在GoFrame中高效处理各种数据格式。' +--- +:::tip +以下常用方法列表,文档更新可能滞后于代码新特性,更多的方法及示例请参考代码文档: [https://pkg.go.dev/github.com/gogf/gf/v2/encoding/gjson](https://pkg.go.dev/github.com/gogf/gf/v2/os/gres) +::: +### `New` + +- 说明: `New` 可以用任意类型的值 `data` 创建一个 `Json` 对象,但是由于数据访问的关系, `data` 应该是一个 `map` 或者 `slice`,否则是无意义的。 + +- 注意: `safe` 参数决定了 `Json` 对象是否是并发安全的,默认为 `false` +- 格式: + +```go +func New(data interface{}, safe ...bool) *Json +``` + +- 示例: + +```go +func ExampleNew() { + jsonContent := `{"name":"john", "score":"100"}` + j := gjson.New(jsonContent) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + + +### `NewWithTag` + +- 说明: `NewWithTag` 可以用任意类型的值 `data` 创建一个 `Json` 对象,但是由于数据访问的关系, `data` 应该是一个 `map` 或者 `slice`,否则是无意义的。 + +- 注意: `tgts` 参数指定了结构体转换到map的标签名的优先级,多个标签用 `','` 分割。 +- `safe` 参数决定了 `Json` 对象是否是并发安全的,默认为 `false` +- 格式: + +```go +func NewWithTag(data interface{}, tags string, safe ...bool) *Json +``` + +- 示例: + +```go +func ExampleNewWithTag() { + type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string + } + me := Me{ + Name: "john", + Score: 100, + Title: "engineer", + } + j := gjson.NewWithTag(me, "tag", true) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(j.Get("Title")) + + // Output: + // john + // 100 + // engineer +} +``` + + +### `NewWithOptions` + +- 说明: `NewWithOptions` 可以用任意类型的值 `data` 创建一个 `Json` 对象,但是由于数据访问的关系, `data` 应该是一个 `map` 或者 `slice`,否则是无意义的。 + +- 格式: + +```go +func NewWithOptions(data interface{}, options Options) *Json +``` + +- 示例: + +```go +func ExampleNewWithOptions() { + type Me struct { + Name string `tag:"name"` + Score int `tag:"score"` + Title string + } + me := Me{ + Name: "john", + Score: 100, + Title: "engineer", + } + + j := gjson.NewWithOptions(me, gjson.Options{ + Tags: "tag", + }) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(j.Get("Title")) + + // Output: + // john + // 100 + // engineer +} +``` + +```go +func ExampleNewWithOptions_UTF8BOM() { + jsonContent := `{"name":"john", "score":"100"}` + + content := make([]byte, 3, len(jsonContent)+3) + content[0] = 0xEF + content[1] = 0xBB + content[2] = 0xBF + content = append(content, jsonContent...) + + j := gjson.NewWithOptions(content, gjson.Options{ + Tags: "tag", + }) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + + +### `Load` + +- 说明:`Load` 从指定的文件 `path` 中加载内容,并将其内容创建一个 `Json` 对象。 + +- 格式: + +```go +func Load(path string, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoad() { + jsonFilePath := gtest.DataPath("json", "data1.json") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + notExistFilePath := gtest.DataPath("json", "data2.json") + j2, _ := gjson.Load(notExistFilePath) + fmt.Println(j2.Get("name")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoad_Xml() { + jsonFilePath := gtest.DataPath("xml", "data1.xml") + j, _ := gjson.Load(jsonFilePath) + fmt.Println(j.Get("doc.name")) + fmt.Println(j.Get("doc.score")) +} +``` + + +### `LoadJson` + +- 说明: `LoadJson` 用给定的 `JSON` 格式的内容创建一个 `Json` 对象。 + +- 格式: + +```go +func LoadJson(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadJson() { + jsonContent := `{"name":"john", "score":"100"}` + j, _ := gjson.LoadJson(jsonContent) + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + + +### `LoadXml` + +- 说明: `LoadXml` 用给定的 `XML` 格式的内容创建一个 `Json` 对象。 + +- 格式: + +```go +func LoadXml(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadXml() { + xmlContent := ` + + john + 100 + ` + j, _ := gjson.LoadXml(xmlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + + +### LoadIni + +- 说明: `LoadIni` 用给定的 `INI` 格式的内容创建一个 `Json` 对象。 + +- 格式: + +```go +func LoadIni(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadIni() { + iniContent := ` + [base] + name = john + score = 100 + ` + j, _ := gjson.LoadIni(iniContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + + +### `LoadYaml` + +- 说明: `LoadYaml` 用给定的 `YAML` 格式的内容创建一个 `Json` 对象。 + +- 格式: + +```go +func LoadYaml(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadYaml() { + yamlContent := + `base: + name: john + score: 100` + + j, _ := gjson.LoadYaml(yamlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + + +### `LoadToml` + +- 说明: `LoadToml` 用给定的 `TOML` 格式的内容创建一个 `Json` 对象。 + +- 格式: + +```go +func LoadToml(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadToml() { + tomlContent := + `[base] + name = "john" + score = 100` + + j, _ := gjson.LoadToml(tomlContent) + fmt.Println(j.Get("base.name")) + fmt.Println(j.Get("base.score")) + + // Output: + // john + // 100 +} +``` + + +### `LoadContent` + +- 说明:`LoadContent` 根据给定的内容创建一个 `Json` 对象,它自动检查 `content` 的数据类型,支持的内容类型如下: `JSON, XML, INI, YAML和TOML`。 + +- 格式: + +```go +func LoadContent(data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadContent() { + jsonContent := `{"name":"john", "score":"100"}` + + j, _ := gjson.LoadContent(jsonContent) + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoadContent_UTF8BOM() { + jsonContent := `{"name":"john", "score":"100"}` + + content := make([]byte, 3, len(jsonContent)+3) + content[0] = 0xEF + content[1] = 0xBB + content[2] = 0xBF + content = append(content, jsonContent...) + + j, _ := gjson.LoadContent(content) + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + + // Output: + // john + // 100 +} +``` + +```go +func ExampleLoadContent_Xml() { + xmlContent := ` + + john + 100 + ` + + x, _ := gjson.LoadContent(xmlContent) + + fmt.Println(x.Get("base.name")) + fmt.Println(x.Get("base.score")) + + // Output: + // john + // 100 +} +``` + + +### LoadContentType + +- 说明:`LoadContentType` 根据给定的内容和类型创建一个 `Json` 对象,支持的内容类型如下: `Json, XML, INI, YAML和TOML`。 + +- 格式: + +```go +func LoadContentType(dataType string, data interface{}, safe ...bool) (*Json, error) +``` + +- 示例: + +```go +func ExampleLoadContentType() { + jsonContent := `{"name":"john", "score":"100"}` + xmlContent := ` + + john + 100 + ` + + j, _ := gjson.LoadContentType("json", jsonContent) + x, _ := gjson.LoadContentType("xml", xmlContent) + j1, _ := gjson.LoadContentType("json", "") + + fmt.Println(j.Get("name")) + fmt.Println(j.Get("score")) + fmt.Println(x.Get("base.name")) + fmt.Println(x.Get("base.score")) + fmt.Println(j1.Get("")) + + // Output: + // john + // 100 + // john + // 100 +} +``` + + +### `IsValidDataType` + +- 说明:`IsValidDataType` 检查给定的 `dataType` 是否是可以用于加载的有效数据内容。 + +- 格式: + +```go +func IsValidDataType(dataType string) bool +``` + +- 示例: + +```go +func ExampleIsValidDataType() { + fmt.Println(gjson.IsValidDataType("json")) + fmt.Println(gjson.IsValidDataType("yml")) + fmt.Println(gjson.IsValidDataType("js")) + fmt.Println(gjson.IsValidDataType("mp4")) + fmt.Println(gjson.IsValidDataType("xsl")) + fmt.Println(gjson.IsValidDataType("txt")) + fmt.Println(gjson.IsValidDataType("")) + fmt.Println(gjson.IsValidDataType(".json")) + + // Output: + // true + // true + // true + // false + // false + // false + // false + // true +} +``` + + +### `Valid` + +- 说明:`Valid` 检查 `data` 是否为有效的 `JSON` 数据类型。 参数 `data` 指定 `JSON` 格式数据,可以是 `bytes` 或 `string` 类型。 + +- 格式: + +```go +func Valid(data interface{}) bool +``` + +- 示例: + +```go +func ExampleValid() { + data1 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) + data2 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`) + fmt.Println(gjson.Valid(data1)) + fmt.Println(gjson.Valid(data2)) + + // Output: + // true + // false +} +``` + + +### Marshal + +- 说明:`Marshal` 是 `Encode` 的别名。 + +- 格式: + +```go +func Marshal(v interface{}) (marshaledBytes []byte, err error) +``` + +- 示例: + +```go +func ExampleMarshal() { + data := map[string]interface{}{ + "name": "john", + "score": 100, + } + + jsonData, _ := gjson.Marshal(data) + fmt.Println(string(jsonData)) + + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "Guo Qiang", + Age: 18, + } + + infoData, _ := gjson.Marshal(info) + fmt.Println(string(infoData)) + + // Output: + // {"name":"john","score":100} + // {"Name":"Guo Qiang","Age":18} +} +``` + + +### `MarshalIndent` + +- 说明:`MarshalIndent` 是 `json.``MarshalIndent` 的别名 。 + +- 格式: + +```go +func MarshalIndent(v interface{}, prefix, indent string) (marshaledBytes []byte, err error) +``` + +- 示例: + +```go +func ExampleMarshalIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.MarshalIndent(info, "", "\t") + fmt.Println(string(infoData)) + + // Output: + // { + // "Name": "John", + // "Age": 18 + // } +} +``` + + +### Unmarshal + +- 说明: `Unmarshal` 是 `DecodeTo` 的别名。 + +- 格式: + +```go +func Unmarshal(data []byte, v interface{}) (err error) +``` + +- 示例: + +```go +func ExampleUnmarshal() { + type BaseInfo struct { + Name string + Score int + } + + var info BaseInfo + + jsonContent := "{\"name\":\"john\",\"score\":100}" + gjson.Unmarshal([]byte(jsonContent), &info) + fmt.Printf("%+v", info) + + // Output: + // {Name:john Score:100} +} +``` + + +### Encode + +- 说明: `Encode` 将任意类型 `value` 序列化为内容为 `JSON` 的 `byte` 数组。 + +- 格式: + +```go +func Encode(value interface{}) ([]byte, error) +``` + +- 示例: + +```go +func ExampleEncode() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.Encode(info) + fmt.Println(string(infoData)) + + // Output: + // {"Name":"John","Age":18} +} +``` + + +### `MustEncode` + +- 说明:`MustEncode` 执行 `Encode` 操作,但如果发生任何错误,它会 `panic`。 + +- 格式: + +```go +func MustEncode(value interface{}) []byte +``` + +- 示例: + +```go +func ExampleMustEncode() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData := gjson.MustEncode(info) + fmt.Println(string(infoData)) + + // Output: + // {"Name":"John","Age":18} +} +``` + + +### `EncodeString` + +- 说明: `EncodeString` 将任意类型 `value` 序列化为内容为 `JSON` 的 `string` 。 + +- 格式: + +```go +func EncodeString(value interface{}) (string, error) +``` + +- 示例: + +```go +func ExampleEncodeString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData, _ := gjson.EncodeString(info) + fmt.Println(infoData) + + // Output: + // {"Name":"John","Age":18} +} +``` + + +### `MustEncodeString` + +- 说明: `MustEncodeString` 将任意类型 `value` 序列化为内容为 `JSON` 的 `string`,但如果发生任何错误,它会 `panic`。 + +- 格式: + +```go +func MustEncodeString(value interface{}) string +``` + +- 示例: + +```go +func ExampleMustEncodeString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + infoData := gjson.MustEncodeString(info) + fmt.Println(infoData) + + // Output: + // {"Name":"John","Age":18} +} +``` + + +### `Decode` + +- 说明:`Decode` 将 `JSON` 格式的内容 `data` 解码为 `interface{}`。 参数 `data` 可以是 `[]byte` 或 `string`。 + +- 格式: + +```go +func Decode(data interface{}, options ...Options) (interface{}, error) +``` + +- 示例: + +```go +func ExampleDecode() { + jsonContent := `{"name":"john","score":100}` + info, _ := gjson.Decode([]byte(jsonContent)) + fmt.Println(info) + + // Output: + // map[name:john score:100] +} +``` + + +### DecodeTo + +- 说明:`DecodeTo` 将 `JSON` 格式的数据 `data` 解码到指定的 `interface` 类型的变量 `v` 中。参数 `data` 可以是 `[]byte` 或 `string`。参数 `v` 应该是指针类型。 + +- 格式: + +```go +func DecodeTo(data interface{}, v interface{}, options ...Options) (err error) +``` + +- 示例: + +```go +func ExampleDecodeTo() { + type BaseInfo struct { + Name string + Score int + } + + var info BaseInfo + + jsonContent := "{\"name\":\"john\",\"score\":100}" + gjson.DecodeTo([]byte(jsonContent), &info) + fmt.Printf("%+v", info) + + // Output: + // {Name:john Score:100} +} +``` + + +### `DecodeToJson` + +- 说明:`DecodeToJson` 将 `JSON` 格式的数据 `data` 编码为 `json` 对象。参数 `data` 可以是 `[]byte` 或 `string`。 + +- 格式: + +```go +func DecodeToJson(data interface{}, options ...Options) (*Json, error) +``` + +- 示例: + +```go +func ExampleDecodeToJson() { + jsonContent := `{"name":"john","score":100}"` + j, _ := gjson.DecodeToJson([]byte(jsonContent)) + fmt.Println(j.Map()) + + // May Output: + // map[name:john score:100] +} + + +``` + + +### `SetSplitChar` + +- 说明:`SetSplitChar` 设置数据访问的层级分隔符。 + +- 格式: + +```go +func (j *Json) SetSplitChar(char byte) +``` + +- 示例: + +```go +func ExampleJson_SetSplitChar() { + data := + `{ + "users" : { + "count" : 2, + "list" : [ + {"name" : "Ming", "score" : 60}, + {"name" : "John", "score" : 99.5} + ] + } + }` + if j, err := gjson.DecodeToJson(data); err != nil { + panic(err) + } else { + j.SetSplitChar('#') + fmt.Println("John Score:", j.Get("users#list#1#score").Float32()) + } + // Output: + // John Score: 99.5 +} +``` + + +### `SetViolenceCheck` + +- 说明: `SetViolenceCheck` 启用/禁用数据层级访问的暴力检查。 + +- 格式: + +```go +func (j *Json) SetViolenceCheck(enabled bool) +``` + +- 示例: + +```go +func ExampleJson_SetViolenceCheck() { + data := + `{ + "users" : { + "count" : 100 + }, + "users.count" : 101 + }` + if j, err := gjson.DecodeToJson(data); err != nil { + fmt.Println(err) + } else { + j.SetViolenceCheck(true) + fmt.Println("Users Count:", j.Get("users.count")) + } + // Output: + // Users Count: 101 +} +``` + + +### `ToJson` + +- 说明: `ToJson` 返回类型为 `[]byte` 的 `JSON` 内容。 + +- 格式: + +```go +func (j *Json) ToJson() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToJson() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.ToJson() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + + +### `ToJsonString` + +- 说明: `ToJsonString` 返回类型为 `string` 的 `JSON` 内容。 + +- 格式: + +```go +func (j *Json) ToJsonString() (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToJsonString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr, _ := j.ToJsonString() + fmt.Println(jsonStr) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + + +### ToJsonIndent + +- 说明: `ToJsonIndent` 返回类型为 `[]byte` 的带缩进格式的 `JSON` 内容。 + +- 格式: + +```go +func (j *Json) ToJsonIndent() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToJsonIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.ToJsonIndent() + fmt.Println(string(jsonBytes)) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + + +### `ToJsonIndentString` + +- 说明: `ToJsonIndentString` 返回类型为 `string` 的带缩进格式的 `JSON` 内容。 + +- 格式: + +```go +func (j *Json) ToJsonIndentString() (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToJsonIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr, _ := j.ToJsonIndentString() + fmt.Println(jsonStr) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + + +### MustToJson + +- 说明: `MustToJson` 返回类型为 `[]byte` 的 `JSON` 内容,如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToJson() []byte +``` + +- 示例: + +```go +func ExampleJson_MustToJson() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes := j.MustToJson() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + + +### `MustToJsonString` + +- 说明: `MustToJsonString` 返回类型为 `string` 的 `JSON` 内容,如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToJsonString() string +``` + +- 示例: + +```go +func ExampleJson_MustToJsonString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr := j.MustToJsonString() + fmt.Println(jsonStr) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + + +### MustToJsonIndent + +- 说明: `MustToJsonStringIndent` 返回类型为 `[]byte` 的带缩进格式的 `JSON` 内容,如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToJsonIndent() []byte +``` + +- 示例: + +```go +func ExampleJson_MustToJsonIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes := j.MustToJsonIndent() + fmt.Println(string(jsonBytes)) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + + +### `MustToJsonIndentString` + +- 说明: `MustToJsonStringIndent` 返回类型为 `string` 的带缩进格式的 `JSON` 内容,如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToJsonIndentString() string +``` + +- 示例: + +```go +func ExampleJson_MustToJsonIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonStr := j.MustToJsonIndentString() + fmt.Println(jsonStr) + + // Output: + //{ + // "Age": 18, + // "Name": "John" + //} +} +``` + + +### ToXml + +- 说明: `ToXml` 返回类型为 `[]byte` 格式为 `XML` 的内容。 + +- 格式: + +```go +func (j *Json) ToXml(rootTag ...string) ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToXml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes, _ := j.ToXml() + fmt.Println(string(xmlBytes)) + + // Output: + // 18John +} +``` + + +### `ToXmlString` + +- 说明: `ToXmlString` 返回类型为 `string` 格式为 `XML` 的内容。 + +- 格式: + +```go +func (j *Json) ToXmlString(rootTag ...string) (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToXmlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr, _ := j.ToXmlString() + fmt.Println(string(xmlStr)) + + // Output: + // 18John +} +``` + + +### ToXmlIndent + +- 说明: `ToXmlIndent` 返回类型为 `[]byte` 的带缩进格式的 `XML` 内容。 + +- 格式: + +```go +func (j *Json) ToXmlIndent(rootTag ...string) ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToXmlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes, _ := j.ToXmlIndent() + fmt.Println(string(xmlBytes)) + + // Output: + // + // 18 + // John + // +} +``` + + +### `ToXmlIndentString` + +- 说明: `ToXmlIndentString` 返回类型为 `string` 的带缩进格式的 `XML` 内容。 + +- 格式: + +```go +func (j *Json) ToXmlIndentString(rootTag ...string) (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToXmlIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr, _ := j.ToXmlIndentString() + fmt.Println(string(xmlStr)) + + // Output: + // + // 18 + // John + // +} +``` + + +### MustToXml + +- 说明: `MustToXml` 返回类型为 `[]byte` 格式为 `XML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToXml(rootTag ...string) []byte +``` + +- 示例: + +```go +func ExampleJson_MustToXml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes := j.MustToXml() + fmt.Println(string(xmlBytes)) + + // Output: + // 18John +} +``` + + +### `MustToXmlString` + +- 说明: `MustToXmlString` 返回类型为 `string` 格式为 `XML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToXmlString(rootTag ...string) string +``` + +- 示例: + +```go +func ExampleJson_MustToXmlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr := j.MustToXmlString() + fmt.Println(string(xmlStr)) + + // Output: + // 18John +} +``` + + +### MustToXmlIndent + +- 说明: `MustToXmlStringIndent` 返回类型为 `[]byte` 带缩进格式的 `XML` 内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToXmlIndent(rootTag ...string) []byte +``` + +- 示例: + +```go +func ExampleJson_MustToXmlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlBytes := j.MustToXmlIndent() + fmt.Println(string(xmlBytes)) + + // Output: + // + // 18 + // John + // +} +``` + + +### `MustToXmlIndentString` + +- 说明: `MustToXmlStringIndentString` 返回类型为 `string` 带缩进格式的 `XML` 内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToXmlIndentString(rootTag ...string) string +``` + +- 示例: + +```go +func ExampleJson_MustToXmlIndentString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + xmlStr := j.MustToXmlIndentString() + fmt.Println(string(xmlStr)) + + // Output: + // + // 18 + // John + // +} +``` + + +### ToYaml + +- 说明: `ToYaml` 返回类型为 `[]byte` 格式为 `YAML` 的内容。 + +- 格式: + +```go +func (j *Json) ToYaml() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToYaml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes, _ := j.ToYaml() + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + + +### ToYamlIndent + +- 说明: `ToYamlIndent` 返回类型为 `[]byte` 带缩进格式的 `YAML` 内容。 + +- 格式: + +```go +func (j *Json) ToYamlIndent(indent string) ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToYamlIndent() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes, _ := j.ToYamlIndent("") + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + + +### `ToYamlString` + +- 说明: `ToYamlString` 返回类型为 `string` 格式为 `YAML` 的内容。 + +- 格式: + +```go +func (j *Json) ToYamlString() (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToYamlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlStr, _ := j.ToYamlString() + fmt.Println(string(YamlStr)) + + // Output: + //Age: 18 + //Name: John +} +``` + + +### `MustToYaml` + +- 说明: `MustToYaml` 返回类型为 `[]byte` 格式为 `YAML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToYaml() []byte +``` + +- 示例: + +```go +func ExampleJson_MustToYaml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlBytes := j.MustToYaml() + fmt.Println(string(YamlBytes)) + + // Output: + //Age: 18 + //Name: John +} +``` + + +### MustToYamlString + +- 说明: `MustToYamlString` 返回类型为 `string` 格式为 `YAML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToYamlString() string +``` + +- 示例: + +```go +func ExampleJson_MustToYamlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + YamlStr := j.MustToYamlString() + fmt.Println(string(YamlStr)) + + // Output: + //Age: 18 + //Name: John +} +``` + + +### `ToToml` + +- 说明: `ToToml` 返回类型为 `[]byte` 格式为 `TOML` 的内容。 + +- 格式: + +```go +func (j *Json) ToToml() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToToml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlBytes, _ := j.ToToml() + fmt.Println(string(TomlBytes)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + + +### ToTomlString + +- 说明: `ToTomlString` 返回类型为 `string` 格式为 `TOML` 的内容。 + +- 格式: + +```go +func (j *Json) ToTomlString() (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToTomlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlStr, _ := j.ToTomlString() + fmt.Println(string(TomlStr)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + + +### `MustToToml` + +- 说明: `MustToToml` 返回类型为 `[]byte` 格式为 `TOML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToToml() []byte +``` + +- 示例: + +```go +func ExampleJson_MustToToml() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlBytes := j.MustToToml() + fmt.Println(string(TomlBytes)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + + +### MustToTomlString + +- 说明: `MustToTomlString` 返回类型为 `string` 格式为 `TOML` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToTomlString() string +``` + +- 示例: + +```go +func ExampleJson_MustToTomlString() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + TomlStr := j.MustToTomlString() + fmt.Println(string(TomlStr)) + + // Output: + //Age = 18 + //Name = "John" +} +``` + + +### `ToIni` + +- 说明: `ToIni` 返回类型为 `[]byte` 格式为 `INI` 的内容。 + +- 格式: + +```go +func (j *Json) ToIni() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_ToIni() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + IniBytes, _ := j.ToIni() + fmt.Println(string(IniBytes)) + + // May Output: + //Name=John + //Age=18 +} +``` + + +### ToIniString + +- 说明: `ToIniString` 返回类型为 `string` 格式为 `INI` 的内容。 + +- 格式: + +```go +func (j *Json) ToIniString() (string, error) +``` + +- 示例: + +```go +func ExampleJson_ToIniString() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniStr, _ := j.ToIniString() + fmt.Println(string(IniStr)) + + // Output: + //Name=John +} +``` + + +### `MustToIni` + +- 说明: `MustToIni` 返回类型为 `[]byte` 格式为 `INI` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToIni() []byte +``` + +- 示例: + +```go +func ExampleJson_MustToIni() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniBytes := j.MustToIni() + fmt.Println(string(IniBytes)) + + // Output: + //Name=John +} +``` + + +### MustToIniString + +- 说明: `MustToIniString` 返回类型为 `string` 格式为 `INI` 的内容。如果发生任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustToIniString() string +``` + +- 示例: + +```go +func ExampleJson_MustToIniString() { + type BaseInfo struct { + Name string + } + + info := BaseInfo{ + Name: "John", + } + + j := gjson.New(info) + IniStr := j.MustToIniString() + fmt.Println(string(IniStr)) + + // Output: + //Name=John +} +``` + + +### MarshalJSON + +- 说明:`MarshalJSON` 实现了 `json.Marshal` 的接口 `MarshalJSON`。 + +- 格式: + +```go +func (j Json) MarshalJSON() ([]byte, error) +``` + +- 示例: + +```go +func ExampleJson_MarshalJSON() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + jsonBytes, _ := j.MarshalJSON() + fmt.Println(string(jsonBytes)) + + // Output: + // {"Age":18,"Name":"John"} +} +``` + + +### UnmarshalJSON + +- 说明: `UnmarshalJSON 实现了` `json.Unmarshal 的接口` `UnmarshalJSON`。 + +- 格式: + +```go +func (j *Json) UnmarshalJSON(b []byte) error +``` + +- 示例: + +```go +func ExampleJson_UnmarshalJSON() { + jsonStr := `{"Age":18,"Name":"John"}` + + j := gjson.New("") + j.UnmarshalJSON([]byte(jsonStr)) + fmt.Println(j.Map()) + + // Output: + // map[Age:18 Name:John] +} +``` + + +### `UnmarshalValue` + +- 说明:`UnmarshalValue` 是一个为 `Json` 设置任何类型的值的接口实现。 + +- 格式: + +```go +func (j *Json) UnmarshalValue(value interface{}) error +``` + +- 示例: + +```go +func ExampleJson_UnmarshalValue_Yaml() { + yamlContent := + `base: + name: john + score: 100` + + j := gjson.New("") + j.UnmarshalValue([]byte(yamlContent)) + fmt.Println(j.Var().String()) + + // Output: + // {"base":{"name":"john","score":100}} +} +``` + +```go +func ExampleJson_UnmarshalValue_Xml() { + xmlStr := `john100` + + j := gjson.New("") + j.UnmarshalValue([]byte(xmlStr)) + fmt.Println(j.Var().String()) + + // Output: + // {"doc":{"name":"john","score":"100"}} +} +``` + + +### `MapStrAny` + +- 说明: `MapStrAny` 实现了接口方法 `MapStrAny()`。 + +- 格式: + +```go +func (j *Json) MapStrAny() map[string]interface{} +``` + +- 示例: + +```go +func ExampleJson_MapStrAny() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.MapStrAny()) + + // Output: + // map[Age:18 Name:John] +} +``` + + +### Interfaces + +- 说明: `Interfaces` 实现了接口方法 `Interfaces()`。 + +- 格式: + +```go +func (j *Json) Interfaces() []interface{} +``` + +- 示例: + +```go +func ExampleJson_Interfaces() { + type BaseInfo struct { + Name string + Age int + } + + infoList := []BaseInfo{ + BaseInfo{ + Name: "John", + Age: 18, + }, + BaseInfo{ + Name: "Tom", + Age: 20, + }, + } + + j := gjson.New(infoList) + fmt.Println(j.Interfaces()) + + // Output: + // [{John 18} {Tom 20}] +} +``` + + +### `Interface` + +- 说明: `Interface` 返回 `Json` 对象的值。 + +- 格式: + +```go +func (j *Json) Interface() interface{} +``` + +- 示例: + +```go +func ExampleJson_Interface() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Interface()) + + var nilJ *gjson.Json = nil + fmt.Println(nilJ.Interface()) + + // Output: + // map[Age:18 Name:John] + // +} +``` + + +### `Var` + +- 说明: `Var` 返回类型为 `*gvar.Var` 的 `Json` 对象的值。 + +- 格式: + +```go +func (j *Json) Var() *gvar.Var +``` + +- 示例: + +```go +func ExampleJson_Var() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Var().String()) + fmt.Println(j.Var().Map()) + + // Output: + // {"Age":18,"Name":"John"} + // map[Age:18 Name:John] +} +``` + + +### IsNil + +- 说明: `IsNil` 检查Json对象值是否为 `nil`。 + +- 格式: + +```go +func (j *Json) IsNil() bool +``` + +- 示例: + +```go +func ExampleJson_IsNil() { + data1 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]}`) + data2 := []byte(`{"n":123456789, "m":{"k":"v"}, "a":[1,2,3]`) + + j1, _ := gjson.LoadContent(data1) + fmt.Println(j1.IsNil()) + + j2, _ := gjson.LoadContent(data2) + fmt.Println(j2.IsNil()) + + // Output: + // false + // true +} +``` + + +### `Get` + +- 说明:`Get` 根据指定的 `pattern` 检索并返回值。如果 `pattern` 给的是 `"."`,将返回当前 `Json` 对象的所有值。没有 `pattern` 没有找到,则返回 `nil`。 + +- 格式: + +```go +func (j *Json) Get(pattern string, def ...interface{}) *gvar.Var +``` + +- 示例: + +```go +func ExampleJson_Get() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + fmt.Println(j.Get(".")) + fmt.Println(j.Get("users")) + fmt.Println(j.Get("users.count")) + fmt.Println(j.Get("users.array")) + + var nilJ *gjson.Json = nil + fmt.Println(nilJ.Get(".")) + + // Output: + // {"users":{"array":["John","Ming"],"count":1}} + // {"array":["John","Ming"],"count":1} + // 1 + // ["John","Ming"] +} +``` + + +### `GetJson` + +- 说明:`GetJson` 通过指定的 `pattern` 获取值,并将其转换为一个非并发安全的 `Json` 对象。 + +- 格式: + +```go +func (j *Json) GetJson(pattern string, def ...interface{}) *Json +``` + +- 示例: + +```go +func ExampleJson_GetJson() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.GetJson("users.array").Array()) + + // Output: + // [John Ming] +} +``` + + +### GetJsons + +- 说明:`GetJsons` 通过指定的 `pattern` 获取值,并将其转换为一个非并发安全的 `Json` 对象切片。 + +- 格式: + +```go +func (j *Json) GetJsons(pattern string, def ...interface{}) []*Json +``` + +- 示例: + +```go +func ExampleJson_GetJsons() { + data := + `{ + "users" : { + "count" : 3, + "array" : [{"Age":18,"Name":"John"}, {"Age":20,"Name":"Tom"}] + } + }` + + j, _ := gjson.LoadContent(data) + + jsons := j.GetJsons("users.array") + for _, json := range jsons { + fmt.Println(json.Interface()) + } + + // Output: + // map[Age:18 Name:John] + // map[Age:20 Name:Tom] +} +``` + + +### `GetJsonMap` + +- 说明:`GetJsonMap` 通过指定的 `pattern` 获取值,并将其转换为一个非并发安全的 `Json` 对象的 `map`。 + +- 格式: + +```go +func (j *Json) GetJsonMap(pattern string, def ...interface{}) map[string]*Json +``` + +- 示例: + +```go +func ExampleJson_GetJsonMap() { + data := + `{ + "users" : { + "count" : 1, + "array" : { + "info" : {"Age":18,"Name":"John"}, + "addr" : {"City":"Chengdu","Company":"Tencent"} + } + } + }` + + j, _ := gjson.LoadContent(data) + + jsonMap := j.GetJsonMap("users.array") + + for _, json := range jsonMap { + fmt.Println(json.Interface()) + } + + // May Output: + // map[City:Chengdu Company:Tencent] + // map[Age:18 Name:John] +} +``` + + +### `Set` + +- 说明:`Set` 设置指定 `pattern` 的值。 它默认支持通过字符 `'.'` 进行数据层级访问。 + +- 格式: + +```go +func (j *Json) Set(pattern string, value interface{}) error +``` + +- 示例: + +```go +func ExampleJson_Set() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.Set("Addr", "ChengDu") + j.Set("Friends.0", "Tom") + fmt.Println(j.Var().String()) + + // Output: + // {"Addr":"ChengDu","Age":18,"Friends":["Tom"],"Name":"John"} +} +``` + + +### `MustSet` + +- 说明:`MustSet` 执行 `Set`,但如果有任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustSet(pattern string, value interface{}) +``` + +- 示例: + +```go +func ExampleJson_MustSet() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.MustSet("Addr", "ChengDu") + fmt.Println(j.Var().String()) + + // Output: + // {"Addr":"ChengDu","Age":18,"Name":"John"} +} +``` + + +### `Remove` + +- 说明: `Remove` 删除指定 `pattern` 的值。 它默认支持通过字符 `'.'` 进行数据层级访问。 + +- 格式: + +```go +func (j *Json) Remove(pattern string) error +``` + +- 示例: + +```go +func ExampleJson_Remove() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.Remove("Age") + fmt.Println(j.Var().String()) + + // Output: + // {"Name":"John"} +} +``` + + +### MustRemove + +- 说明:`MustRemove` 执行`Remove`,但如果有任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustRemove(pattern string) +``` + +- 示例: + +```go +func ExampleJson_MustRemove() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + j.MustRemove("Age") + fmt.Println(j.Var().String()) + + // Output: + // {"Name":"John"} +} +``` + + +### `Contains` + +- 说明: `Contains` 检查指定 `pattern` 的值是否存在。 + +- 格式: + +```go +func (j *Json) Contains(pattern string) bool +``` + +- 示例: + +```go +func ExampleJson_Contains() { + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{ + Name: "John", + Age: 18, + } + + j := gjson.New(info) + fmt.Println(j.Contains("Age")) + fmt.Println(j.Contains("Addr")) + + // Output: + // true + // false +} +``` + + +### Len + +- 说明:`Len` 根据指定的 `pattern` 返回 值的长度/大小。 `pattern` 的值应该是 `slice` 或 `map` 的类型。 如果找不到目标值或类型无效,则返回 `-1`。 + +- 格式: + +```go +func (j *Json) Len(pattern string) int +``` + +- 示例: + +```go +func ExampleJson_Len() { + data := + `{ + "users" : { + "count" : 1, + "nameArray" : ["Join", "Tom"], + "infoMap" : { + "name" : "Join", + "age" : 18, + "addr" : "ChengDu" + } + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Len("users.nameArray")) + fmt.Println(j.Len("users.infoMap")) + + // Output: + // 2 + // 3 +} +``` + + +### `Append` + +- 说明:`Append` 通过指定的 `pattern` 将值追加到 `Json` 对象中。 `pattern` 的值的类型应该是 `slice`。 + +- 格式: + +```go +func (j *Json) Append(pattern string, value interface{}) error +``` + +- 示例: + +```go +func ExampleJson_Append() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + j.Append("users.array", "Lily") + + fmt.Println(j.Get("users.array").Array()) + + // Output: + // [John Ming Lily] +} +``` + + +### MustAppend + +- 说明:`MustAppend` 执行 `Append`,但如果有任何错误,会发生 `panic`。 + +- 格式: + +```go +func (j *Json) MustAppend(pattern string, value interface{}) +``` + +- 示例: + +```go +func ExampleJson_MustAppend() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + j.MustAppend("users.array", "Lily") + + fmt.Println(j.Get("users.array").Array()) + + // Output: + // [John Ming Lily] +} +``` + + +### Map + +- 说明: `Map` 将当前 `Json` 对象转换为 `map[string]interface{}`。 如果失败,则返回 `nil`。 + +- 格式: + +```go +func (j *Json) Map() map[string]interface{} +``` + +- 示例: + +```go +func ExampleJson_Map() { + data := + `{ + "users" : { + "count" : 1, + "info" : { + "name" : "John", + "age" : 18, + "addr" : "ChengDu" + } + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Get("users.info").Map()) + + // Output: + // map[addr:ChengDu age:18 name:John] +} +``` + + +### Array + +- 说明:`Array` 将当前 `Json` 对象转换为 `[]interface{}`。 如果失败,则返回 `nil`。 + +- 格式: + +```go +func (j *Json) Array() []interface{} +``` + +- 示例: + +```go +func ExampleJson_Array() { + data := + `{ + "users" : { + "count" : 1, + "array" : ["John", "Ming"] + } + }` + + j, _ := gjson.LoadContent(data) + + fmt.Println(j.Get("users.array")) + + // Output: + // ["John","Ming"] +} +``` + + +### `Scan` + +- 说明: `Scan` 自动调用 `Struct` 或 `Structs` 函数根据参数 `pointer` 的类型来进行转换。 + +- 格式: + +```go +func (j *Json) Scan(pointer interface{}, mapping ...map[string]string) error +``` + +- 示例: + +```go +func ExampleJson_Scan() { + data := `{"name":"john","age":"18"}` + + type BaseInfo struct { + Name string + Age int + } + + info := BaseInfo{} + + j, _ := gjson.LoadContent(data) + j.Scan(&info) + + fmt.Println(info) + + // May Output: + // {john 18} +} +``` + + +### Dump + +- 说明: `Dump` 以可读性更高的方式打印 `Json` 对象。 + +- 格式: + +```go +func (j *Json) Dump() +``` + +- 示例: + +```go +func ExampleJson_Dump() { + data := `{"name":"john","age":"18"}` + + j, _ := gjson.LoadContent(data) + j.Dump() + + // May Output: + //{ + // "name": "john", + // "age": "18", + //} +} +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" new file mode 100644 index 00000000000..7935918fd5a --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-TLS\345\212\240\345\257\206.md" @@ -0,0 +1,152 @@ +--- +slug: '/docs/components/network-gtcp-tls' +title: 'TCP组件-TLS加密' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gtcp,TLS加密,网络通信,Go语言,服务器,客户端,安全通信,网络安全] +description: '使用GoFrame框架的gtcp模块在安全性要求较高的环境中实现TLS加密通信。通过提供的示例代码,我们讲解了如何创建TLS服务端和客户端,如何使用证书进行数据加密传输,以及如何处理可能出现的证书过期问题。这对于需要安全传输数据的开发者来说至关重要。' +--- + +`gtcp` 模块支持 `TLS` 加密通信服务端及客户端,在对安全要求比较高的场景中非常必要。 `TLS` 服务端创建可以通过 `NewServerTLS` 或者 `NewServerKeyCrt` 方法实现。 `TLS` 客户端创建可以通过 `NewConnKeyCrt` 或者 `NewConnTLS` 方法实现。 + +使用示例: + +[https://github.com/gogf/gf/tree/master/.example/net/gtcp/tls](https://github.com/gogf/gf/tree/master/.example/net/gtcp/tls) + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + g.Log().Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConnKeyCrt(address, crtFile, keyFile) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + g.Log().Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} +``` + +执行后,可以看到客户端执行时报错: + +``` +panic: x509: certificate has expired or is not yet valid +``` + +那是因为我们的证书是手动创建的,并且已经过期了,为了演示方便,我们在客户端代码中去掉客户端对证书的校验。 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + address := "127.0.0.1:8999" + crtFile := "server.crt" + keyFile := "server.key" + // TLS Server + go gtcp.NewServerKeyCrt(address, crtFile, keyFile, func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + // if client closes, err will be: EOF + g.Log().Error(err) + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + tlsConfig, err := gtcp.LoadKeyCrt(crtFile, keyFile) + if err != nil { + panic(err) + } + tlsConfig.InsecureSkipVerify = true + + conn, err := gtcp.NewConnTLS(address, tlsConfig) + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + g.Log().Error(err) + } + time.Sleep(time.Second) + if i == 5 { + conn.Close() + break + } + } + + // exit after 5 seconds + time.Sleep(5 * time.Second) +} +``` + +执行后,终端输出结果为: + +```0 +1 +2 +3 +4 +5 +2019-06-05 00:13:12.488 [ERRO] EOF +Stack: +1. /Users/john/Workspace/Go/GOPATH/src/github.com/gogf/gf/v2/geg/net/gtcp/tls/gtcp_server_client.go:25 +``` + +其中客户端在 `5` 秒后关闭了连接,因此服务端在接收数据时获取到了一个 `EOF` 错误,这种错误在正式使用中我们直接忽略,报错时服务端直接关闭客户端连接即可。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..79b410434fb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,70 @@ +--- +slug: '/docs/components/network-gtcp-funcs' +title: 'TCP组件-工具方法' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,TCP组件,gtcp模块,Go语言,网络编程,TLS加密,数据通信,短链接,工具方法] +description: 'GoFrame框架中的gtcp模块和一些常用的工具方法,通过这些方法可以实现TCP连接的创建、TLS安全加密通信以及数据发送和接收的功能,并提供了一个具体示例展示如何通过TCP访问指定的Web站点。' +--- + +`gtcp` 模块也提供了一些常用的工具方法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +func LoadKeyCrt(crtFile, keyFile string) (*tls.Config, error) +func NewNetConn(addr string, timeout ...int) (net.Conn, error) +func NewNetConnKeyCrt(addr, crtFile, keyFile string) (net.Conn, error) +func NewNetConnTLS(addr string, tlsConfig *tls.Config) (net.Conn, error) +func Send(addr string, data []byte, retry ...Retry) error +func SendPkg(addr string, data []byte, option ...PkgOption) error +func SendPkgWithTimeout(addr string, data []byte, timeout time.Duration, option ...PkgOption) error +func SendRecv(addr string, data []byte, receive int, retry ...Retry) ([]byte, error) +func SendRecvPkg(addr string, data []byte, option ...PkgOption) ([]byte, error) +func SendRecvPkgWithTimeout(addr string, data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) +func SendRecvWithTimeout(addr string, data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) +func SendWithTimeout(addr string, data []byte, timeout time.Duration, retry ...Retry) error +``` + +1. `NewNetConn` 用于简化标准库连接对象 `net.Conn` 的创建; +2. `NewNetConnTLS` 和 `NewNetConnKeyCrt` 用于创建支持TLS安全加密通信的TCP客户端; +3. `Send*` 系列方法直接通过给定地址进行数据发送,并获取该请求的返回结果,用于短链接请求的情况; + +以下为一个简单的示例,我们使用工具方法来访问指定的Web站点: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" +) + +func main() { + data, err := gtcp.SendRecv("www.baidu.com:80", []byte("HEAD / HTTP/1.1\n\n"), -1) + if err != nil { + panic(err) + } + fmt.Println(string(data)) +} +``` + +在这个示例中,我们通过TCP访问百度首页,模拟HTTP请求头信息,并获得返回结果。 执行后,输出结果如下: + +``` +HTTP/1.1 302 Found +Connection: Keep-Alive +Content-Length: 17931 +Content-Type: text/html +Date: Tue, 04 Jun 2019 15:53:09 GMT +Etag: "54d9749e-460b" +Server: bfe/1.0.8.18 +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..b1a31e952b2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" @@ -0,0 +1,269 @@ +--- +slug: '/docs/components/network-gtcp-conn' +title: 'TCP组件-连接对象' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gtcp,TCP通信,连接对象,超时处理,数据写入,数据读取,网络编程,错误重试] +description: '此页面介绍了GoFrame框架中的gtcp模块及其提供的TCP连接对象的使用方法。包括如何使用gtcp.Conn进行可靠的TCP通信,处理数据的发送和接收。文中还描述了超时处理机制以及通过简单示例演示如何使用这些功能,展示如何在GoFrame环境下灵活进行网络编程。' +--- + +`gtcp` 模块提供了简便易用的 `gtcp.Conn` 链接操作对象。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Conn + func NewConn(addr string, timeout ...int) (*Conn, error) + func NewConnByNetConn(conn net.Conn) *Conn + func NewConnKeyCrt(addr, crtFile, keyFile string) (*Conn, error) + func NewConnTLS(addr string, tlsConfig *tls.Config) (*Conn, error) + func (c *Conn) Close() error + func (c *Conn) LocalAddr() net.Addr + func (c *Conn) Recv(length int, retry ...Retry) ([]byte, error) + func (c *Conn) RecvLine(retry ...Retry) ([]byte, error) + func (c *Conn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *Conn) RemoteAddr() net.Addr + func (c *Conn) Send(data []byte, retry ...Retry) error + func (c *Conn) SendRecv(data []byte, receive int, retry ...Retry) ([]byte, error) + func (c *Conn) SendRecvWithTimeout(data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *Conn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retry) error + func (c *Conn) SetDeadline(t time.Time) error + func (c *Conn) SetRecvBufferWait(bufferWaitDuration time.Duration) + func (c *Conn) SetRecvDeadline(t time.Time) error + func (c *Conn) SetSendDeadline(t time.Time) error +``` + +## 写入操作 + +TCP通信写入操作由 `Send` 方法实现,并提供了错误重试的机制,由第二个非必需参数 `retry` 提供。需要注意的是 `Send` 方法不带任何的缓冲机制,也就是说每调用一次 `Send` 方法将会立即调用底层的TCP Write方法写入数据(缓冲机制依靠系统底层实现)。因此,如果想要进行输出缓冲控制,请在业务层进行处理。 + +在进行TCP写入时,可靠的通信场景下往往是一写一读,即 `Send` 成功之后接着便开始 `Recv` 获取目标的反馈结果。因此 `gtcp.Conn` 也提供了方便的 `SendRecv` 方法。 + +## 读取操作 + +TCP通信读取操作由 `Recv` 方法实现,同时也提供了错误重试的机制,由第二个非必需参数 `retry` 提供。 `Recv` 方法提供了内置的读取缓冲控制,读取数据时可以指定读取的长度(由 `length` 参数指定),当读取到指定长度的数据后将会立即返回。如果 `length < 0` 那么将会读取所有可读取的缓冲区数据并返回。当 `length = 0` 时表示获取一次缓冲区的数据后立即返回。 + +如果使用 `Recv(-1)` 可以读取所有缓冲区可读数据(长度不定,如果发送的数据包太长有可能会被截断),但需要注意包的解析问题,容易产生非完整包的情况。这个时候,业务层需要根据既定的数据包结构自己负责包的完整性处理。推荐使用后续介绍的 `简单协议` 通过 `SendPkg`/ `RecvPkg` 来实现消息包的发送/接收,具体请查看后续章节。 + +## 超时处理 + +`gtcp.Conn` 对TCP通信时的数据写入和读取提供了超时处理,通过方法中的 `timeout` 参数指定,这块比较简单,不过多阐述。 + +* * * + +我们接下来通过通过几个例子来看看如何使用 `gtcp.Conn` 对象。 + +## 使用示例 + +### 示例1,简单使用 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + fmt.Println(string(data)) + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + for i := 0; i < 10000; i++ { + if err := conn.Send([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +1. `Server` 端,接收到客户端的数据后立即打印到终端上。 +2. `Client` 端,使用同一个连接对象,在循环中每隔1秒向服务端发送当前递增的数字。同时,该功能也可以演示出底层 `Socket` 通信并没有使用缓冲实现,也就是说,执行一次 `Send` 即立刻向服务端发送数据。因此,客户端需要在本地自行管理好需要发送的缓冲数据。 +3. 执行结果 执行后,可以看到Server在终端上输出以下信息: + +```shell + 2018-07-11 22:11:08.650 0 + 2018-07-11 22:11:09.651 1 + 2018-07-11 22:11:10.651 2 + 2018-07-11 22:11:11.651 3 + 2018-07-11 22:11:12.651 4 + 2018-07-11 22:11:13.651 5 + 2018-07-11 22:11:14.652 6 + 2018-07-11 22:11:15.652 7 + 2018-07-11 22:11:16.652 8 + 2018-07-11 22:11:17.652 9 + 2018-07-11 22:11:18.652 10 + 2018-07-11 22:11:19.653 11 + ... +``` + + +### 示例2,回显服务 + +我们将之前的回显服务改进一下: + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +该示例程序中, `Client` 每隔1秒钟向 `Server` 发送当前的时间信息, `Server` 接收到之后返回原数据信息, `Client` 接收到 `Server` 端返回信息后立即打印到终端。 + +执行后,输出结果为: + +``` +> 2018-07-19 23:25:43 127.0.0.1:34306 127.0.0.1:8999 +> 2018-07-19 23:25:44 127.0.0.1:34308 127.0.0.1:8999 +> 2018-07-19 23:25:45 127.0.0.1:34312 127.0.0.1:8999 +> 2018-07-19 23:25:46 127.0.0.1:34314 127.0.0.1:8999 +``` + +### 示例3,HTTP客户端 + +我们在这个示例中使用gtcp包来实现一个简单的HTTP客户端,读取并打印出百度首页的 `header` 和 `content` 内容。 + +```go +package main + +import ( + "fmt" + "bytes" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/util/gconv" +) + +func main() { + conn, err := gtcp.NewConn("www.baidu.com:80") + if err != nil { + panic(err) + } + defer conn.Close() + + if err := conn.Send([]byte("GET / HTTP/1.1\r\n\r\n")); err != nil { + panic(err) + } + + header := make([]byte, 0) + content := make([]byte, 0) + contentLength := 0 + for { + data, err := conn.RecvLine() + // header读取,解析文本长度 + if len(data) > 0 { + array := bytes.Split(data, []byte(": ")) + // 获得页面内容长度 + if contentLength == 0 && len(array) == 2 && bytes.EqualFold([]byte("Content-Length"), array[0]) { + contentLength = gconv.Int(string(array[1][:len(array[1])-1])) } + header = append(header, data...) + header = append(header, '\n') + } + // header读取完毕,读取文本内容 + if contentLength > 0 && len(data) == 1 { + content, _ = conn.Recv(contentLength) + break + } + if err != nil { + fmt.Errorf("ERROR: %s\n", err.Error()) + break + } + } + + if len(header) > 0 { + fmt.Println(string(header)) + } + if len(content) > 0 { + fmt.Println(string(content)) + } +} +``` + +执行后,输出结果为: + +``` +HTTP/1.1 200 OK +Accept-Ranges: bytes +Cache-Control: no-cache +Connection: Keep-Alive +Content-Length: 14615 +Content-Type: text/html +Date: Sat, 21 Jul 2018 04:21:03 GMT +Etag: "5b3c3650-3917" +Last-Modified: Wed, 04 Jul 2018 02:52:00 GMT +P3p: CP=" OTI DSP COR IVA OUR IND COM " +Pragma: no-cache +Server: BWS/1.1 +... +(略) +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" new file mode 100644 index 00000000000..c467fd21129 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\346\266\210\346\201\257\345\214\205\345\244\204\347\220\206.md" @@ -0,0 +1,243 @@ +--- +slug: '/docs/components/network-gtcp-conn-package' +title: '连接对象-消息包处理' +sidebar_position: 0 +hide_title: true +keywords: [TCP,粘包,gtcp,GoFrame,数据协议,封包解包,消息包,网络通信,简单协议,消息交互] +description: '使用gtcp模块及其封包解包处理功能解决TCP消息协议的粘包问题。通过使用GoFrame框架的简单轻量级数据交互协议,开发者可以更轻松地进行消息包交互,无需关注复杂的封包解包细节。此外,文档还提供了多个使用示例,以帮助开发者更好理解和实现自定义数据结构的消息传递。' +--- + +`gtcp` 提供了许多方便的原生操作连接数据的方法,但是在绝大多数的应用场景中,开发者需要自己设计数据结构,并进行封包/解包处理,由于 `TCP` 消息协议是没有消息边界保护的,因此复杂的网络通信环境中很容易出现 **粘包** 的情况。因此 `gtcp` 也提供了简单的数据协议,方便开发者进行消息包交互,开发者不再需要担心消息包的处理细节,包括封包/解包处理,这一切复杂的逻辑 `gtcp` 已经帮你处理好了。 + +## 简单协议 + +`gtcp` 模块提供了简单轻量级数据交互协议,效率非常高,协议格式如下: + +``` +数据长度(16bit)|数据字段(变长) +``` + +1. 数据长度:默认为 `16位`( `2字节`),用于标识该消息体的数据长度,单位为字节,不包含自身的2字节; +2. 数据字段:变长,根据数据长度可以知道,数据最大长度不能超过 `0xFFFF = 65535 bytes = 64 KB`; + +简单协议由 `gtcp` 封装实现,如果开发者客户端和服务端如果都使用 `gtcp` 模块来通信则无需关心协议实现,专注 `数据` 字段封装/解析实现即可。如果涉及和其他开发语言对接,则需要按照该协议实现对接即可,由于简单协议非常简单轻量级,因此对接成本很低。 +:::tip +数据字段也可以为空,即没有任何长度。 +::: +## 操作方法 + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Conn + func (c *Conn) SendPkg(data []byte, option ...PkgOption) error + func (c *Conn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error + func (c *Conn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) + func (c *Conn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *Conn) RecvPkg(option ...PkgOption) (result []byte, err error) + func (c *Conn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error) +``` + +可以看到,消息包方法命名是在原有的基本连接操作方法中加上了 `Pkg` 关键词便于区分。 + +其中,请求参数中的 `PkgOption` 数据结构如下,用于定义消息包接收策略: + +```go +// 数据读取选项 +type PkgOption struct { + HeaderSize int // 自定义头大小(默认为2字节,最大不能超过4字节) + MaxDataSize int // (byte)数据读取的最大包大小,默认最大不能超过2字节(65535 byte) + Retry Retry // 失败重试策略 +} +``` + +## 使用示例 + +### 示例1,基本使用 + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/util/gconv" + "time" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + fmt.Println(err) + break + } + fmt.Println("receive:", data) + } + }).Run() + + time.Sleep(time.Second) + + // Client + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + for i := 0; i < 10000; i++ { + if err := conn.SendPkg([]byte(gconv.String(i))); err != nil { + glog.Error(err) + } + time.Sleep(1*time.Second) + } +} +``` + +这个示例比较简单,执行后,输出结果为: + +``` +receive: [48] +receive: [49] +receive: [50] +receive: [51] +... +``` + +### 示例2,自定义数据结构 + +大多数场景下,我们需要对发送的消息能自定义数据结构,开发者可以利用 `数据` 字段传递任意的消息内容实现。 + +以下是一个简单的自定义数据结构的示例,用于客户端上报当前主机节点的内存及CPU使用情况。在该示例中, `数据` 字段使用了 `JSON` 数据格式进行自定义,便于数据的编码/解码。 +:::tip +实际场景中,开发者往往需要自定义包解析协议,或者采用较通用的 `protobuf` 二进制包封装/解析协议。 +::: +1. `types/types.go` + +数据结构定义。 + +```go +package types + + +import "github.com/gogf/gf/v2/frame/g" + + +type NodeInfo struct { + Cpu float32 // CPU百分比(%) + Host string // 主机名称 + Ip g.Map // IP地址信息(可能多个) + MemUsed int // 内存使用(byte) + MemTotal int // 内存总量(byte) + Time int // 上报时间(时间戳) +} +``` + +2. `gtcp_monitor_server.go` + +服务端。 + +```go +package main + + +import ( + "encoding/json" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/monitor/types" +) + + +func main() { + // 服务端,接收客户端数据并格式化为指定数据结构,打印 + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.RecvPkg() + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + info := &types.NodeInfo{} + if err := json.Unmarshal(data, info); err != nil { + glog.Errorf("invalid package structure: %s", err.Error()) + } else { + glog.Println(info) + conn.SendPkg([]byte("ok")) + } + } + }).Run() +} +``` + +3. `gtcp_monitor_client.go` + +客户端。 + +```go +package main + + +import ( + "encoding/json" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/monitor/types" +) + + +func main() { + // 数据上报客户端 + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // 使用JSON格式化数据字段 + info, err := json.Marshal(types.NodeInfo{ + Cpu : float32(66.66), + Host : "localhost", + Ip : g.Map { + "etho" : "192.168.1.100", + "eth1" : "114.114.10.11", + }, + MemUsed : 15560320, + MemTotal : 16333788, + Time : int(gtime.Timestamp()), + }) + if err != nil { + panic(err) + } + // 使用 SendRecvPkg 发送消息包并接受返回 + if result, err := conn.SendRecvPkg(info); err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + } else { + glog.Println(string(result)) + } +} +``` + +4. 执行后 + + - 客户端输出结果为: + + ```html + 2019-05-03 13:33:25.710 ok + ``` + + - 服务端输出结果为: + + ```html + 2019-05-03 13:33:25.710 &{66.66 localhost map[eth1:114.114.10.11 etho:192.168.1.100] 15560320 16333788 1556861605} + 2019-05-03 13:33:25.710 client closed + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" new file mode 100644 index 00000000000..5ca02ce8e0b --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241/\350\277\236\346\216\245\345\257\271\350\261\241-\351\200\232\344\277\241\345\274\200\345\217\221\350\277\233\351\230\266.md" @@ -0,0 +1,242 @@ +--- +slug: '/docs/components/network-gtcp-conn-senior' +title: '连接对象-通信开发进阶' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,gtcp,长连接,异步通信,全双工通信,SendPkg,RecvPkg,网络编程,并发安全] +description: '在GoFrame框架下使用gtcp实现异步全双工通信的高级开发。通过完整示例展示了如何在程序中处理长连接,利用SendPkg和RecvPkg方法进行数据传输,并确保并发安全。' +--- + +## 开发进阶 + +针对于短连接而言,每一次发送接收数据即关闭连接,连接的处理逻辑比较简单,当然通信效率也会比较低。在大多数的TCP通信场景中,往往是使用长连接操作,并采用异步全双工的TCP通信模式,即所有的通信完全是异步。在这种场景下, `gtcp.Conn` 链接对象可能同时处于多个读写操作( `gtcp.Conn` 对象的数据读写操作是并发安全的),因此 `SendRecv` 操作在逻辑上将会失效。因为当你在同一逻辑操作中发送完毕数据之后,随后立即获取数据有可能得到的是其他写操作的结果。 + +无论服务端还是客户端,都需要在独立的异步循环中封装使用 `Recv*` 方法获取数据并通过 `switch...case...` 处理数据(仅在一个 `goroutine` 中全权负责读取数据),根据请求数据进行业务处理的转发。 +:::tip +也就是说, `Send*`/ `Recv*` 方法是并发安全的,但是发送数据时要一次性发送。由于支持异步并发写, `gtcp.Conn` 对象不带任何缓冲实现。 +::: +## 使用示例 + +我们通过一个完成的示例来说明一下如何在程序中实现异步全双工通信,完成示例代码位于: [https://github.com/gogf/gf/tree/master/.example/net/gtcp/pkg\_operations/common](https://github.com/gogf/gf/tree/master/.example/net/gtcp/pkg_operations/common) + +1. `types/types.go` + +定义通信的数据格式,随后我们可以使用 `SendPkg`/ `RecvPkg` 方法来通信。 + +考虑到简化测试代码复杂度,因此这里使用 `JSON` 数据格式来传递数据。在一些对于消息包大小比较严格的场景中, `数据` 字段可以自行按照二进制进行封装解析设计。此外,需要注意的是,即使使用 `JSON` 数据格式,其中的 `Act` 字段往往定义常量来实现,大部分场景中使用 `uint8` 类型即可,以减小消息包大小,这里偷一下懒,直接使用字符串,以便演示。 + +```go +package types + + +type Msg struct { + Act string // 操作 + Data string // 数据 +} +``` + +2. `funcs/funcs.go` + +自定义数据格式的发送/获取定义,便于数据结构编码/解析。 + +```go +package funcs + + +import ( + "encoding/json" + "fmt" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" +) + + +// 自定义格式发送消息包 +func SendPkg(conn *gtcp.Conn, act string, data...string) error { + s := "" + if len(data) > 0 { + s = data[0] + } + msg, err := json.Marshal(types.Msg{ + Act : act, + Data : s, + }) + if err != nil { + panic(err) + } + return conn.SendPkg(msg) +} + + +// 自定义格式接收消息包 +func RecvPkg(conn *gtcp.Conn) (msg *types.Msg, err error) { + if data, err := conn.RecvPkg(); err != nil { + return nil, err + } else { + msg = &types.Msg{} + err = json.Unmarshal(data, msg) + if err != nil { + return nil, fmt.Errorf("invalid package structure: %s", err.Error()) + } + return msg, err + } +} +``` + +3. `gtcp_common_server.go` + +通信服务端。在该示例中,服务端并不主动断开连接,而是在 `10` 秒后向客户端发送 `doexit` 消息,通知客户端主动断开连接,以结束示例。 + +```go +package main + + +import ( + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" + "time" +) + + +func main() { + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + // 测试消息, 10秒后让客户端主动退出 + gtimer.SetTimeout(10*time.Second, func() { + funcs.SendPkg(conn, "doexit") + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("client closed") + } + break + } + switch msg.Act { + case "hello": onClientHello(conn, msg) + case "heartbeat": onClientHeartBeat(conn, msg) + default: + glog.Errorf("invalid message: %v", msg) + break + } + } + }).Run() +} + + +func onClientHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("hello message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) + funcs.SendPkg(conn, msg.Act, "Nice to meet you!") +} + + +func onClientHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) +} +``` + +4. `gtcp_common_client.go` + +通信客户端,可以看到代码结构和服务端差不多,数据获取独立处于 `for` 循环中,每个业务逻辑发送消息包时直接使用 `SendPkg` 方法进行发送。 + +心跳消息常用 `gtimer` 定时器实现,在该示例中,客户端每隔 `1` 秒主动向服务端发送心跳消息,在 `3` 秒后向服务端发送 `hello` 测试消息。这些都是由 `gtimer` 定时器实现的,定时器在TCP通信中比较常见。 + +客户端连接 `10` 秒后,服务端会给客户端发送 `doexit` 消息,客户端收到该消息后便主动断开连接,长连接结束。 + +```go +package main + + +import ( + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtimer" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/funcs" + "github.com/gogf/gf/.example/net/gtcp/pkg_operations/common/types" + "time" +) + + +func main() { + conn, err := gtcp.NewConn("127.0.0.1:8999") + if err != nil { + panic(err) + } + defer conn.Close() + // 心跳消息 + gtimer.SetInterval(time.Second, func() { + if err := funcs.SendPkg(conn, "heartbeat"); err != nil { + panic(err) + } + }) + // 测试消息, 3秒后向服务端发送hello消息 + gtimer.SetTimeout(3*time.Second, func() { + if err := funcs.SendPkg(conn, "hello", "My name's John!"); err != nil { + panic(err) + } + }) + for { + msg, err := funcs.RecvPkg(conn) + if err != nil { + if err.Error() == "EOF" { + glog.Println("server closed") + } + break + } + switch msg.Act { + case "hello": onServerHello(conn, msg) + case "doexit": onServerDoExit(conn, msg) + case "heartbeat": onServerHeartBeat(conn, msg) + default: + glog.Errorf("invalid message: %v", msg) + break + } + } +} + + +func onServerHello(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("hello response message from [%s]: %s", conn.RemoteAddr().String(), msg.Data) +} + + +func onServerHeartBeat(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("heartbeat from [%s]", conn.RemoteAddr().String()) +} + + +func onServerDoExit(conn *gtcp.Conn, msg *types.Msg) { + glog.Printf("exit command from [%s]", conn.RemoteAddr().String()) + conn.Close() +} +``` + +5. 执行后 + + - ` + ` 服务端输出结果 + + ```html + 2019-05-03 14:59:13.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:14.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:15.733 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:15.733 hello message from [127.0.0.1:51220]: My name's John! + 2019-05-03 14:59:16.731 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:17.733 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:18.731 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:19.730 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:20.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:21.732 heartbeat from [127.0.0.1:51220] + 2019-05-03 14:59:22.698 client closed + ``` + + - 客户端输出结果 + + ```html + 2019-05-03 14:59:15.733 hello response message from [127.0.0.1:8999]: Nice to meet you! + 2019-05-03 14:59:22.698 exit command from [127.0.0.1:8999] + ``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" new file mode 100644 index 00000000000..43d084017b2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266-\350\277\236\346\216\245\346\261\240\347\211\271\346\200\247.md" @@ -0,0 +1,173 @@ +--- +slug: '/docs/components/network-gtcp-connection-pool' +title: 'TCP组件-连接池特性' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,GoFrame框架,TCP连接池,gtcp模块,连接池特性,短链接操作,高并发,断开重连,数据发送,示例程序] +description: 'GoFrame框架中的gtcp模块连接池特性,通过gtcp.PoolConn实现的连接池具有600秒的固定存活时间,并具备数据发送时的断开重连机制,适用于频繁短链接操作和高连接并发的场景。文章提供了两个示例,演示了连接池的基础使用及断开重连操作,帮助用户深刻理解连接池在网络编程中的实际应用。' +--- + +`gtcp` 模块提供了连接池的特性,由 `gtcp.PoolConn` 对象实现,连接池缓存固定存活时间为600秒,且内部实现了数据发送时的断开重连机制。连接池非常适合于频繁的短链接操作且连接并发量大的场景。我们接下来使用两个示例来演示一下连接池的作用。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type PoolConn + func NewPoolConn(addr string, timeout ...int) (*PoolConn, error) + func (c *PoolConn) Close() error + func (c *PoolConn) Recv(length int, retry ...Retry) ([]byte, error) + func (c *PoolConn) RecvLine(retry ...Retry) ([]byte, error) + func (c *PoolConn) RecvPkg(option ...PkgOption) ([]byte, error) + func (c *PoolConn) RecvPkgWithTimeout(timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *PoolConn) RecvWithTimeout(length int, timeout time.Duration, retry ...Retry) (data []byte, err error) + func (c *PoolConn) Send(data []byte, retry ...Retry) error + func (c *PoolConn) SendPkg(data []byte, option ...PkgOption) (err error) + func (c *PoolConn) SendPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) error + func (c *PoolConn) SendRecv(data []byte, receive int, retry ...Retry) ([]byte, error) + func (c *PoolConn) SendRecvPkg(data []byte, option ...PkgOption) ([]byte, error) + func (c *PoolConn) SendRecvPkgWithTimeout(data []byte, timeout time.Duration, option ...PkgOption) ([]byte, error) + func (c *PoolConn) SendRecvWithTimeout(data []byte, receive int, timeout time.Duration, retry ...Retry) ([]byte, error) + func (c *PoolConn) SendWithTimeout(data []byte, timeout time.Duration, retry ...Retry) error +``` + +由于 `gtcp.PoolConn` 继承于 `gtcp.Conn` 因此同时也可以使用 `gtcp.Conn` 的方法。 + +## 示例1,基本使用 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +在这个示例中,Server创建新的goroutine异步运行,Client在main goroutine中执行。Server端是一个回显服务器,Client每隔1秒向Server端发送当前的时间,经过Server端回显返回后,在Client端打印出双方的连接端口信息。 + +执行后,结果如下: + +``` +> 2018-07-11 23:29:54 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:55 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:56 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:57 127.0.0.1:55876 127.0.0.1:8999 +> 2018-07-11 23:29:58 127.0.0.1:55876 127.0.0.1:8999 +... +``` + +可以看到,Client的端口一直未变,每一次通过 `gtcp.NewConn("127.0.0.1:8999")` 获得的都是同一个 `gtcp.Conn` 对象,且每一次 `conn.Close()` 时并不是真正的关闭连接,而是将该对象重新丢回到连接池里循环使用。 + +## 示例2,连接断开情况 + +这个例子是为了展示当服务端关闭连接后,该连接对象还是否有效的处理。 + +```go +package main + +import ( + "fmt" + "time" + "github.com/gogf/gf/v2/net/gtcp" + "github.com/gogf/gf/v2/os/glog" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + // Server + go gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + return + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gtcp.NewPoolConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + fmt.Println(err) + } + conn.Close() + } else { + glog.Error(err) + } + time.Sleep(time.Second) + } +} +``` + +执行后,输出结果如下: + +``` +> 2018-07-20 12:56:15 127.0.0.1:59368 127.0.0.1:8999 +EOF +> 2018-07-20 12:56:17 127.0.0.1:59376 127.0.0.1:8999 +EOF +> 2018-07-20 12:56:19 127.0.0.1:59378 127.0.0.1:8999 +EOF +... +``` + +在这个示例中,Server每处理完毕一条请求之后便关闭链接。Client在第一条请求发送完毕后,由于连接池的IO复用特性,下一次获取到的将是同一个连接对象,由于Server链接已主动关闭,第二次请求写入成功(其实并未成功发送到Server端,需要通过下一次的读取操作才能检测到链接错误),但是读取却失败了( `EOF` 表示目标连接关闭),因此这个时候Client执行 `Close` 时将会销毁该连接操作对象,而不是进一步复用。下一次再通过 `gtcp.NewPoolConn` 获得连接对象时,Client将会与Server创建一个新的连接进行数据通信。所以你看到Client的端口一直在变化,那是因为该 `gtcp.Conn` 对象已经是一个新的连接对象,之前的连接对象已经被销毁。 + +连接对象的IO复用涉及到十分微妙的连接状态变化问题,由于点对点网络通信本身是比较复杂的环境,连接对象的状态随时可能被动发生着变化,因此,在使用gtcp连接池特性时,需要注意当通信错误产生时的连接对象重建机制,一旦产生错误,立即丢弃( `Close`)该对象( `gtcp.PoolConn`)并重建( `gtcp.NewPoolConn`)。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..b6a4ee908d1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/TCP\347\273\204\344\273\266/TCP\347\273\204\344\273\266.md" @@ -0,0 +1,87 @@ +--- +slug: '/docs/components/network-gtcp' +title: 'TCP组件' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gtcp,TCPServer,服务器,编程,网络连接,轻量级,并发,例子] +description: 'GoFrame框架中的gtcp模块,该模块实现了简便易用、轻量级的TCPServer服务端。通过使用gtcp,用户可以轻松创建和管理TCP服务,并支持高并发连接。文档中提供了简单的代码示例,以演示如何使用gtcp模块创建一个基本的echo服务器。' +--- + + +## 基本介绍 +`gtcp` 模块实现简便易用、轻量级的 `TCPServer` 服务端。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gtcp" +``` + +**接口文档**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gtcp) + +```go +type Server + func GetServer(name ...interface{}) *Server + func NewServer(address string, handler func(*Conn), name ...string) *Server + func NewServerKeyCrt(address, crtFile, keyFile string, handler func(*Conn), name ...string) *Server + func NewServerTLS(address string, tlsConfig *tls.Config, handler func(*Conn), name ...string) *Server + func (s *Server) Close() error + func (s *Server) Run() (err error) + func (s *Server) SetAddress(address string) + func (s *Server) SetHandler(handler func(*Conn)) + func (s *Server) SetTLSConfig(tlsConfig *tls.Config) + func (s *Server) SetTLSKeyCrt(crtFile, keyFile string) error +``` + +其中, `GetServer` 使用单例模式通过给定一个唯一的名称获取/创建一个单例 `Server`,后续可通过 `SetAddress` 和 `SetHandler` 方法动态修改Server属性; `NewServer` 则直接根据给定参数创建一个Server对象,并可指定名称。 + +我们通过实现一个简单的 `echo服务器` 来演示 `TCPServer` 的使用: + +```go +package main + +import ( + "fmt" + "github.com/gogf/gf/v2/net/gtcp" +) + +func main() { + gtcp.NewServer("127.0.0.1:8999", func(conn *gtcp.Conn) { + defer conn.Close() + for { + data, err := conn.Recv(-1) + if len(data) > 0 { + if err := conn.Send(append([]byte("> "), data...)); err != nil { + fmt.Println(err) + } + } + if err != nil { + break + } + } + }).Run() +} +``` + +在这个示例中我们使用了 `Send` 和 `Recv` 来发送和接收数据。其中 `Recv` 方法会通过阻塞方式接收数据,直到客户端”发送完毕一条数据”(执行一次 `Send`,底层Socket通信不带缓冲实现),或者关闭链接。关于其中的链接对象 `gtcp.Conn` 的介绍,请继续阅读后续章节。 + +执行之后我们使用 `telnet` 工具来进行测试: + +```bash +john@home:~$ telnet 127.0.0.1 8999 +Trying 127.0.0.1... +Connected to 127.0.0.1. +Escape character is '^]'. +hello +> hello +hi there +> hi there +``` + +每一个客户端发起的TCP链接,TCPServer都会创建一个 `goroutine` 进行处理,直至TCP链接断开。由于goroutine比较轻量级,因此可以支撑很高的并发量。 + +## 相关文档 + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" new file mode 100644 index 00000000000..6763c28e5f5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\345\267\245\345\205\267\346\226\271\346\263\225.md" @@ -0,0 +1,25 @@ +--- +slug: '/docs/components/network-gudp-funcs' +title: 'UDP组件-工具方法' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,UDP组件,gudp模块,工具方法,UDP通信,数据传输,网络编程,Go语言,网络协议] +description: '使用GoFrame框架中的gudp模块进行UDP通信的常用工具方法,包括如何通过NewNetConn创建UDP连接,使用Send和SendRecv方法进行数据传输,以及使用*Pkg方法简化数据包协议传输。' +--- + +`gudp` 模块也提供了一些常用的工具方法。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**接口文档**: + +[https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + + + +`gudp` 的工具相对比较简单,这里不做详细介绍。 + diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" new file mode 100644 index 00000000000..66fd83b46e2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266-\350\277\236\346\216\245\345\257\271\350\261\241.md" @@ -0,0 +1,93 @@ +--- +slug: '/docs/components/network-gudp-conn' +title: 'UDP组件-连接对象' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,gudp.Conn,UDP组件,UDP连接,gudp模块,网络编程,Go语言,数据通信,编程示例] +description: '使用GoFrame框架进行UDP组件开发,特别是gudp.Conn连接对象的使用。文中提供了详细的函数接口说明以及一个完整的客户端与服务端通信的示例代码,帮助开发者快速掌握UDP连接对象的具体操作和应用场景。' +--- + +`gudp` 模块提供了非常简便易用的 `gudp.Conn` 链接操作对象。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**接口文档**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + + +## 基本介绍 + +`gudp.Conn` 的操作绝大部分类似于 `gtcp` 的操作方式(大部分的方法名称也相同),但由于 `UDP` 是面向非连接的协议,因此 `gudp.Conn`(底层通信端口)也只能完成最多一次数据写入和读取,客户端下一次再与目标服务端进行通信的时候,将需要创建新的 `Conn` 对象进行通信。 + +## 使用示例 + +```go +package main + +import ( + "context" + "fmt" + "time" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/gudp" + "github.com/gogf/gf/v2/os/gtime" +) + +func main() { + var ( + ctx = context.Background() + logger = g.Log() + ) + // Server + go gudp.NewServer("127.0.0.1:8999", func(conn *gudp.ServerConn) { + defer conn.Close() + for { + data, addr, err := conn.Recv(-1) + if len(data) > 0 { + if err = conn.Send(append([]byte("> "), data...), addr); err != nil { + logger.Error(ctx, err) + } + } + if err != nil { + logger.Error(ctx, err) + } + } + }).Run() + + time.Sleep(time.Second) + + // Client + for { + if conn, err := gudp.NewClientConn("127.0.0.1:8999"); err == nil { + if b, err := conn.SendRecv([]byte(gtime.Datetime()), -1); err == nil { + fmt.Println(string(b), conn.LocalAddr(), conn.RemoteAddr()) + } else { + logger.Error(ctx, err) + } + conn.Close() + } else { + logger.Error(ctx, err) + } + time.Sleep(time.Second) + } +} +``` + +该示例与 `gtcp.Conn` 中的通信示例类似,不同的是,客户端与服务端无法保持连接,每次通信都需要创建的新的连接对象进行通信。 + +执行后,输出结果如下: + +```text +> 2018-07-21 23:13:31 127.0.0.1:33271 127.0.0.1:8999 +> 2018-07-21 23:13:32 127.0.0.1:45826 127.0.0.1:8999 +> 2018-07-21 23:13:33 127.0.0.1:58027 127.0.0.1:8999 +> 2018-07-21 23:13:34 127.0.0.1:33056 127.0.0.1:8999 +> 2018-07-21 23:13:35 127.0.0.1:39260 127.0.0.1:8999 +> 2018-07-21 23:13:36 127.0.0.1:33967 127.0.0.1:8999 +> 2018-07-21 23:13:37 127.0.0.1:52359 127.0.0.1:8999 +... +``` \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..758f18ab352 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/UDP\347\273\204\344\273\266/UDP\347\273\204\344\273\266.md" @@ -0,0 +1,60 @@ +--- +slug: '/docs/components/network-gudp' +title: 'UDP组件' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,UDP组件,UDP协议,无连接协议,gudp.Server,gudp.Conn,接口文档,gudp用法,NewServer,SetAddress] +description: 'GoFrame框架中的UDP组件,使用gudp.Server和gudp.Conn实现UDP协议的简单不可靠信息传送服务。通过示例代码展示如何创建和运行UDP服务器,并提供了相关接口文档链接供参考。' +--- + +## 基本介绍 +`UDP (User Datagram Protocol)` 一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。 `UDP` 服务端通过 `gudp.Server` 实现,客户端通过 `gudp.ClientConn` 对象或者工具方法实现。 + +**使用方式**: + +```go +import "github.com/gogf/gf/v2/net/gudp" +``` + +**接口文档**: [https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp](https://pkg.go.dev/github.com/gogf/gf/v2/net/gudp) + + +## 使用示例 + +```go +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2/net/gudp" +) + +func main() { + handler := func(conn *gudp.ServerConn) { + defer conn.Close() + for { + if data, addr, _ := conn.Recv(-1); len(data) > 0 { + fmt.Println(string(data), addr.String()) + } + } + } + err := gudp.NewServer("127.0.0.1:8999", handler).Run() + if err != nil { + fmt.Println(err) + } +} +``` + +`UDPServer` 是阻塞运行的,用户可以在自定义的回调函数中根据读取内容进行并发处理。 + +在 `Linux` 下可以使用以下命令向服务端发送 `UDP` 数据进行测试,随后查看服务端端是否有输出: + +```bash +echo "hello" > /dev/udp/127.0.0.1/8999 +``` + +## 相关文档 +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" new file mode 100644 index 00000000000..8d8e68f0bdc --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\347\275\221\347\273\234\347\273\204\344\273\266/\347\275\221\347\273\234\347\273\204\344\273\266.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/network' +title: '网络组件' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,GoFrame框架,网络服务,开发,网络协议,服务优化,Web服务,RESTful API,TCP/UDP,负载均衡] +description: '使用GoFrame框架进行网络服务开发,涵盖网络协议的实现、RESTful API设计、TCP/UDP通讯、以及如何通过负载均衡优化网络服务性能。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" new file mode 100644 index 00000000000..6b108dc15e6 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\345\244\204\347\220\206-gerror.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/error-gerror' +title: '错误处理-gerror' +sidebar_position: 0 +hide_title: true +description: 'GoFrame框架下的gerror模块用于错误处理。gerror模块作为GoFrame的核心组件,提供了统一的错误处理机制,提升了代码的可读性和可维护性,适用于Go语言的软件开发。' +keywords: [GoFrame,GoFrame框架,gerror,错误处理,Go语言,编程,软件开发,代码可读性,代码维护,核心组件] +--- + +错误处理功能由 `gerror` 模块实现,具体请参考 [错误处理](../../核心组件/错误处理/错误处理.md) 章节。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" new file mode 100644 index 00000000000..991b1916c54 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\240\201\346\216\245\345\217\243-gcode.md" @@ -0,0 +1,10 @@ +--- +slug: '/docs/components/error-gcode' +title: '错误码接口-gcode' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误码接口,错误处理,核心组件,接口参考,gcode,组件开发,架构设计,系统稳定性] +description: '了解GoFrame框架中的错误码接口gcode,以及如何在项目中有效地处理错误。本文档详细介绍了错误码接口的功能和使用方法,帮助开发者在GoFrame框架中构建稳定和可靠的应用程序,确保系统的高可用性和可维护性。' +--- + +请参考核心组件章节: [错误处理-错误码接口](../../核心组件/错误处理/错误处理-错误码特性/错误处理-错误码接口.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" new file mode 100644 index 00000000000..0678a9218fb --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\347\273\204\344\273\266\345\210\227\350\241\250/\351\224\231\350\257\257\347\256\241\347\220\206/\351\224\231\350\257\257\347\256\241\347\220\206.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/components/error' +title: '错误管理' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,GoFrame框架,错误管理,错误处理,异常捕获,Web开发,应用程序错误,编程错误,错误日志,错误调试] +description: '在GoFrame框架中进行错误管理和处理。通过适当的方法捕获和处理错误,可以提高Web应用的稳定性和用户体验。详细讲解了错误日志记录和异常捕获的方法,帮助开发者更好地应对运行时错误。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..1740d389c5f --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\344\273\243\347\220\206\351\203\250\347\275\262.md" @@ -0,0 +1,90 @@ +--- +slug: '/docs/deploy/proxy' +title: '代理部署' +sidebar_position: 1 +hide_title: true +keywords: [代理部署,GoFrame,反向代理,Nginx,静态文件,Golang应用,WebServer,动态请求,负载均衡,域名配置] +description: '使用Nginx作为反向代理的前端接入层来部署GoFrame框架应用。通过配置静态文件后缀或目录,可以有效分离静态和动态请求,以提高性能。配置示例详细展示了如何将请求转发到Golang应用,实现专业性的WebServer部署方案。' +--- + +代理部署即前置一层第三方的 `WebServer` 服务器处理所有的请求,将部分请求(往往是动态处理请求)有选择性地转交给后端的 `Golang` 应用程序执行,后端部署的 `Golang` 应用程序可以配置有多个。这种模式常用在复杂的 `WebServer` 配置中,常见的场景例如:需要静态文件分离、需要配置多个域名及证书、需要自建负载均衡层,等等。 + +虽然 `Golang` 实现的 `WebServer` 也能够处理静态文件,但是相比较于专业性的 `WebServer` 如 `nginx`/ `apache` 来说比较简单,性能也较弱。因此不推荐使用 `Golang WebServer` 作为前端服务直接处理静态文件请求。 + +## `Nginx` + +我们推荐使用 `Nginx` 作为反向代理的前端接入层,有两种配置方式实现动静态请求的拆分。 + +### 静态文件后缀 + +这种方式通过文件名后缀区分,将指定的静态文件转交给 `nginx` 处理,其他的请求转交给 `golang` 应用。 配置示例如下: + +```conf +server { + listen 80; + server_name goframe.org; + + access_log /var/log/gf-app-access.log; + error_log /var/log/gf-app-error.log; + + location ~ .*\.(gif|jpg|jpeg|png|js|css|eot|ttf|woff|svg|otf)$ { + access_log off; + expires 1d; + root /var/www/gf-app/public; + try_files $uri @backend; + } + + location / { + try_files $uri @backend; + } + + location @backend { + proxy_pass http://127.0.0.1:8199; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +其中, `8199` 为本地的 `golang` 应用 `WebServer` 监听端口。 + +例如,在该配置下,我们可以通过 `http://goframe.org/my.png` 访问到指定的静态文件。 + +### 静态文件目录 + +这种方式通过文件目录区分,将指定目录的访问请求转交给 `nginx` 处理,其他的请求转交给 `golang` 应用。 配置示例如下: + +```conf +server { + listen 80; + server_name goframe.org; + + access_log /var/log/gf-app-access.log; + error_log /var/log/gf-app-error.log; + + location ^~ /public { + access_log off; + expires 1d; + root /var/www/gf-app; + try_files $uri @backend; + } + + location / { + try_files $uri @backend; + } + + location @backend { + proxy_pass http://127.0.0.1:8199; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +其中, `8199` 为本地的 `golang` 应用 `WebServer` 监听端口。 + +例如,在该配置下,我们可以通过 `http://goframe.org/public/my.png` 访问静态文件。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..55128301893 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\345\256\271\345\231\250\351\203\250\347\275\262.md" @@ -0,0 +1,87 @@ +--- +slug: '/docs/deploy/container' +title: '容器部署' +sidebar_position: 2 +hide_title: true +keywords: [容器部署,docker,golang,交叉编译,静态编译,alpine,Dockerfile,镜像分发,kubernetes,docker swarm] +description: '使用Docker化方式部署Golang应用,深入探讨了交叉编译技术,如何在alpine镜像上构建并分发Docker镜像。在企业级环境下,推荐结合Kubernetes或Docker Swarm进行容器编排以提高系统扩展性和可靠性。' +--- + +容器部署即使用 `docker` 化部署 `golang` 应用程序,这是在云服务时代最流行的部署方式,也是最推荐的部署方式。 + +> 在以下我们的示例中,统一使用 `main` 作为项目名称。 + +## 1\. 编译程序 + +跨平台交叉编译是 `golang` 的特点之一,可以非常方便地编译出我们需要的目标服务器平台的版本,而且是静态编译,非常容易地解决了运行依赖问题。 + +使用以下指令可以静态编译 `Linux` 平台 `amd64` 架构的可执行文件: + +```bash +CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main main.go +``` + +生成的 `main` 便是我们静态编译的,可部署于 `Linux amd64` 上的可执行文件。 + +## 2\. 编译镜像 + +我们需要将该可执行文件 `main` 编译生成 `docker` 镜像,以便于分发及部署。 `Golang` 的运行环境推荐使用 `alpine` 基础系统镜像,编译出的容器镜像约为 `20MB` 左右。 + +一个参考的 `Dockerfile` 文件如下: + +```dockerfile +FROM loads/alpine:3.8 + +LABEL maintainer="john@goframe.org" + +############################################################################### +# INSTALLATION +############################################################################### + +# 设置固定的项目路径 +ENV WORKDIR /app/main + +# 添加应用可执行文件,并设置执行权限 +ADD ./main $WORKDIR/main +RUN chmod +x $WORKDIR/main + +# 添加静态资源文件 +ADD resource $WORKDIR/resource + +############################################################################### +# START +############################################################################### +WORKDIR $WORKDIR +CMD ./main +``` + +其中,我们的基础镜像使用了 `loads/alpine:3.8`,中国国内的用户推荐使用该基础镜像,基础镜像的 `Dockerfile` 地址: [https://github.com/gqcn/dockerfiles](https://github.com/gqcn/dockerfiles) ,仓库地址: [https://hub.docker.com/u/loads](https://hub.docker.com/u/loads) + +随后使用 `docker build -t main .` 指令编译生成名为 `main` 的 `docker` 镜像。 + +**注意事项** + +需要注意的是,在某些项目的架构设计中, **静态文件** 和 **配置文件** 可能不会随着镜像进行编译发布,而是分开进行管理和发布。 + +例如,使用 `MVVM` 模式的项目中(例如使用 `vue` 框架),往往是前后端非常独立的,因此在镜像中往往并不会包含 `public` 目录。而使用了 `配置管理中心`(例如使用 `consul`/ `etcd`/ `zookeeper`)的项目中,也往往并不需要 `config` 目录。 + +因此对于以上示例的 `Dockerfile` 的使用,仅作参考,根据实际情况请进行必要的调整。 + +## 3\. 运行镜像 + +使用以下指令可直接运行刚才编译成的镜像: + +```bash +docker run main +``` + +## 4\. 镜像分发 + +容器的分发可以使用 `docker` 官方的平台: [https://hub.docker.com/](https://hub.docker.com/) ,国内也可以考虑使用阿里云: [https://www.aliyun.com/product/acr](https://www.aliyun.com/product/acr) 。 + +## 5\. 容器编排 + +在企业级生产环境中, `docker` 容器往往需要结合 `kubernetes` 或者 `docker swarm` 容器编排工具一起使用。 容器编排涉及到的内容比较多,感兴趣的同学可以参考以下资料: + +- [https://kubernetes.io/](https://kubernetes.io/) +- [https://docs.docker.com/swarm/](https://docs.docker.com/swarm/) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..d4f108b013d --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\347\213\254\347\253\213\351\203\250\347\275\262.md" @@ -0,0 +1,135 @@ +--- +slug: '/docs/deploy/standalone' +title: '独立部署' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,独立部署,服务器部署,nix服务器,Ubuntu部署,后台守护进程,进程管理,Linux,Windows] +description: '独立部署使用GoFrame框架开发的应用程序,适用于*nix系列服务器如Linux和MacOS。文中详细讲解了在Ubuntu系统上使用nohup、tmux、supervisor、systemctl和screen等工具进行后台守护进程的设置和管理方法。此外,还包括在Windows系统上使用NSSM工具的相关指导。' +--- + +使用 `GoFrame` 开发的应用程序可以独立地部署到服务器上,设置为后台守护进程运行即可。这种模式常用在简单的API服务项目中。 + +服务器我们推荐使用 `*nix` 服务器系列(包括: `Linux`, `MacOS`, `*BSD`),以下使用 `Ubuntu` 系统为例,介绍如何部署使用 `GoFrame` 框架开发的项目。 + +## \*nix + +### 1\. `nohup` + +我们可以使用简单的 `nohup` 命令来运行应用程序,使其作为后台守护进程运行,即使远程连接的SSH断开也不会影响程序的执行。在流行的Linux发行版中往往都默认安装好了 `nohup` 命令工具。 命令如下: + +```bash +nohup ./gf-app & +``` + +### 2\. `tmux` + +`tmux` 是一款 `Linux` 下的终端复用工具,可以开启不同的终端窗口来将应用程序作为后台守护进程执行,即使远程连接的 `SSH` 断开也不会影响程序的执行。 在 `ubuntu` 系统下直接使用 `sudo apt-get install tmux` 安装即可。使用以下步骤将应用程序后台运行。 + +1. `tmux new -s gf-app`; +2. 在新终端窗口中执行 `./gf-app` 即可; +3. 使用 `ctrl` \+ `B & D` 快捷键可以退出当前终端窗口; +4. 使用 `tmux attach -t gf-app` 可进入到之前的终端窗口; + +### 3\. `supervisor` + +`supervisor` 是用 `Python` 开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台 `daemon`,并监控进程状态,异常退出时能自动重启。官方网站: [http://supervisord.org/](http://supervisord.org/) 常见配置如下: + +```ini +[program:gf-app] +user = root +directory = /var/www +command = /var/www/main +stdout_logfile = /var/log/gf-app-stdout.log +stderr_logfile = /var/log/gf-app-stderr.log +autostart = true +autorestart = true +``` + +使用步骤如下: + +1. 使用 `sudo service supervisor start` 启动 `supervisor` 服务; +2. 创建应用配置文件 `/etc/supervisor/conf.d/gf-app.conf`, 内容如上; +3. 使用 `sudo supervisorctl` 进入 `supervisor` 管理终端; +4. 使用 `reload` 重新读取配置文件并重启当前 `supoervisor` 管理的所有进程; +5. 也可以使用 `update` 重新加载配置(默认不重启),随后使用 `start gf-app` 启动指定的应用程序; +6. 随后可以使用 `status` 指令查看当前 `supervisor` 管理的进程状态; + +踩坑分享经验: + +1. `conf` 配置文件变更后需要在 `supervisorctl` 中执行 `reload` 才能更新使用到最新的配置。 +2. `directory` 配置一般是不能缺少的,指定当前的工作目录路径,且必须配置在 `command` 之前。 +3. `command` 命令需要使用绝对路径,否则会找不到执行文件。 + +### 4\. `systemctl` + +`Systemd` 是 `Linux` 系统工具,用来启动守护进程,已成为大多数发行版的标准配置。 + +而 `systemctl` 是 `Systemd` 的主命令,用于管理系统。可以参考 [阮一峰对于 Systemd 的解读](http://www.ruanyifeng.com/blog/2016/03/systemd-tutorial-commands.html) ,文章的第四、五章节。 + +其实我们大部分服务都有使用 `systemctl` 管理,比如 `MySQL、Nginx` 等等。 + +常见配置如下: + +```ini +[Unit] +# 单元描述 +Description=GF APP +# 在什么服务启动之后再执行本程序 +After=mysql.service + +[Service] +Type=simple +# 程序执行的目录 +WorkingDirectory=/data/server/gfapp/ +# 启动的脚本命令 +ExecStart=/data/server/gfapp/gfapp +# 重启条件 +Restart=always +# 几秒后重启 +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +使用方法: + +1. 创建应用配置文件 `/etc/systemd/system/gfapp.service`, 内容如上; +2. 使用 `systemctl daemon-reload` 重新加载服务; +3. 执行 `systemctl start gfapp` 来启动服务; +4. 最后执行 `systemctl status gfapp` 来查看服务运行的状态信息; +5. 执行 `systemctl enable gfapp` 将服务添加到开机启动项; +6. 注意:执行的 `gfapp` 是使用文件名作为服务名; +7. 常见的命令有: `start(启动), stop(停止), restart(重启), status(查看运行状态), enable(添加到开机启动项), disable(将程序从开机启动中移除)` + +### 5\. `screen` + +`Screen` 是一款由 `GNU` 计划开发的用于命令行终端切换的自由软件。用户可以通过该软件同时连接多个本地或远程的命令行会话,并在其间自由切换。 `GNU Screen` 可以看作是窗口管理器的命令行界面版本。它提供了统一的管理多个会话的界面和相应的功能。 + +安装方式: + +`sudo apt install -y screen `( `debian` 系列) + +`sudo yum install -y screen`  ( `centos`) + +**常用参数:** + +1. `screen -S yourname` -\> 新建一个叫 yourname 的 session +2. `screen -ls` -\> 列出当前所有的 session +3. `screen -r yourname` -\> 回到 yourname 这个 session +4. `screen -d yourname` -\> 远程detach某个 session +5. `screen -d -r yourname` -\> 结束当前 session 并回到 yourname 这个 session + +**使用方法:** + +1. 使用命令 `screen -S gfapp` 创建一个 session; +2. 在新终端窗口中执行 `./gf-app` 即可; +3. 执行 `ctrl-a, ctrl-d` 暂时离开当前session; +4. 执行 `screen -r gfapp` 返回命令窗口; 若返回不成功, 可能是该窗口被占用( `Attached`)了, 可以尝试使用 `screen -Dr gfapp`; +5. 执行 `screen -X -S gfapp quit` 结束程序; + +## windows + +### 1. `NSSM` + +[小巧又实用的NSSM封装windows服务工具介绍 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/455904037) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" new file mode 100644 index 00000000000..49aec754cf5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/docs/\351\241\271\347\233\256\351\203\250\347\275\262/\351\241\271\347\233\256\351\203\250\347\275\262.md" @@ -0,0 +1,12 @@ +--- +slug: '/docs/deploy' +title: '项目部署' +sidebar_position: 10 +hide_title: true +keywords: [项目部署,GoFrame,GoFrame框架,文档,指南,如何部署,部署步骤,开发框架,服务器配置,应用上线] +description: '使用GoFrame框架进行项目部署的详细指南,包括从准备服务器环境到将应用上线的完整步骤,帮助开发者顺利完成部署过程。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/examples/balancer/balancer.md b/versioned_docs/version-2.8.x/examples/balancer/balancer.md new file mode 100644 index 00000000000..4f429705032 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/balancer/balancer.md @@ -0,0 +1,29 @@ +--- +title: 负载均衡 +slug: /examples/balancer +keywords: [负载均衡, goframe] +description: GoFrame 框架中的负载均衡功能示例 +hide_title: true +sidebar_position: 5 +--- + +# 负载均衡示例 + +Github Source: https://github.com/gogf/examples/tree/main/balancer + + +## 介绍 + +本分类包含了一些在 `GoFrame` 框架中实现负载均衡相关的示例代码。 + + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/balancer/http/http.md b/versioned_docs/version-2.8.x/examples/balancer/http/http.md new file mode 100644 index 00000000000..ffd8809559a --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/balancer/http/http.md @@ -0,0 +1,47 @@ +--- +title: HTTP负载均衡 +slug: /examples/balancer/http +keywords: [负载均衡, http, 服务发现, goframe] +description: GoFrame 中的 HTTP 服务负载均衡示例 +hide_title: true +--- + +# HTTP 服务负载均衡示例 + +Github Source: https://github.com/gogf/examples/tree/main/balancer/http + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中实现 HTTP 服务的负载均衡。主要演示: +- 使用 `etcd` 配置负载均衡器 +- 服务注册和服务发现 +- 请求路由和负载分发 + +## 目录结构 + +```text +. +├── server/ # HTTP 服务器 +├── client/ # HTTP 客户端 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## 使用说明 + +1. 启动服务器: + ```bash + go run server/main.go + ``` + +2. 在另一个终端启动客户端: + ```bash + go run client/main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/balancer/polaris/polaris.md b/versioned_docs/version-2.8.x/examples/balancer/polaris/polaris.md new file mode 100644 index 00000000000..62b45e2b0a2 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/balancer/polaris/polaris.md @@ -0,0 +1,74 @@ +--- +title: 使用Polaris实现负载均衡 +slug: /examples/balancer/polaris +keywords: [负载均衡, polaris, 服务发现, goframe] +description: GoFrame 中使用 Polaris 实现负载均衡的示例 +hide_title: true +--- + +# Polaris 负载均衡示例 + +Github Source: https://github.com/gogf/examples/tree/main/balancer/polaris + + +## 介绍 + +本示例展示了如何在 `GoFrame` 中使用 `Polaris` 实现负载均衡。主要演示: +- `Polaris` 负载均衡集成 +- 服务发现机制 +- 动态路由配置 +- 故障转移实现 + +## 目录结构 + +```text +. +├── server/ # HTTP 服务器 +├── client/ # HTTP 客户端 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Polaris Registry](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) + +## 配置说明 + +1. `Polaris` 配置示例: + ```yaml + balancer: + polaris: + address: "localhost:8091" + namespace: "default" + service: + name: "demo-service" + metadata: + version: "1.0.0" + loadbalancer: + type: "weighted_random" + weights: + "instance-1": 3 + "instance-2": 2 + circuit_breaker: + enabled: true + error_rate: 0.5 + min_request: 10 + ``` + +## 使用说明 + +1. 启动 `Polaris`: + ```bash + docker run -d --name polaris \ + -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \ + polarismesh/polaris-standalone:v1.17.2 + ``` + +2. 启动服务: + ```bash + go run main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/config/apollo/apollo.md b/versioned_docs/version-2.8.x/examples/config/apollo/apollo.md new file mode 100644 index 00000000000..8b163055a64 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/apollo/apollo.md @@ -0,0 +1,54 @@ +--- +title: Apollo +slug: /examples/config/apollo +keywords: [配置中心, apollo, goframe] +description: GoFrame 框架中 Apollo 配置中心的集成示例 +hide_title: true +sidebar_position: 1 +--- + +# `Apollo` 配置中心示例 + +Github Source: https://github.com/gogf/examples/tree/main/config/apollo + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中通过配置管理组件集成 `Apollo` 配置中心。 + + +## 目录结构 + +```text +. +├── boot/ # 启动配置 +│ └── boot.go # Apollo 客户端初始化 +├── main.go # 主程序入口 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Apollo Config](https://github.com/gogf/gf/tree/master/contrib/config/apollo) + +## 使用说明 + +1. 确保 `Apollo` 服务端已启动 + +2. 配置 `Apollo` 连接信息: + ```yaml + apollo: + appId: "your-app-id" + cluster: "default" + namespaceName: "application" + ip: "localhost:8080" + ``` + +3. 运行示例: + ```bash + go run main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/config/config.md b/versioned_docs/version-2.8.x/examples/config/config.md new file mode 100644 index 00000000000..26a87599fdb --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/config.md @@ -0,0 +1,30 @@ +--- +title: 配置管理 +slug: /examples/config +keywords: [配置管理, goframe] +description: GoFrame 框架中的配置管理功能示例 +hide_title: true +sidebar_position: 3 +--- + +# 配置管理示例 + +Github Source: https://github.com/gogf/examples/tree/main/config + + +## 介绍 + +本分类包含了一些在 `GoFrame` 框架中使用配置管理组件相关的示例代码。 + +主要展示了如何使用 `GoFrame` 对接不同配置管理服务的能力。 + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/config/consul/consul.md b/versioned_docs/version-2.8.x/examples/config/consul/consul.md new file mode 100644 index 00000000000..44a8b270656 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/consul/consul.md @@ -0,0 +1,53 @@ +--- +title: Consul +slug: /examples/config/consul +keywords: [配置中心, consul, goframe] +description: GoFrame 框架中 Consul 配置中心的集成示例 +hide_title: true +sidebar_position: 1 +--- + +# `Consul` 配置中心示例 + +Github Source: https://github.com/gogf/examples/tree/main/config/consul + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中通过配置管理组件集成 `Consul` 配置中心。 + + +## 目录结构 + +```text +. +├── boot/ # 启动配置 +│ └── boot.go # Consul 客户端初始化 +├── main.go # 主程序入口 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Consul Config](https://github.com/gogf/gf/tree/master/contrib/config/consul) + +## 使用说明 + +1. 确保 `Consul` 服务端已启动 + +2. 配置 `Consul` 连接信息: + ```yaml + consul: + address: "localhost:8500" + scheme: "http" + datacenter: "dc1" + ``` + +3. 运行示例: + ```bash + go run main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/config/kubecm/kubecm.md b/versioned_docs/version-2.8.x/examples/config/kubecm/kubecm.md new file mode 100644 index 00000000000..59deecdd60b --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/kubecm/kubecm.md @@ -0,0 +1,43 @@ +--- +title: Kubernetes ConfigMap +slug: /examples/config/kubecm +keywords: [配置中心, kubernetes, configmap, goframe] +description: GoFrame 框架中 Kubernetes ConfigMap 的集成示例 +hide_title: true +sidebar_position: 9 +--- + +# `Kubernetes ConfigMap` 配置示例 + +Github Source: https://github.com/gogf/examples/tree/main/config/kubecm + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中通过配置管理组件集成 `Kubernetes ConfigMap`。 + + +## 目录结构 + +```text +. +├── boot_in_pod/ # Pod内部署的启动配置 +│ └── boot.go # Pod内客户端初始化 +├── boot_out_pod/ # Pod外部署的启动配置 +│ └── boot.go # Pod外客户端初始化 +├── main.go # 主程序入口 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Kubernetes ConfigMap Config](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) + + + + diff --git a/versioned_docs/version-2.8.x/examples/config/nacos/nacos.md b/versioned_docs/version-2.8.x/examples/config/nacos/nacos.md new file mode 100644 index 00000000000..02f5eaa2f2d --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/nacos/nacos.md @@ -0,0 +1,55 @@ +--- +title: Nacos +slug: /examples/config/nacos +keywords: [配置中心, nacos, goframe] +description: GoFrame 框架中 Nacos 配置中心的集成示例 +hide_title: true +sidebar_position: 1 +--- + +# `Nacos` 配置中心示例 + +Github Source: https://github.com/gogf/examples/tree/main/config/nacos + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中通过配置管理组件集成 `Nacos` 配置中心。 + +## 目录结构 + +```text +. +├── boot/ # 启动配置 +│ └── boot.go # Nacos 客户端初始化 +├── main.go # 主程序入口 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Nacos Config](https://github.com/gogf/gf/tree/master/contrib/config/nacos) +- [Nacos Server](https://nacos.io/) + +## 使用说明 + +1. 确保 `Nacos` 服务端已启动 + +2. 配置 `Nacos` 连接信息: + ```yaml + nacos: + serverAddr: "127.0.0.1:8848" + namespace: "public" + dataId: "example" + group: "DEFAULT_GROUP" + ``` + +3. 运行示例: + ```bash + go run main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/config/polaris/polaris.md b/versioned_docs/version-2.8.x/examples/config/polaris/polaris.md new file mode 100644 index 00000000000..94424435a8c --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/config/polaris/polaris.md @@ -0,0 +1,50 @@ +--- +title: Polaris +slug: /examples/config/polaris +keywords: [配置中心, polaris, goframe] +description: GoFrame 框架中 Polaris 配置中心的集成示例 +hide_title: true +sidebar_position: 1 +--- + +# `Polaris` 配置中心示例 + +Github Source: https://github.com/gogf/examples/tree/main/config/polaris + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中通过配置管理组件集成 `Polaris` 配置中心。 + +## 目录结构 + +```text +. +├── boot/ # 启动配置 +│ └── boot.go # Polaris 客户端初始化 +├── main.go # 主程序入口 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [GoFrame Polaris Config](https://github.com/gogf/gf/tree/master/contrib/config/polaris) +- [Polaris Server](https://polarismesh.cn/) + +## 使用说明 + +1. 启动 `Polaris`: + ```bash + docker run -d --name polaris \ + -p 8090:8090 -p 8091:8091 -p 8093:8093 -p 9090:9090 -p 9091:9091 \ + polarismesh/polaris-standalone:v1.17.2 + ``` + +2. 启动服务: + ```bash + go run main.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/grpc/balancer/balancer.md b/versioned_docs/version-2.8.x/examples/grpc/balancer/balancer.md new file mode 100644 index 00000000000..5dfcb42cd54 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/balancer/balancer.md @@ -0,0 +1,63 @@ +--- +title: 负载均衡 +slug: /examples/grpc/balancer +keywords: [grpc, 负载均衡, 服务发现, goframe] +description: GoFrame 中的 gRPC 负载均衡 +hide_title: true +sidebar_position: 2 +--- + +# `gRPC` - 负载均衡 + +Github Source: https://github.com/gogf/examples/tree/main/grpc/balancer + + +## 介绍 + +本示例展示了如何在 `GoFrame` 应用程序中实现 `gRPC` 负载均衡。 + + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带负载均衡的客户端实现 +├── controller/ # 服务控制器 +│ └── hello.go # Hello 服务实现 +├── protobuf/ # protobuf协议定义 +├── server/ # 服务器示例 +│ ├── config.yaml # 服务器配置 +│ └── server.go # 服务器实现 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Protocol Buffers](https://developers.google.com/protocol-buffers) + +## 使用说明 + +1. 启动多个服务器实例(使用随机端口): + ```bash + cd server + go run server.go + + # 启动第二个服务器实例 + go run server.go + + # 启动第三个服务器实例 + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/grpc/basic/basic.md b/versioned_docs/version-2.8.x/examples/grpc/basic/basic.md new file mode 100644 index 00000000000..6168ac08281 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/basic/basic.md @@ -0,0 +1,56 @@ +--- +title: 基础示例 +slug: /examples/grpc/basic +keywords: [grpc, 基础, goframe] +description: GoFrame 中的基础 gRPC 用法 +hide_title: true +sidebar_position: 0 +--- + +# `gRPC` - 基础示例 + +Github Source: https://github.com/gogf/examples/tree/main/grpc/basic + + +## 介绍 + +本示例展示了在 `GoFrame` 应用程序中使用 `gRPC` 的基础示例。 + + + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 客户端实现 +├── controller/ # 服务控制器 +│ └── hello.go # Hello 服务实现 +├── protobuf/ # protobuf协议定义 +├── server/ # 服务器示例 +│ ├── config.yaml # 服务器配置 +│ └── server.go # 服务器实现 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Protocol Buffers](https://developers.google.com/protocol-buffers) + +## 使用说明 + +1. 启动服务器: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/grpc/ctx/ctx.md b/versioned_docs/version-2.8.x/examples/grpc/ctx/ctx.md new file mode 100644 index 00000000000..7d0ab98fa75 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/ctx/ctx.md @@ -0,0 +1,55 @@ +--- +title: 上下文示例 +slug: /examples/grpc/ctx +keywords: [grpc, 上下文, 元数据, goframe] +description: GoFrame 中的 gRPC 上下文用法 +hide_title: true +sidebar_position: 3 +--- + +# `gRPC` - 上下文示例 + +Github Source: https://github.com/gogf/examples/tree/main/grpc/ctx + + +## 介绍 + +本示例展示了如何在 `GoFrame` 的 `gRPC` 中使用上下文和元数据。 + + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带上下文处理的客户端实现 +├── controller/ # 服务控制器 +│ └── helloworld.go # 带上下文处理的 Hello 服务 +├── protobuf/ # protobuf协议定义 +├── server/ # 服务器示例 +│ ├── config.yaml # 服务器配置 +│ └── server.go # 服务器实现 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Protocol Buffers](https://developers.google.com/protocol-buffers) + +## 使用说明 + +1. 启动服务器: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` diff --git a/versioned_docs/version-2.8.x/examples/grpc/grpc.md b/versioned_docs/version-2.8.x/examples/grpc/grpc.md new file mode 100644 index 00000000000..c7129c2cb17 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/grpc.md @@ -0,0 +1,26 @@ +--- +slug: '/examples/grpc' +title: 'gRPC' +hide_title: true +sidebar_position: 0 +--- + +# `gRPC` 使用示例 + +Github Source: https://github.com/gogf/examples/tree/main/grpc + + +## 介绍 + +本分类包含了一些 `GoFrame` 框架中 `gRPC` 相关的示例代码。 + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md b/versioned_docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md new file mode 100644 index 00000000000..2cd84aad4e2 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/rawgrpc/rawgrpc.md @@ -0,0 +1,57 @@ +--- +title: 原生 gRPC 示例 +slug: /examples/grpc/rawgrpc +keywords: [grpc, 原生, 实现, goframe] +description: GoFrame 中的原生 gRPC 实现 +hide_title: true +sidebar_position: 9 +--- + +# `gRPC` - 原生示例 + +Github Source: https://github.com/gogf/examples/tree/main/grpc/rawgrpc + + +## 介绍 + +本示例展示了如何在 `GoFrame` 中实现原生 `gRPC` 服务,不使用额外的抽象层。 + + + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 原生 gRPC 客户端实现 +├── helloworld/ # protobuf协议定义 +│ └── helloworld.proto # 服务和消息定义 +├── server/ # 服务器示例 +│ └── server.go # 原生 gRPC 服务器实现 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Protocol Buffers](https://developers.google.com/protocol-buffers) + +## 使用说明 + +1. 启动服务器: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` + diff --git a/versioned_docs/version-2.8.x/examples/grpc/resolver/resolver.md b/versioned_docs/version-2.8.x/examples/grpc/resolver/resolver.md new file mode 100644 index 00000000000..2abf128f56f --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/grpc/resolver/resolver.md @@ -0,0 +1,64 @@ +--- +title: 服务发现 +slug: /examples/grpc/resolver +keywords: [grpc, 服务发现, etcd, goframe] +description: GoFrame 中使用 etcd 的 gRPC 服务发现 +hide_title: true +sidebar_position: 1 +--- + +# `gRPC` - 服务发现 + +Github Source: https://github.com/gogf/examples/tree/main/grpc/resolver + + +## 介绍 + +本示例展示了如何在 `GoFrame` 的 `gRPC` 服务中使用 `etcd` 进行服务发现。 + + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带服务发现的客户端 +├── controller/ # 服务控制器 +│ └── helloworld.go # Hello 服务实现 +├── protobuf/ # protobuf协议定义 +│ └── helloworld.proto # 服务和消息定义 +├── server/ # 服务器示例 +│ └── server.go # 带服务注册的服务器 +├── go.mod # Go 模块文件 +└── go.sum # Go 模块校验和 +``` + + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Protocol Buffers](https://developers.google.com/protocol-buffers) +- [etcd](https://etcd.io/) + +## 使用说明 + +1. 启动 `etcd`: + ```bash + docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.4.24 + ``` + +2. 启动服务器: + ```bash + cd server + go run server.go + ``` + +3. 运行客户端: + ```bash + cd client + go run client.go + ``` + diff --git a/versioned_docs/version-2.8.x/examples/httpserver/httpserver.md b/versioned_docs/version-2.8.x/examples/httpserver/httpserver.md new file mode 100644 index 00000000000..dcf09f9acea --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/httpserver.md @@ -0,0 +1,28 @@ +--- +title: HTTP Server +slug: /examples/httpserver +keywords: [http, 服务器, goframe] +description: GoFrame 框架中的 HTTP 服务器示例 +hide_title: true +sidebar_position: 2 +--- + +# `HTTP Server` 示例 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver + + +## 介绍 + +本分类包含了一些使用 `GoFrame` 框架构建 `HTTP` 服务器的各种示例。每个示例都提供了详细的介绍和代码示例。 + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/httpserver/jwt/jwt.md b/versioned_docs/version-2.8.x/examples/httpserver/jwt/jwt.md new file mode 100644 index 00000000000..3dc7ec13f7a --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/jwt/jwt.md @@ -0,0 +1,89 @@ +--- +title: JWT认证 +slug: /examples/httpserver/jwt +keywords: [http, server, jwt, authentication, goframe] +description: 使用GoFrame框架实现的JWT认证示例 +hide_title: true +sidebar_position: 0 +--- + +# GoFrame JWT认证示例 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/jwt + + +本示例展示了如何在`GoFrame`HTTP服务器中使用 `github.com/golang-jwt/jwt` 包实现`JWT(JSON Web Token)`认证。 + +## 功能特性 + +- 用户登录接口,生成`JWT`令牌 +- 使用`JWT`中间件保护路由 +- 令牌验证和解析 +- 受保护资源访问示例 +- 标准的`GoFrame`项目结构 + +## 项目结构 + +```text +jwt/ +├── api/ +│ └── v1/ +│ └── auth.go # API接口定义 +├── internal/ +│ ├── controller/ +│ │ └── auth.go # 业务逻辑实现 +│ └── middleware/ +│ └── jwt.go # JWT中间件 +└── main.go # 入口文件 +``` + +## API接口 + +1. 登录接口: `POST /login` + ```json + { + "username": "admin", + "password": "password" + } + ``` + +2. 受保护资源: `GET /api/protected` + - 需要在`Authorization`头部携带`Bearer`令牌 + - 示例: `Authorization: Bearer your-token-here` + +## 运行示例 + +1. 启动服务器: + ```bash + go run main.go + ``` + +2. 服务器将在`8199`端口启动 + +## 测试API + +1. 登录获取令牌: + ```bash + curl -X POST http://localhost:8199/login \ + -H "Content-Type: application/json" \ + -d '{"username":"admin","password":"password"}' + ``` + +2. 访问受保护的接口: + ```bash + curl http://localhost:8199/api/protected \ + -H "Authorization: Bearer your-token-here" + ``` + +## 安全注意事项 + +- 在生产环境中,请替换硬编码的密钥为安全的值 +- 将用户凭证存储在数据库中 +- 实现适当的密码哈希 +- 考虑实现刷新令牌机制 +- 为登录尝试添加速率限制 + +## 参考资料 + +有关`JWT`实现的更多详细信息,请参考第三方组件文档: +- [github.com/golang-jwt/jwt](https://github.com/golang-jwt/jwt) diff --git a/versioned_docs/version-2.8.x/examples/httpserver/proxy/proxy.md b/versioned_docs/version-2.8.x/examples/httpserver/proxy/proxy.md new file mode 100644 index 00000000000..d83c2f46671 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/proxy/proxy.md @@ -0,0 +1,50 @@ +--- +title: 反向代理 +slug: /examples/httpserver/proxy +keywords: [http, 服务器, 代理, 反向代理, goframe] +description: 使用 GoFrame 框架实现反向代理服务器 +hide_title: true +sidebar_position: 1 +--- + +# HTTP 服务器代理 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/proxy + + +## 介绍 + +本示例展示了如何使用 `GoFrame` 创建一个反向代理服务器。示例包含两个服务器: + +1. 在 `8198` 端口运行的后端服务器,提供实际服务 +2. 在 `8199` 端口运行的代理服务器,将请求转发到后端服务器 + +代理服务器实现了以下功能: +- 使用 `httputil.NewSingleHostReverseProxy` 实现反向代理功能 +- 自定义代理失败的错误处理 +- `URL` 路径重写 +- 请求体处理 +- 详细的代理操作日志 + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## 使用说明 + +1. 运行服务: + ```bash + go run main.go + ``` + +2. 服务监听两个端口: + - 后端服务器在 http://127.0.0.1:8198 + - 代理服务器在 http://127.0.0.1:8199 + +3. 测试代理: + - 通过代理访问:http://127.0.0.1:8199/proxy/user/1 + - 直接访问后端:http://127.0.0.1:8198/user/1 + diff --git a/versioned_docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md b/versioned_docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md new file mode 100644 index 00000000000..384b8823ebe --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/rate-limit/rate-limit.md @@ -0,0 +1,46 @@ +--- +title: 访问限流 +slug: /examples/httpserver/rate-limit +keywords: [http, 服务器, 限流, 中间件, goframe] +description: 使用 GoFrame 框架实现 HTTP 服务器限流 +hide_title: true +sidebar_position: 2 +--- + +# HTTP 服务器限流 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/rate-limit + + +## 介绍 + +本示例展示了如何在使用 `GoFrame` 的 HTTP 服务器中实现限流。 +它展示了如何使用 `golang.org/x/time/rate` 包实现的令牌桶算法来保护 `API` 端点免受过多请求的影响。 + + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## 目录结构 + +- `go.mod`: Go 模块文件 +- `main.go`: 主程序入口 + +## 使用说明 + +1. 启动服务器: + ```bash + go run main.go + ``` + +2. 测试限流: + ```bash + # 正常请求 + curl http://localhost:8199/hello?name=world + + # 快速发送多个请求测试限流 + for i in {1..20}; do curl http://localhost:8199/hello?name=world; done + ``` diff --git a/versioned_docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md b/versioned_docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md new file mode 100644 index 00000000000..266e89fb1e4 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/response-json-array/response-json-array.md @@ -0,0 +1,45 @@ +--- +title: JSON数组响应 +slug: /examples/httpserver/response-json-array +keywords: [http, 服务器, json, 数组, goframe] +description: 使用 GoFrame 框架处理 HTTP 服务器的 JSON 数组响应 +hide_title: true +sidebar_position: 99 +--- + +# HTTP 服务器 JSON 数组响应 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/response-json-array + + +## 介绍 + +本示例展示了如何使用 `GoFrame` 实现一个返回 `JSON` 数组响应的 `HTTP` 服务器。它展示了如何: +- 将 `API` 响应构造为 `JSON` 数组 +- 配置 `OpenAPI/Swagger` 文档 +- 使用 `GoFrame` 的中间件实现一致的响应处理 +- 定义类型安全的请求和响应结构 + +本示例实现了一个返回 `JSON` 数组格式用户列表的 `/user` 端点。 + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## 目录结构 + +- `go.mod`: Go 模块文件 +- `main.go`: 主程序入口 + +## 使用说明 + +1. 启动服务: + ```bash + go run main.go + ``` + +2. 测试接口:http://localhost:8199/user + +3. 接口文档:http://127.0.0.1:8199/swagger diff --git a/versioned_docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md b/versioned_docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md new file mode 100644 index 00000000000..2cd903ef60c --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/upload-file/upload-file.md @@ -0,0 +1,50 @@ +--- +title: 文件上传 +slug: /examples/httpserver/upload-file +keywords: [http, 服务器, 文件, 上传, goframe] +description: 使用 GoFrame 框架处理 HTTP 服务器的文件上传 +hide_title: true +sidebar_position: 3 +--- + +# HTTP 服务器文件上传 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/upload-file + + +## 介绍 + +本示例展示了如何使用 `GoFrame` 在 HTTP 服务器中实现文件上传功能。它展示了: +- 现代化且用户友好的文件上传界面 +- 服务器端文件上传处理 +- 文件上传进度跟踪 +- 适当的错误处理和验证 +- 最大文件大小配置 + +本示例同时提供了 `REST API` 端点和 `Web` 界面用于文件上传。 + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) + +## 目录结构 + +- `go.mod`: Go 模块文件,用于依赖管理 +- `main.go`: 主程序入口 +- `resource/`: 静态资源目录 + - `public/`: 公共资源 + - `upload.html`: 上传页面 + - `upload/`: 上传文件存储目录 + +## 使用说明 + +1. 启动服务: + ```bash + go run main.go + ``` + +2. 上传页面:http://localhost:8199/upload.html + + diff --git a/versioned_docs/version-2.8.x/examples/httpserver/websocket/websocket.md b/versioned_docs/version-2.8.x/examples/httpserver/websocket/websocket.md new file mode 100644 index 00000000000..a800699f568 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/httpserver/websocket/websocket.md @@ -0,0 +1,148 @@ +--- +title: WebSocket +slug: /examples/httpserver/websocket +keywords: [websocket, server, client, goframe, https] +description: 使用GoFrame框架实现的WebSocket服务器和客户端 +hide_title: true +sidebar_position: 5 +--- + +# `WebSocket` 服务器和客户端 + +Github Source: https://github.com/gogf/examples/tree/main/httpserver/websocket + + +## 描述 + +本示例展示了如何使用 `GoFrame` 实现 `WebSocket` 通信。示例包含 `HTTP` 和 `HTTPS` 两种实现,主要组件包括: + +1. 支持安全和非安全连接的 `WebSocket` 服务器 +2. 支持 `HTTP` 和 `HTTPS` 的 `WebSocket` 客户端实现 +3. `WebSocket` 消息交换示例 + +实现展示了以下功能: +- 基本的 `WebSocket` 服务器设置 +- 安全 `WebSocket`(`WSS`)实现 +- 客户端连接处理 +- 客户端和服务器之间的消息交换 +- 正确的连接清理 + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [Gorilla WebSocket](https://github.com/gorilla/websocket) + +## 目录结构 + +```text +websocket/ +├── http/ +│ ├── client.go # HTTP WebSocket客户端 +│ ├── server.go # HTTP WebSocket服务器 +│ └── static/ # 静态文件目录 +├── https/ +│ ├── client.go # HTTPS WebSocket客户端 +│ ├── server.go # HTTPS WebSocket服务器 +│ ├── static/ # 静态文件目录 +│ └── certs/ # SSL证书 +└── README.MD +``` + +## 功能特性 + +- `WebSocket` 服务器实现 +- 安全 `WebSocket`(`WSS`)支持 +- 客户端连接处理 +- 消息回显功能 +- 连接生命周期管理 +- 来源检查(可配置) +- 错误处理 + +## 安装设置 + +1. 克隆仓库: + ```bash + git clone https://github.com/gogf/examples.git + cd examples/httpserver/websocket + ``` + +2. 安装依赖: + ```bash + go mod tidy + ``` + +3. 对于`HTTPS/WSS`支持,生成自签名证书(可选): + ```bash + mkdir -p https/certs + openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout https/certs/server.key -out https/certs/server.crt + ``` + +## 使用方法 + +### HTTP WebSocket + +1. 启动服务器: + ```bash + cd http + go run server.go + ``` + +2. 运行客户端: + ```bash + cd http + go run client.go + ``` + +3. 通过浏览器访问演示页面: + + 在浏览器中打开 `http://127.0.0.1:8000` 查看 `WebSocket` 演示效果。页面提供了一个简单的聊天界面,可以实时发送和接收消息。 + +### HTTPS WebSocket + +1. 启动安全服务器: + ```bash + cd https + go run server.go + ``` + +2. 运行安全客户端: + ```bash + cd https + go run client.go + ``` + +3. 通过浏览器访问演示页面: + + 在浏览器中打开 `https://127.0.0.1:8000` 查看 `WebSocket` 演示效果。 + 注意:由于使用自签名证书,浏览器可能会显示安全警告,这是正常的。 + +## 实现细节 + +### 服务器特性 +- `WebSocket` 升级处理 +- 消息回显功能 +- 连接生命周期管理 +- 可配置的来源检查 +- 错误处理和日志记录 + +### 客户端特性 +- 连接建立 +- 消息发送和接收 +- 安全连接的 `TLS` 配置 +- 清理的连接关闭 + +## 注意事项 + +- `WebSocket` 服务器会回显收到的任何消息 +- `HTTPS`/`WSS` 示例中使用了自签名证书 +- 在生产环境中,需要实现适当的来源检查 +- 正确处理连接关闭以避免资源泄漏 + +## 更多信息 + +更多关于`WebSocket`实现的详细信息,请参考: +- [GoFrame WebSocket指南](https://goframe.org/docs/web/senior-websocket) +- [Gorilla WebSocket文档](https://github.com/gorilla/websocket) + diff --git a/versioned_docs/version-2.8.x/examples/nosql/mongodb/mongodb.md b/versioned_docs/version-2.8.x/examples/nosql/mongodb/mongodb.md new file mode 100644 index 00000000000..c3c791282e0 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/nosql/mongodb/mongodb.md @@ -0,0 +1,94 @@ +--- +title: MongoDB +slug: /examples/nosql/mongodb +keywords: [nosql, mongodb, database, goframe] +description: 在 GoFrame 中使用 MongoDB 的示例 +hide_title: true +sidebar_position: 2 +--- + +# GoFrame MongoDB 示例 + +Github Source: https://github.com/gogf/examples/tree/main/nosql/mongodb + + +此示例演示了如何在 `GoFrame` 框架中使用 `MongoDB`。 + +## 概述 + +本示例展示了: +1. 如何使用 `YAML` 配置文件配置 `MongoDB` 连接 +2. 如何创建 `MongoDB` 客户端 +3. 基本的 `MongoDB` 操作(`INSERT`、`FIND`、`UPDATE`) +4. 如何使用 `BSON` 进行文档操作 + +## 环境要求 + +- `Go 1.15` 或更高版本 +- `MongoDB` 服务器 +- `GoFrame v2` + +## 代码结构 + +- `main.go`: 包含主要逻辑和 `MongoDB` 客户端初始化 +- `config.yaml`: `MongoDB` 配置文件 + + +## 配置说明 + +`MongoDB` 配置存储在 `config.yaml` 文件中: + +```yaml +mongo: + database: "user" + address: "mongodb://127.0.0.1:27017/test?retryWrites=true" +``` + +您可以根据自己的 `MongoDB` 服务器配置修改这些设置。 + +## 使用 Docker 运行 MongoDB + +如果您本地没有安装 `MongoDB`,可以使用 `Docker` 快速启动一个 `MongoDB` 实例: + +```bash +# 运行 MongoDB 容器 +docker run --name mongo-test -p 27017:27017 -d mongo:latest + +# 验证容器是否正在运行 +docker ps + +# 如果需要停止容器 +docker stop mongo-test + +# 如果需要删除容器 +docker rm mongo-test +``` + +如果需要带认证的 `MongoDB`: + +```bash +# 运行带认证的 MongoDB +docker run --name mongo-test -p 27017:27017 -e MONGO_INITDB_ROOT_USERNAME=admin -e MONGO_INITDB_ROOT_PASSWORD=password -d mongo:latest + +# 记得相应地更新 config.yaml: +# mongo: +# database: "user" +# address: "mongodb://admin:password@127.0.0.1:27017/test?retryWrites=true" +``` + +## 运行示例 + +1. 确保 `MongoDB` 服务器正在运行 +2. 根据需要更新 `config.yaml` +3. 运行示例: + +```bash +go run main.go +``` + + + + +## 更多参考 + +更多 `MongoDB` 的使用方法请参考官方 `MongoDB` Go 驱动 [github.com/mongodb/mongo-go-driver](https://github.com/mongodb/mongo-go-driver)。 diff --git a/versioned_docs/version-2.8.x/examples/nosql/nosql.md b/versioned_docs/version-2.8.x/examples/nosql/nosql.md new file mode 100644 index 00000000000..3f7e2088091 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/nosql/nosql.md @@ -0,0 +1,28 @@ +--- +title: NoSQL +slug: /examples/nosql +keywords: [nosql, mongodb, redis, database, goframe] +description: 在 GoFrame 中使用 NoSQL 数据库的示例 +hide_title: true +sidebar_position: 0 +--- + +# `NoSQL` 示例 + +Github Source: https://github.com/gogf/examples/tree/main/nosql + + +## 介绍 + +本分类包含了一些在 `GoFrame` 框架中使用 `NoSQL` 数据库的示例。 + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/nosql/redis/redis.md b/versioned_docs/version-2.8.x/examples/nosql/redis/redis.md new file mode 100644 index 00000000000..422568aa45b --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/nosql/redis/redis.md @@ -0,0 +1,92 @@ +--- +title: Redis +slug: /examples/nosql/redis +keywords: [nosql, redis, cache, database, goframe] +description: 在 GoFrame 中使用 Redis 的示例 +hide_title: true +sidebar_position: 1 +--- + +# GoFrame Redis 示例 + +Github Source: https://github.com/gogf/examples/tree/main/nosql/redis + + +此示例演示了如何在 `GoFrame` 框架中使用 `Redis`。 + +## 概述 + +本示例展示了: +1. 如何使用 `YAML` 配置文件配置 `Redis` 连接 +2. 如何创建 `Redis` 客户端 +3. 基本的 `Redis` 操作(`SET/GET`) + +## 环境要求 + +- `Go 1.15` 或更高版本 +- `Redis` 服务器 +- `GoFrame v2` + +## 代码结构 + +- `main.go`: 包含主要逻辑和 `Redis` 客户端初始化 +- `config.yaml`: `Redis` 配置文件 + +## 配置说明 + +`Redis` 配置存储在 `config.yaml` 文件中: + +```yaml +redis: + address: "127.0.0.1:6379" + password: +``` + +您可以根据自己的 `Redis` 服务器配置修改这些设置。 + +## 使用 Docker 运行 Redis + +如果您本地没有安装 `Redis`,可以使用 `Docker` 快速启动一个 `Redis` 实例: + +```bash +# 运行 Redis 容器 +docker run --name redis-test -p 6379:6379 -d redis:latest + +# 验证容器是否正在运行 +docker ps + +# 如果需要停止容器 +docker stop redis-test + +# 如果需要删除容器 +docker rm redis-test +``` + +如果需要带密码认证的 `Redis`: + +```bash +# 运行带密码的 Redis +docker run --name redis-test -p 6379:6379 -d redis:latest redis-server --requirepass your_password + +# 记得相应地更新 config.yaml: +# redis: +# address: "127.0.0.1:6379" +# password: "your_password" +``` + +## 运行示例 + +1. 确保 `Redis` 服务器正在运行 +2. 根据需要更新 `config.yaml` +3. 运行示例: + +```bash +go run main.go +``` + + + + +## 更多参考 + +更多 `Redis` 的使用方法请参考第三方组件 [github.com/redis/go-redis](https://github.com/redis/go-redis)。 diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/basic/basic.md b/versioned_docs/version-2.8.x/examples/observability/metric/basic/basic.md new file mode 100644 index 00000000000..8c8715931b4 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/basic/basic.md @@ -0,0 +1,148 @@ +--- +title: 基础用法 +slug: /examples/observability/metric/basic +keywords: [指标, 基础, prometheus, opentelemetry, goframe] +description: 在GoFrame中演示各种指标类型及其使用的基础示例 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - 基础用法 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/basic + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来实现基础的指标收集功能。主要展示: +- 创建和使用不同类型的指标 +- 配置指标属性 +- 导出 `Prometheus` 格式的指标 +- 设置指标暴露端点 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示指标使用 +``` + +## 功能特性 + +本示例展示了以下指标类型: + +1. Counter (计数器) + - 累积型测量 + - 只能增加 + - 用于事件计数 + - 示例:请求总数、字节数 + +2. UpDownCounter (上下计数器) + - 双向计数器 + - 可增可减 + - 用于测量变化量 + - 示例:队列长度、连接数 + +3. Histogram (直方图) + - 测量值分布 + - 可配置的桶 + - 用于延迟/大小测量 + - 示例:请求延迟、响应大小 + +4. Observable指标 + - ObservableCounter + - ObservableUpDownCounter + - ObservableGauge + - 通过回调更新 + - 示例:CPU使用率、内存使用 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_metric_demo_counter 这是Counter使用的简单演示 + goframe_metric_demo_counter{const_attr_1="1"} 11 + + # HELP goframe_metric_demo_histogram 这是histogram使用的简单演示 + goframe_metric_demo_histogram_bucket{const_attr_3="3",le="0"} 0 + goframe_metric_demo_histogram_bucket{const_attr_3="3",le="10"} 1 + ... + ``` + +## 实现说明 + +1. Counter实现 + ```go + counter := meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "计数器使用示例", + Unit: "bytes", + }, + ) + counter.Inc(ctx) // 增加1 + counter.Add(ctx, 10) // 增加指定值 + ``` + +2. UpDownCounter实现 + ```go + upDownCounter := meter.MustUpDownCounter( + "goframe.metric.demo.updown_counter", + gmetric.MetricOption{ + Help: "上下计数器使用示例", + Unit: "%", + }, + ) + upDownCounter.Inc(ctx) // 增加1 + upDownCounter.Dec(ctx) // 减少1 + ``` + +3. Histogram实现 + ```go + histogram := meter.MustHistogram( + "goframe.metric.demo.histogram", + gmetric.MetricOption{ + Help: "直方图使用示例", + Unit: "ms", + Buckets: []float64{0, 10, 20, 50, 100}, + }, + ) + histogram.Record(30) // 记录测量值 + ``` + +4. Observable指标实现 + ```go + observableGauge := meter.MustObservableGauge( + "goframe.metric.demo.observable_gauge", + gmetric.MetricOption{ + Help: "可观察量表使用示例", + Unit: "%", + }, + ) + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableGauge, 30) + return nil + }, observableGauge) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/callback/callback.md b/versioned_docs/version-2.8.x/examples/observability/metric/callback/callback.md new file mode 100644 index 00000000000..c049157d40d --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/callback/callback.md @@ -0,0 +1,113 @@ +--- +title: 回调处理 +slug: /examples/observability/metric/callback +keywords: [指标, 回调, prometheus, opentelemetry, goframe] +description: GoFrame 中基于回调的指标收集实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - 回调处理 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/callback + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来实现基于回调的指标收集。主要展示: +- 创建带有回调函数的指标 +- 通过回调自动更新指标值 +- 配置指标属性 +- 导出 `Prometheus` 格式的指标 + +## 目录结构 + +```text +. +├── go.mod # Go 模块文件 +├── go.sum # Go 模块校验和 +└── main.go # 主程序,演示回调式指标使用 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 基于回调的指标 + - 自动值更新 + - 自定义回调函数 + - 无需手动更新 + - 适用于动态值监控 + +2. 常规指标 + - 手动值更新 + - 直接控制值 + - 与回调方式对比 + - 适用于事件计数 + +3. 指标配置 + - 自定义属性 + - 帮助文本 + - 单位设置 + - 常量标签 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用 curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_metric_demo_counter 这是 Counter 使用的简单演示 + goframe_metric_demo_counter{const_attr_1="1"} 11 + + # HELP goframe_metric_demo_observable_counter 这是 ObservableCounter 使用的简单演示 + goframe_metric_demo_observable_counter{const_attr_3="3"} 10 + ``` + +## 实现说明 + +1. 回调式指标实现 + ```go + observableCounter := meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "回调计数器示例", + Unit: "%", + Callback: func(ctx context.Context, obs gmetric.MetricObserver) error { + obs.Observe(10) // 自动设置值为 10 + return nil + }, + }, + ) + ``` + +2. 常规指标实现 + ```go + counter := meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "普通计数器示例", + Unit: "%", + }, + ) + counter.Inc(ctx) // 手动增加 1 + counter.Add(ctx, 10) // 手动增加指定值 + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md b/versioned_docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md new file mode 100644 index 00000000000..82f8ba280f7 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/dynamic_attributes/dynamic_attributes.md @@ -0,0 +1,130 @@ +--- +title: 动态属性 +slug: /examples/observability/metric/dynamic_attributes +keywords: [指标, 动态属性, prometheus, opentelemetry, goframe] +description: GoFrame中动态指标属性的实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - 动态属性 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/dynamic_attributes + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来实现动态指标属性。主要展示: +- 在运行时为指标添加动态属性 +- 组合使用常量属性和动态属性 +- 在常规指标和可观察指标中使用属性 +- 导出带有动态属性的指标 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示动态属性使用 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 动态属性 + - 运行时属性赋值 + - 属性组合使用 + - 基于值的属性 + - 灵活的标签管理 + +2. 带属性的指标类型 + - 带动态属性的计数器 + - 带动态属性的可观察计数器 + - 常量属性基线 + - 属性继承机制 + +3. 属性管理 + - 属性创建 + - 值类型处理 + - 属性作用域 + - 属性生命周期 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_metric_demo_counter 这是Counter使用的简单演示 + goframe_metric_demo_counter{const_attr_1="1",dynamic_attr_2="2"} 11 + + # HELP goframe_metric_demo_observable_counter 这是ObservableCounter使用的简单演示 + goframe_metric_demo_observable_counter{const_attr_4="4",dynamic_attr_1="1"} 10 + ``` + +## 实现说明 + +1. 常规指标的动态属性 + ```go + counter := meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "计数器示例", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("const_attr_1", 1), // 常量属性 + }, + }, + ) + + // 添加动态属性 + counter.Add(ctx, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_2", 2), // 动态属性 + }, + }) + ``` + +2. 可观察指标的动态属性 + ```go + observableCounter := meter.MustObservableCounter( + "goframe.metric.demo.observable_counter", + gmetric.MetricOption{ + Help: "可观察计数器示例", + Unit: "%", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("const_attr_4", 4), // 常量属性 + }, + }, + ) + + // 在回调中添加动态属性 + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10, gmetric.Option{ + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("dynamic_attr_1", 1), // 动态属性 + }, + }) + return nil + }, observableCounter) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md b/versioned_docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md new file mode 100644 index 00000000000..f99cc3a18ed --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/global_attributes/global_attributes.md @@ -0,0 +1,118 @@ +--- +title: 全局属性 +slug: /examples/observability/metric/global_attributes +keywords: [指标, 全局属性, prometheus, opentelemetry, goframe] +description: GoFrame中全局指标属性的实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - 全局属性 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/global_attributes + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来实现全局指标属性。主要展示: +- 设置和管理全局属性 +- 在多个指标中应用全局属性 +- 配置属性作用域和匹配模式 +- 组合使用全局属性和局部属性 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示全局属性使用 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 全局属性 + - 全局属性配置 + - 属性继承机制 + - 基于模式的应用 + - 版本控制支持 + +2. 带全局属性的指标类型 + - 带全局属性的计数器 + - 带全局属性的可观察计数器 + - 局部属性组合 + - 属性优先级 + +3. 属性管理 + - 全局属性作用域 + - 基于版本的过滤 + - 基于模式的过滤 + - 属性继承规则 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_metric_demo_counter 这是Counter使用的简单演示 + goframe_metric_demo_counter{const_attr_1="1",global_attr_1="1"} 11 + + # HELP goframe_metric_demo_observable_counter 这是ObservableCounter使用的简单演示 + goframe_metric_demo_observable_counter{const_attr_2="2",global_attr_1="1"} 10 + ``` + +## 实现说明 + +1. 设置全局属性 + ```go + gmetric.SetGlobalAttributes(gmetric.Attributes{ + gmetric.NewAttribute("global_attr_1", 1), // 全局属性 + }, gmetric.SetGlobalAttributesOption{ + Instrument: instrument, // 仅应用于特定的instrument + InstrumentVersion: instrumentVersion, // 仅应用于特定版本 + InstrumentPattern: "", // 空模式表示应用于所有指标 + }) + ``` + +2. 带全局属性的指标 + ```go + counter := meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "计数器示例", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("const_attr_1", 1), // 局部常量属性 + }, + }, + ) + ``` + +3. 在可观察指标中使用全局属性 + ```go + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) // 全局属性会自动添加 + return nil + }, observableCounter) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/http_client/http_client.md b/versioned_docs/version-2.8.x/examples/observability/metric/http_client/http_client.md new file mode 100644 index 00000000000..50bf3f3d8bd --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/http_client/http_client.md @@ -0,0 +1,118 @@ +--- +title: HTTP客户端 +slug: /examples/observability/metric/http_client +keywords: [指标, http客户端, prometheus, opentelemetry, goframe] +description: GoFrame中HTTP客户端指标收集的实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - HTTP客户端 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/http_client + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来收集和监控HTTP客户端指标。主要展示: +- 监控HTTP客户端请求 +- 跟踪请求持续时间 +- 收集响应状态码 +- 导出客户端指标数据 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示HTTP客户端指标收集 +``` + +## 功能特性 + +本示例展示了以下指标: + +1. 请求指标 + - 总请求数(Total Requests Count) + - 活跃请求数(Active Requests) + - 请求持续时间(Request Duration) + - 请求大小(Request Size) + +2. 响应指标 + - 响应状态码(Response Status Codes) + - 响应大小(Response Size) + - 错误计数(Error Count) + - 响应持续时间(Response Duration) + +3. 连接指标 + - 连接池统计(Connection Pool Stats) + - DNS查询持续时间(DNS Lookup Duration) + - TLS握手持续时间(TLS Handshake Duration) + - 连接建立时间(Connection Establishment Time) + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_http_client_request_duration_seconds HTTP请求的持续时间 + goframe_http_client_request_duration_seconds_bucket{method="GET",status="200",url="https://goframe.org",le="0.1"} 1 + + # HELP goframe_http_client_requests_total HTTP请求的总数 + goframe_http_client_requests_total{method="GET",status="200",url="https://goframe.org"} 1 + + # HELP goframe_http_client_response_size_bytes HTTP响应的大小 + goframe_http_client_response_size_bytes{method="GET",status="200",url="https://goframe.org"} 12345 + ``` + +## 实现说明 + +1. 配置指标导出器 + ```go + // 创建Prometheus导出器,用于指标数据的导出 + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), // 移除计数器后缀以保持指标名称简洁 + prometheus.WithoutUnits(), // 移除单位后缀以保持指标名称简洁 + ) + if err != nil { + g.Log().Fatal(ctx, err) + } + ``` + +2. 初始化OpenTelemetry提供者 + ```go + // 初始化并配置OpenTelemetry提供者 + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), // 配置指标读取器 + otelmetric.WithBuiltInMetrics(), // 启用内置指标收集 + ) + provider.SetAsGlobal() // 设置为全局指标提供者 + defer provider.Shutdown(ctx) // 确保程序退出时正确关闭提供者 + ``` + +3. 使用HTTP客户端 + ```go + // 发起HTTP请求并自动收集指标 + url := `https://goframe.org` + content := g.Client().GetContent(ctx, url) // 发起HTTP GET请求 + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/http_server/http_server.md b/versioned_docs/version-2.8.x/examples/observability/metric/http_server/http_server.md new file mode 100644 index 00000000000..b7bfd7d6078 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/http_server/http_server.md @@ -0,0 +1,150 @@ +--- +title: HTTP服务器 +slug: /examples/observability/metric/http_server +keywords: [指标, http服务器, prometheus, opentelemetry, goframe] +description: GoFrame中HTTP服务器指标收集的实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - HTTP服务器 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/http_server + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来收集和监控HTTP服务器指标。主要展示: +- 监控HTTP服务器请求 +- 跟踪请求延迟 +- 收集错误率 +- 导出服务器端指标 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示HTTP服务器指标收集 +``` + +## 功能特性 + +本示例展示了以下端点和指标: + +1. 端点(Endpoints) + - `/`: 基础端点,返回"ok" + - `/error`: 触发错误的端点 + - `/sleep`: 带5秒延迟的端点 + - `/metrics`: Prometheus指标端点 + +2. 请求指标 + - 总请求数(Total Requests Count) + - 活跃请求数(Active Requests) + - 请求持续时间(Request Duration) + - 请求大小(Request Size) + +3. 响应指标 + - 响应状态码(Response Status Codes) + - 响应大小(Response Size) + - 错误计数(Error Count) + - 响应延迟(Response Latency) + +4. 服务器指标 + - 协程数量(Goroutine Count) + - 内存使用(Memory Usage) + - GC统计(GC Statistics) + - 连接统计(Connection Stats) + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 测试不同端点: + ```bash + # 基础请求 + curl http://localhost:8000/ + + # 错误请求 + curl http://localhost:8000/error + + # 慢请求 + curl http://localhost:8000/sleep + ``` + +3. 查看指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +4. 指标输出示例: + ```text + # HELP goframe_http_server_requests_total HTTP请求总数 + goframe_http_server_requests_total{method="GET",path="/",status="200"} 1 + + # HELP goframe_http_server_request_duration_seconds HTTP请求持续时间 + goframe_http_server_request_duration_seconds_bucket{method="GET",path="/sleep",status="200",le="5.0"} 1 + + # HELP goframe_http_server_panics_total 导致panic的HTTP请求总数 + goframe_http_server_panics_total{method="GET",path="/error"} 1 + ``` + +## 实现说明 + +1. 配置指标导出器 + ```go + // 创建Prometheus导出器,用于指标数据的导出 + exporter, err := prometheus.New( + prometheus.WithoutCounterSuffixes(), // 移除计数器后缀以保持指标名称简洁 + prometheus.WithoutUnits(), // 移除单位后缀以保持指标名称简洁 + ) + ``` + +2. 初始化OpenTelemetry提供者 + ```go + // 初始化并配置OpenTelemetry提供者 + provider := otelmetric.MustProvider( + otelmetric.WithReader(exporter), // 配置指标读取器 + otelmetric.WithBuiltInMetrics(), // 启用内置指标收集 + ) + provider.SetAsGlobal() // 设置为全局指标提供者 + defer provider.Shutdown(ctx) // 确保程序退出时正确关闭提供者 + ``` + +3. 配置HTTP服务器端点 + ```go + s := g.Server() + + // 基础端点 + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("ok") + }) + + // 错误端点 + s.BindHandler("/error", func(r *ghttp.Request) { + panic("error") + }) + + // 慢请求端点 + s.BindHandler("/sleep", func(r *ghttp.Request) { + time.Sleep(time.Second * 5) + r.Response.Write("ok") + }) + + // 指标端点 + s.BindHandler("/metrics", otelmetric.PrometheusHandler) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md b/versioned_docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md new file mode 100644 index 00000000000..6a4cd742dc7 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/meter_attributes/meter_attributes.md @@ -0,0 +1,119 @@ +--- +title: 指标计量器属性 +slug: /examples/observability/metric/meter_attributes +keywords: [指标, 计量器属性, prometheus, opentelemetry, goframe] +description: GoFrame中指标计量器级别属性的实现 +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - 计量器属性 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/meter_attributes + + +## 简介 + +本示例演示了如何在 `GoFrame` 中使用 `OpenTelemetry` 和 `Prometheus` 集成来配置和管理指标计量器级别的属性。主要展示: +- 配置计量器级别属性 +- 在所有指标中应用属性 +- 组合计量器和指标属性 +- 管理属性继承关系 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── main.go # 主程序,演示计量器属性使用 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 计量器属性 + - 全局计量器配置 + - 计量器级别属性 + - 属性继承机制 + +2. 带继承属性的指标类型 + - 带组合属性的计数器(Counter) + - 带组合属性的可观察计数器(Observable Counter) + - 属性优先级规则 + +3. 属性管理 + - 计量器属性配置 + - 基于版本的属性 + - 属性组合方式 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry Metric` 扩展 + +## 使用说明 + +1. 运行示例: + ```bash + go run main.go + ``` + +2. 访问指标: + ```bash + # 使用curl + curl http://localhost:8000/metrics + + # 或在浏览器中打开 + http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP goframe_metric_demo_counter 这是Counter用法的简单演示 + goframe_metric_demo_counter{const_attr_1="1",meter_label_1="1",meter_label_2="2"} 11 + + # HELP goframe_metric_demo_observable_counter 这是ObservableCounter用法的简单演示 + goframe_metric_demo_observable_counter{const_attr_2="2",meter_label_1="1",meter_label_2="2"} 10 + ``` + +## 实现说明 + +1. 配置计量器属性 + ```go + // 创建全局计量器实例,配置计量器级别属性 + meter = gmetric.GetGlobalProvider().Meter(gmetric.MeterOption{ + Instrument: instrument, // 指标标识符 + InstrumentVersion: instrumentVersion, // 指标版本 + Attributes: gmetric.Attributes{ // 计量器级别属性 + gmetric.NewAttribute("meter_label_1", 1), + gmetric.NewAttribute("meter_label_2", 2), + }, + }) + ``` + +2. 创建带属性的指标 + ```go + // 创建Counter指标,包含计量器属性和指标特定属性 + counter = meter.MustCounter( + "goframe.metric.demo.counter", + gmetric.MetricOption{ + Help: "这是Counter用法的简单演示", + Unit: "bytes", + Attributes: gmetric.Attributes{ + gmetric.NewAttribute("const_attr_1", 1), + }, + }, + ) + ``` + +3. 注册可观察指标回调 + ```go + // 注册回调函数,自动继承计量器属性 + meter.MustRegisterCallback(func(ctx context.Context, obs gmetric.Observer) error { + obs.Observe(observableCounter, 10) + return nil + }, observableCounter) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/metric.md b/versioned_docs/version-2.8.x/examples/observability/metric/metric.md new file mode 100644 index 00000000000..8090eaa59e7 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/metric.md @@ -0,0 +1,28 @@ +--- +title: 指标收集 +slug: /examples/observability/metric +keywords: [指标, 监控, prometheus, opentelemetry, goframe] +description: GoFrame 框架中的指标收集功能示例 +hide_title: true +--- + +# 指标收集示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric + + +## 介绍 + +本分类包含了一些在 GoFrame 框架中实现指标收集相关的示例代码。这些示例展示了如何在不同场景下使用 GoFrame 的指标收集功能。 + + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md b/versioned_docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md new file mode 100644 index 00000000000..290ef1108e4 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/metric/prometheus/prometheus.md @@ -0,0 +1,129 @@ +--- +title: Prometheus集成 +slug: /examples/observability/metric/prometheus +keywords: [指标, prometheus, 直接集成, goframe] +description: GoFrame中直接集成Prometheus(不使用OpenTelemetry) +hide_title: true +sidebar_position: 1 +--- + +# 指标收集 - Prometheus直接集成 + +Github Source: https://github.com/gogf/examples/tree/main/observability/metric/prometheus + + +## 简介 + +本示例演示了如何在 `GoFrame` 中直接集成 `Prometheus` 指标,而不使用 `OpenTelemetry`。主要展示: +- 直接创建Prometheus指标 +- 向Prometheus注册表注册指标 +- 通过HTTP端点暴露指标 +- 动态更新指标值 + +## 目录结构 + +```text +. +├── go.mod # Go模块文件 +├── go.sum # Go模块校验和 +└── prometheus.go # 主程序,演示Prometheus直接集成 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 指标类型 + - 计数器(Counter):单调递增的值 + - 仪表盘(Gauge):可上下变化的值 + +2. 指标操作 + - 指标注册 + - 值更新 + - HTTP暴露 + - 随机值生成 + +3. HTTP端点 + - `/`: 触发指标更新 + - `/metrics`: 暴露Prometheus指标 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `Prometheus` 客户端库 + +## 使用说明 + +1. 运行示例: + ```bash + go run prometheus.go + ``` + +2. 测试应用: + ```bash + # 生成一些指标值 + curl http://localhost:8000/ + + # 查看指标 + curl http://localhost:8000/metrics + ``` + +3. 指标输出示例: + ```text + # HELP demo_counter 这是一个演示计数器 + # TYPE demo_counter counter + demo_counter 1 + + # HELP demo_gauge 这是一个演示仪表盘 + # TYPE demo_gauge gauge + demo_gauge 42 + ``` + +## 实现说明 + +1. 定义指标变量 + ```go + // 创建计数器类型指标 + metricCounter = promauto.NewCounter( + prometheus.CounterOpts{ + Name: "demo_counter", // Prometheus格式的指标名称 + Help: "这是一个演示计数器", // 指标描述 + }, + ) + + // 创建仪表盘类型指标 + metricGauge = promauto.NewGauge( + prometheus.GaugeOpts{ + Name: "demo_gauge", // Prometheus格式的指标名称 + Help: "这是一个演示仪表盘", // 指标描述 + }, + ) + ``` + +2. 注册指标 + ```go + // 创建Prometheus注册表 + registry := prometheus.NewRegistry() + + // 注册指标 + registry.MustRegister( + metricCounter, // 注册计数器指标 + metricGauge, // 注册仪表盘指标 + ) + ``` + +3. 配置HTTP服务器 + ```go + s := g.Server() + + // 更新指标的处理器 + s.BindHandler("/", func(r *ghttp.Request) { + metricCounter.Add(1) // 增加计数器 + metricGauge.Set(float64(grand.N(1, 100))) // 设置随机仪表盘值 + r.Response.Write("fake ok") + }) + + // 暴露指标的处理器 + s.BindHandler("/metrics", ghttp.WrapH(promhttp.Handler())) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/observability.md b/versioned_docs/version-2.8.x/examples/observability/observability.md new file mode 100644 index 00000000000..8f23191f963 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/observability.md @@ -0,0 +1,24 @@ +--- +title: 可观测性 +slug: /examples/observability +keywords: [可观测性, 监控, 指标, 追踪, goframe] +description: GoFrame 框架中的可观测性示例 +hide_title: true +sidebar_position: 6 +--- + +# 可观测性示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability + + +## 介绍 + +本分类包含了一些使用 `GoFrame` 框架实现可观测性的各种示例。 + +## 示例列表 + + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md b/versioned_docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md new file mode 100644 index 00000000000..7c1190535df --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/grpc-with-db/grpc-with-db.md @@ -0,0 +1,178 @@ +--- +title: gRPC带数据库 +slug: /examples/observability/trace/grpc-with-db +keywords: [链路跟踪, grpc, 数据库, opentelemetry, goframe] +description: GoFrame中gRPC服务与数据库操作的分布式跟踪实现 +hide_title: true +sidebar_position: 1 +--- + +# 链路跟踪 - gRPC带数据库 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/grpc-with-db + + +## 简介 + +本示例演示了如何在 `GoFrame` 中实现gRPC服务与数据库交互的分布式跟踪。主要展示: +- 配置gRPC服务的跟踪 +- 跟踪数据库操作 +- 传播跟踪上下文 +- 可视化分布式跟踪 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame gRPCx` +- `GoFrame MySQL` 驱动 +- `GoFrame Etcd` 注册中心 +- `GoFrame OpenTelemetry` 跟踪 + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带跟踪的客户端 +├── controller/ # 服务控制器 +├── protobuf/ # 协议定义 +│ └── user/ # 用户服务proto文件 +├── server/ # 服务端示例 +│ ├── server.go # 带跟踪的服务端 +│ └── config.yaml # 服务端配置 +├── sql.sql # 数据库模式 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + + +## 前置条件 + +1. 运行 `MySQL` 实例: + ```bash + docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + mysql:5.7 + ``` + +2. 初始化数据库模式: + ```bash + # 连接MySQL容器 + docker exec -i mysql mysql -uroot -p12345678 test < sql.sql + ``` + +3. 运行 `Redis` 实例: + ```bash + docker run -d --name redis \ + -p 6379:6379 \ + redis:6.0 + ``` + +4. 运行 `Jaeger` 实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +5. 安装 `Protocol buffer` 编译器: + ```bash + # macOS + brew install protobuf + + # 安装protoc-gen-go和protoc-gen-go-grpc + go install google.golang.org/protobuf/cmd/protoc-gen-go@latest + go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest + ``` + +## 配置说明 + +服务端配置在 `server/config.yaml` 中定义: + +```yaml +grpc: + name: "demo" # 服务名称 + logStdout: true # 启用标准输出日志 + errorLogEnabled: true # 启用错误日志 + accessLogEnabled: true # 启用访问日志 + errorStack: true # 启用错误堆栈跟踪 + +database: + logger: + level: "all" # 记录所有SQL操作 + stdout: true # 打印到标准输出 + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true # 启用调试模式 + +redis: + default: + address: 127.0.0.1:6379 # 默认Redis实例 + db: 0 + cache: + address: 127.0.0.1:6379 # 缓存Redis实例 + db: 1 +``` + +## 使用说明 + +1. 生成协议代码: + ```bash + cd protobuf + protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. ./user/*.proto + ``` + +2. 启动服务端: + ```bash + cd server + go run server.go + ``` + +3. 运行客户端: + ```bash + cd client + go run client.go + ``` + +4. 查看跟踪: + 在浏览器中打开 http://localhost:16686 查看 `Jaeger` UI中的跟踪信息。 + +## 实现说明 + +1. 服务端实现 + ```go + // 配置服务发现 + grpcx.Resolver.Register(etcd.New("127.0.0.1:2379")) + + // 初始化跟踪 + shutdown, err := otlpgrpc.Init(serviceName, endpoint, traceToken) + + // 配置Redis作为ORM缓存适配器 + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + ``` + +2. 客户端实现 + ```go + // 创建新的跟踪span + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + // 设置跟踪的baggage值 + ctx = gtrace.SetBaggageValue(ctx, "uid", 100) + + // 创建gRPC客户端 + client := user.NewUserClient(grpcx.Client.MustNewGrpcClientConn("demo")) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md b/versioned_docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md new file mode 100644 index 00000000000..ac32c290345 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/http-with-db/http-with-db.md @@ -0,0 +1,193 @@ +--- +title: HTTP带数据库 +slug: /examples/observability/trace/http-with-db +keywords: [链路跟踪, http, 数据库, goframe] +description: GoFrame中HTTP服务与数据库操作的分布式跟踪实现 +hide_title: true +sidebar_position: 1 +--- + +# 链路跟踪 - HTTP带数据库 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/http-with-db + + +## 简介 + +本示例演示了如何在 `GoFrame` 中实现HTTP服务与数据库交互的分布式跟踪。主要展示: +- 配置HTTP服务的跟踪 +- 跟踪数据库操作 +- 传播跟踪上下文 +- 可视化分布式跟踪 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame MySQL` 驱动 +- `GoFrame Redis` 驱动 +- `GoFrame OpenTelemetry` 跟踪 + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带跟踪的客户端 +├── server/ # 服务端示例 +│ ├── server.go # 带跟踪的服务端 +│ └── config.yaml # 服务端配置 +├── sql.sql # 数据库模式 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + + + +## 前置条件 + +1. 运行 `MySQL` 实例: + ```bash + docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + mysql:5.7 + ``` + +2. 初始化数据库模式: + ```bash + # 连接MySQL容器 + docker exec -i mysql mysql -uroot -p12345678 test < sql.sql + ``` + +3. 运行 `Redis` 实例: + ```bash + docker run -d --name redis \ + -p 6379:6379 \ + redis:6.0 + ``` + +4. 运行 `Jaeger` 实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## 配置说明 + +服务端配置在`server/config.yaml`中定义: + +```yaml +database: + logger: + level: "all" # 记录所有SQL操作 + stdout: true # 打印到标准输出 + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + debug: true # 启用调试模式 + +redis: + default: + address: 127.0.0.1:6379 # 默认Redis实例 + db: 0 + cache: + address: 127.0.0.1:6379 # 缓存Redis实例 + db: 1 +``` + +## 使用说明 + +1. 启动服务端: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` + +3. 查看跟踪: + 在浏览器中打开 http://localhost:16686 查看 `Jaeger` UI中的跟踪信息。 + +## API接口 + +服务器提供以下HTTP接口: + +1. 插入用户 + ```text + POST /insert + 请求: {"Name": "string"} + 响应: {"ID": number} + ``` + +2. 查询用户 + ```text + GET /query + 请求: {"ID": number} + 响应: {"User": object} + ``` + +3. 删除用户 + ```text + DELETE /delete + 请求: {"Id": number} + 响应: {} + ``` + +## 实现说明 + +1. 服务端实现 + ```go + // 配置Redis作为ORM缓存适配器 + g.DB().GetCache().SetAdapter(gcache.NewAdapterRedis(g.Redis())) + + // 启动HTTP服务器 + s := g.Server() + s.Use(ghttp.MiddlewareHandlerResponse) + s.Group("/", func(group *ghttp.RouterGroup) { + group.ALL("/user", new(cTrace)) + }) + + // 查询用户信息 + func (c *cTrace) Query(ctx context.Context, req *QueryReq) (res *QueryRes, err error) { + one, err := g.Model("user").Ctx(ctx).Cache(gdb.CacheOption{ + Duration: 5 * time.Second, + Name: c.userCacheKey(req.ID), + Force: false, + }).WherePri(req.ID).One() + if err != nil { + return nil, err + } + res = &QueryRes{ + User: one, + } + return + } + ``` + +2. 客户端实现 + ```go + // 创建新的跟踪span + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + // 发送HTTP请求 + err = client.PostVar(ctx, "http://127.0.0.1:8199/user/insert", g.Map{ + "name": "john", + }).Scan(&insertRes) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/http/http.md b/versioned_docs/version-2.8.x/examples/observability/trace/http/http.md new file mode 100644 index 00000000000..b941d2a739f --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/http/http.md @@ -0,0 +1,122 @@ +--- +title: HTTP服务 +slug: /examples/observability/trace/http +keywords: [链路跟踪, http, goframe] +description: GoFrame中HTTP服务的分布式跟踪实现 +hide_title: true +sidebar_position: 1 +--- + +# 链路跟踪 - HTTP服务 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/http + + +## 简介 + +本示例演示了如何在 `GoFrame` 中实现HTTP服务的分布式跟踪。主要展示: +- 配置HTTP服务的跟踪 +- 跟踪HTTP请求和响应 +- 传播跟踪上下文 +- 可视化分布式跟踪 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `GoFrame` 框架 +- `GoFrame OpenTelemetry` 跟踪 + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 带跟踪的客户端 +├── server/ # 服务端示例 +│ └── server.go # 带跟踪的服务端 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + + +## 前置条件 + +1. 运行 `Jaeger` 实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## 使用说明 + +1. 启动服务端: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` + +3. 查看跟踪: + 在浏览器中打开 http://localhost:16686 查看 `Jaeger` UI中的跟踪信息。 + +## API接口 + +服务器提供以下HTTP接口: + +1. Hello World + ```text + GET /hello + 响应: "Hello World" + ``` + +## 实现说明 + +1. 服务端实现 + ```go + // 初始化跟踪 + shutdown, err := otlphttp.Init(serviceName, endpoint, path) + + // 创建HTTP服务器 + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.GET("/hello", HelloHandler) + }) + + // 处理请求并跟踪 + func HelloHandler(r *ghttp.Request) { + ctx, span := gtrace.NewSpan(r.Context(), "HelloHandler") + defer span.End() + + value := gtrace.GetBaggageVar(ctx, "name").String() + r.Response.Write("hello:", value) + } + ``` + +2. 客户端实现 + ```go + // 创建新的跟踪span + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + // 设置跟踪的baggage值 + ctx = gtrace.SetBaggageValue(ctx, "name", "GoFrame") + + // 发送HTTP请求 + response, err := g.Client().Get(ctx, "http://127.0.0.1:8199/hello") + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md b/versioned_docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md new file mode 100644 index 00000000000..61175a9686d --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/inprocess-grpc/inprocess-grpc.md @@ -0,0 +1,134 @@ +--- +title: 进程内服务 (gRPC导出器) +slug: /examples/observability/trace/inprocess-grpc +keywords: [链路跟踪, 进程内, grpc, goframe, otlp-grpc] +description: 使用GoFrame和基于gRPC的OpenTelemetry导出器实现进程内服务的分布式链路跟踪 +hide_title: true +sidebar_position: 1 +--- + +# 链路跟踪 - 进程内服务 (gRPC导出器) + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/inprocess-grpc + + +## 简介 + +本示例演示了如何使用GoFrame和基于gRPC的OpenTelemetry导出器在进程内服务中实现分布式跟踪。主要展示: +- 使用gRPC导出器配置单进程跟踪 +- 跟踪函数调用和操作 +- 通过gRPC传播跟踪上下文 +- 可视化分布式跟踪 + +## 环境要求 + +- Go `1.22` 或更高版本 +- GoFrame框架 +- GoFrame OpenTelemetry gRPC跟踪 + +## 目录结构 + +``` +. +├── main.go # 带gRPC跟踪的主应用程序 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + + +## 前置条件 + +1. 运行Jaeger实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## 使用说明 + +1. 运行应用程序: + ```bash + go run main.go + ``` + +2. 查看跟踪: + 在浏览器中打开 http://localhost:16686 查看Jaeger UI中的跟踪信息。 + +## 实现说明 + +本示例演示了: + +1. 用户数据操作 + ```go + GetUser(ctx, id) // 检索完整的用户数据 + ├── GetInfo(ctx, id) // 获取基本用户信息 + ├── GetDetail(ctx, id) // 获取详细用户信息 + └── GetScores(ctx, id) // 获取用户成绩 + ``` + +2. 跟踪上下文流程 + ```go + // 创建带gRPC上下文的根span + ctx, span := gtrace.NewSpan(ctx, "main") + defer span.End() + + // 创建带gRPC元数据的子span + ctx, span := gtrace.NewSpan(ctx, "GetUser") + defer span.End() + ``` + +3. 错误处理 + ```go + // 处理不存在的用户ID + if id == 100 { + return g.Map{ + "id": 100, + "name": "john", + "gender": 1, + } + } + return nil + ``` + +4. 基于gRPC的跟踪导出 + ```go + // 初始化gRPC跟踪导出器 + shutdown, err = otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown(ctx) + ``` + + +## 示例输出 + +对于用户ID 100: +```go +{ + "id": 100, + "name": "john", + "gender": 1, + "site": "https://goframe.org", + "email": "john@goframe.org", + "math": 100, + "english": 60, + "chinese": 50 +} +``` + +对于不存在的用户ID: +```go +{} +``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md b/versioned_docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md new file mode 100644 index 00000000000..47034c29b53 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/inprocess/inprocess.md @@ -0,0 +1,185 @@ +--- +title: 进程内服务 (HTTP导出器) +slug: /examples/observability/trace/inprocess +keywords: [链路跟踪, 进程内, goframe, otlp-http] +description: 使用GoFrame和基于HTTP的OpenTelemetry导出器实现进程内服务的分布式跟踪 +hide_title: true +sidebar_position: 1 +--- + +# 链路跟踪 - 进程内服务 (HTTP导出器) + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/inprocess + + +## 简介 + +本示例演示了如何使用GoFrame和基于HTTP的OpenTelemetry导出器在进程内服务中实现分布式跟踪。主要展示: +- 使用HTTP导出器配置单进程跟踪 +- 跟踪函数调用和操作 +- 传播跟踪上下文 +- 可视化分布式跟踪 + +## 环境要求 + +- Go `1.22` 或更高版本 +- GoFrame框架 +- GoFrame OpenTelemetry HTTP跟踪 + +## 目录结构 + +``` +. +├── main.go # 带HTTP跟踪的主应用程序 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 基于HTTP导出器的分布式跟踪 + - 基于HTTP的跟踪数据传输 + - Span管理 + - 跟踪可视化 + +2. 函数调用跟踪 + - 函数进入/退出跟踪 + - 上下文传播 + - 错误处理 + +3. 数据操作 + - 用户数据检索 + - 数据聚合 + - 错误处理 + +## 前置条件 + +1. 运行Jaeger实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## 使用说明 + +1. 运行应用程序: + ```bash + go run main.go + ``` + +2. 查看跟踪: + 在浏览器中打开 http://localhost:16686 查看Jaeger UI中的跟踪信息。 + +## 实现说明 + +本示例演示了: + +1. 用户数据操作 + ```go + GetUser(ctx, id) // 检索完整的用户数据 + ├── GetInfo(ctx, id) // 获取基本用户信息 + ├── GetDetail(ctx, id) // 获取详细用户信息 + └── GetScores(ctx, id) // 获取用户成绩 + ``` + +2. 跟踪上下文流程 + ```go + // 创建根span + ctx, span := gtrace.NewSpan(ctx, "main") + defer span.End() + + // 创建子span并传播上下文 + ctx, span := gtrace.NewSpan(ctx, "GetUser") + defer span.End() + ``` + +3. 错误处理 + ```go + // 处理不存在的用户ID + if id == 100 { + return g.Map{ + "id": 100, + "name": "john", + "gender": 1, + } + } + return nil + ``` + +4. 基于HTTP的跟踪导出 + ```go + // 初始化HTTP跟踪导出器 + shutdown, err = otlphttp.Init(serviceName, endpoint, path) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown(ctx) + ``` + +## 故障排除 + +1. 应用程序问题: + - 检查应用程序是否正常运行 + - 验证函数调用输出 + - 查看错误日志 + +2. 跟踪问题: + - 验证Jaeger是否运行:`docker ps | grep jaeger` + - 检查Jaeger UI可访问性:http://localhost:16686 + - 确保配置中的HTTP端点正确 + +3. HTTP导出问题: + - 检查HTTP连接状态 + - 验证HTTP端点可访问性 + - 查看HTTP错误消息 + +## 示例输出 + +对于用户ID 100: +```go +{ + "id": 100, + "name": "john", + "gender": 1, + "site": "https://goframe.org", + "email": "john@goframe.org", + "math": 100, + "english": 60, + "chinese": 50 +} +``` + +对于不存在的用户ID: +```go +{} +``` + +## 最佳实践 + +1. 跟踪配置 + - 设置合适的服务名称,便于在Jaeger UI中识别 + - 配置适当的采样率,平衡性能和可观测性 + - 使用有意义的span名称和标签 + +2. 错误处理 + - 在span中记录错误信息 + - 使用合适的状态码标识错误类型 + - 保持日志和跟踪的一致性 + +3. 性能优化 + - 避免创建过多的span + - 及时关闭span + - 合理使用上下文传播 diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/otlp/otlp.md b/versioned_docs/version-2.8.x/examples/observability/trace/otlp/otlp.md new file mode 100644 index 00000000000..f6e8095f8da --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/otlp/otlp.md @@ -0,0 +1,169 @@ +--- +title: OpenTelemetry示例 +slug: /examples/observability/trace/otlp +keywords: [链路跟踪, otlp, grpc, http, goframe] +description: GoFrame中OpenTelemetry链路跟踪数据导出方法 +hide_title: true +sidebar_position: 8 +--- + +# OpenTelemetry链路跟踪示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/otlp + + +## 简介 + +本目录包含了演示在GoFrame应用程序中使用不同方法导出OpenTelemetry链路跟踪数据的示例。包括: + +1. 基于gRPC的导出 (`grpc/`) + - 使用gRPC协议传输链路跟踪数据 + - 适用于高性能、流式链路跟踪数据导出 + - 支持双向流和连接复用 + +2. 基于HTTP的导出 (`http/`) + - 使用HTTP协议传输链路跟踪数据 + - 适用于有HTTP代理或防火墙限制的环境 + - 配置和调试更简单 + +## 目录结构 + +``` +. +├── grpc/ # 基于gRPC的链路跟踪示例 +│ └── main.go # gRPC链路跟踪导出器实现 +├── http/ # 基于HTTP的链路跟踪示例 +│ └── main.go # HTTP链路跟踪导出器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 环境要求 + +- Go `1.22` 或更高版本 +- GoFrame框架 +- GoFrame OpenTelemetry gRPC链路跟踪 +- GoFrame OpenTelemetry HTTP链路跟踪 + +## 前置条件 + +1. 运行Jaeger实例: + ```bash + docker run --rm --name jaeger \ + -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \ + -p 6831:6831/udp \ + -p 6832:6832/udp \ + -p 5778:5778 \ + -p 16686:16686 \ + -p 4317:4317 \ + -p 4318:4318 \ + -p 14250:14250 \ + -p 14268:14268 \ + -p 14269:14269 \ + -p 9411:9411 \ + jaegertracing/all-in-one:1.55 + ``` + +## 导出方法对比 + +### gRPC导出 (grpc/) +1. 优势: + - 更高性能 + - 双向流传输 + - 连接复用 + - 更适合大量链路跟踪数据 + +2. 配置: + - 需要gRPC端点 + - 支持认证令牌 + - 可配置连接设置 + +3. 使用场景: + - 大量链路跟踪数据 + - 微服务架构 + - 性能关键系统 + +### HTTP导出 (http/) +1. 优势: + - 设置更简单 + - 可通过HTTP代理 + - 更易调试 + - 更好的防火墙兼容性 + +2. 配置: + - 需要HTTP端点 + - 支持路径配置 + - 标准HTTP设置 + +3. 使用场景: + - 有HTTP代理的环境 + - 简单部署要求 + - 开发和测试 + +## 使用说明 + +### gRPC导出示例 +1. 进入gRPC示例目录: + ```bash + cd grpc + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +### HTTP导出示例 +1. 进入HTTP示例目录: + ```bash + cd http + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +3. 查看链路跟踪: + 在浏览器中打开 http://localhost:16686 查看Jaeger UI中的链路跟踪信息。 + +## 实现说明 + +两个示例都演示了: + +1. 链路跟踪上下文管理 + ```go + // 创建新的链路跟踪span + ctx, span := gtrace.NewSpan(gctx.New(), "StartRequests") + defer span.End() + + // 设置链路跟踪的baggage值 + ctx = gtrace.SetBaggageValue(ctx, "name", "john") + ``` + +2. 错误处理 + ```go + // 初始化导出器 + shutdown, err = otlpgrpc.Init(serviceName, endpoint, traceToken) + if err != nil { + g.Log().Fatal(ctx, err) + } + defer shutdown(ctx) + ``` + +3. 配置管理 + ```go + // gRPC配置 + const ( + serviceName = "otlp-grpc-client" + endpoint = "tracing-analysis-dc-bj.aliyuncs.com:8090" + traceToken = "******_******" + ) + + // HTTP配置 + const ( + serviceName = "otlp-http-client" + endpoint = "tracing-analysis-dc-hz.aliyuncs.com" + path = "adapt_******_******/api/otlp/traces" + ) + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/processes/processes.md b/versioned_docs/version-2.8.x/examples/observability/trace/processes/processes.md new file mode 100644 index 00000000000..07662ade5d6 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/processes/processes.md @@ -0,0 +1,167 @@ +--- +title: 多进程示例 +slug: /examples/observability/trace/processes +keywords: [链路跟踪, 多进程, gcmd, gproc, goframe] +description: 使用GoFrame不同进程管理方式实现的多进程分布式链路跟踪示例 +hide_title: true +sidebar_position: 1 +--- + +# 多进程链路跟踪示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/processes + + +## 简介 + +本目录包含了使用GoFrame不同进程管理方式实现多进程分布式链路跟踪的示例。包括: + +1. 命令行进程管理 (`gcmd/`) + - 使用`gcmd`包进行进程管理 + - 演示基于命令行的进程创建 + - 展示父子进程间的链路跟踪上下文传播 + +2. 程序化进程管理 (`gproc/`) + - 使用`gproc`包进行进程管理 + - 演示程序化的进程创建 + - 展示进程层级中的链路跟踪传播 + +## 目录结构 + +``` +. +├── gcmd/ # 命令行进程管理示例 +│ ├── main.go # 主进程实现 +│ └── sub/ # 子进程实现 +│ └── sub.go # 子进程代码 +├── gproc/ # 进程管理示例 +│ ├── main.go # 主进程实现 +│ └── sub/ # 子进程实现 +│ └── sub.go # 子进程代码 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 环境要求 + +- Go `1.22` 或更高版本 +- GoFrame框架 +- GoFrame链路跟踪支持 + +## 功能特性 + +本示例展示了以下功能: + +1. 进程管理 + ```go + // gcmd方式 + Main = &gcmd.Command{ + Name: "main", + Brief: "main process", + Func: func(ctx context.Context, parser *gcmd.Parser) (err error) { + return gproc.ShellRun(ctx, `go run sub/sub.go`) + }, + } + + // gproc方式 + if err := gproc.ShellRun(ctx, `go run sub/sub.go`); err != nil { + panic(err) + } + ``` + +2. 链路跟踪上下文传播 + ```go + // 主进程 + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is main process`) + + // 子进程 + ctx := gctx.GetInitCtx() + g.Log().Debug(ctx, `this is sub process`) + ``` + +3. 日志和调试 + - 进程标识 + - 调试日志 + - 错误报告 + +## 管理方式对比 + +### 命令行管理 (gcmd/) +1. 特点: + - 命令行界面 + - 结构化命令处理 + - 内置帮助和文档 + - 命令层级支持 + +2. 使用场景: + - CLI应用程序 + - 命令驱动工具 + - 交互式应用 + +### 程序化管理 (gproc/) +1. 特点: + - 程序化进程控制 + - 直接进程操作 + - Shell命令执行 + - 进程同步 + +2. 使用场景: + - 后台进程 + - 服务管理 + - 进程自动化 + +## 使用说明 + +### 命令行示例 +1. 进入gcmd示例目录: + ```bash + cd gcmd + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +### 程序化管理示例 +1. 进入gproc示例目录: + ```bash + cd gproc + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +## 实现说明 + +两个示例都演示了: + +1. 进程创建 + - 主进程初始化 + - 子进程启动 + - 进程环境设置 + +2. 上下文管理 + ```go + // 创建和初始化上下文 + ctx := gctx.GetInitCtx() + + // 在进程间传播上下文 + g.Log().Debug(ctx, `process message`) + ``` + +3. 错误处理 + ```go + // 进程执行错误处理 + if err := gproc.ShellRun(ctx, command); err != nil { + panic(err) + } + + // 命令执行错误处理 + if err := cmd.Run(ctx); err != nil { + return err + } + ``` diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/provider/provider.md b/versioned_docs/version-2.8.x/examples/observability/trace/provider/provider.md new file mode 100644 index 00000000000..99ed8190d21 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/provider/provider.md @@ -0,0 +1,213 @@ +--- +title: OpenTelemetry Provider +slug: /examples/observability/trace/provider +keywords: [链路跟踪, provider, grpc, http, goframe] +description: 在GoFrame中使用不同OpenTelemetry链路跟踪Provider配置的示例 +hide_title: true +sidebar_position: 9 +--- + +# OpenTelemetry Provider链路跟踪示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace/provider + + +## 简介 + +本目录包含了在GoFrame应用程序中使用不同OpenTelemetry链路跟踪Provider配置的示例。包括: + +1. gRPC Provider (`grpc/`) + - 使用gRPC协议传输链路跟踪数据 + - 配置带gRPC导出器的链路跟踪Provider + - 演示资源和采样器配置 + +2. HTTP Provider (`http/`) + - 使用HTTP协议传输链路跟踪数据 + - 配置带HTTP导出器的链路跟踪Provider + - 展示资源和采样器配置 + +3. 内部组件 (`internal/`) + - 通用Provider初始化代码 + - 共享常量和配置 + - 链路跟踪设置工具函数 + +## 目录结构 + +``` +. +├── grpc/ # gRPC provider示例 +│ └── main.go # gRPC provider实现 +├── http/ # HTTP provider示例 +│ └── main.go # HTTP provider实现 +├── internal/ # 共享组件 +│ ├── consts.go # 常量定义 +│ ├── provider.go # Provider初始化 +│ └── request.go # 请求工具 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 环境要求 + +- Go `1.22` 或更高版本 +- GoFrame框架 +- OpenTelemetry支持 + +## 功能特性 + +本示例展示了以下功能: + +1. Provider配置 + ```go + // 资源配置 + res, err = resource.New(ctx, + resource.WithFromEnv(), + resource.WithProcess(), + resource.WithTelemetrySDK(), + resource.WithHost(), + resource.WithAttributes( + semconv.ServiceNameKey.String(internal.GRPCServiceName), + semconv.HostNameKey.String(serverIP), + ), + ) + + // 采样器配置 + trace.WithSampler(trace.TraceIDRatioBased(0.1)) + ``` + +2. 资源属性 + - 服务名称 + - 主机信息 + - 进程详情 + - 自定义属性 + +3. 采样策略 + ```go + // 可用的采样策略 + trace.WithSampler(trace.AlwaysSample()) // 总是采样 + trace.WithSampler(trace.NeverSample()) // 从不采样 + trace.WithSampler(trace.ParentBased(...)) // 基于父级的采样 + trace.WithSampler(trace.TraceIDRatioBased()) // 基于跟踪ID的采样 + ``` + +4. Span处理 + ```go + // 简单Span处理器 + trace.WithSpanProcessor(trace.NewSimpleSpanProcessor(exporter)) + + // 批量Span处理器 + trace.WithSpanProcessor(trace.NewBatchSpanProcessor(exporter)) + ``` + +## Provider对比 + +### gRPC Provider (grpc/) +1. 特点: + - 高性能流式传输 + - 双向通信 + - 连接复用 + - 压缩支持 + +2. 配置: + ```go + otlptracegrpc.NewClient( + otlptracegrpc.WithInsecure(), + otlptracegrpc.WithEndpoint(internal.Endpoint), + otlptracegrpc.WithHeaders(map[string]string{ + "Authentication": internal.TraceToken, + }), + otlptracegrpc.WithCompressor(gzip.Name), + ) + ``` + +3. 使用场景: + - 大规模链路跟踪 + - 性能关键系统 + - 流式跟踪数据 + +### HTTP Provider (http/) +1. 特点: + - 标准HTTP协议 + - 配置简单 + - 防火墙友好 + - 压缩支持 + +2. 配置: + ```go + otlptracehttp.NewClient( + otlptracehttp.WithEndpoint(internal.HTTPEndpoint), + otlptracehttp.WithURLPath(internal.HTTPPath), + otlptracehttp.WithInsecure(), + otlptracehttp.WithCompression(1), + ) + ``` + +3. 使用场景: + - 基本链路跟踪需求 + - HTTP代理环境 + - 简单部署 + +## 使用说明 + +### gRPC Provider示例 +1. 进入gRPC示例目录: + ```bash + cd grpc + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +### HTTP Provider示例 +1. 进入HTTP示例目录: + ```bash + cd http + ``` + +2. 运行示例: + ```bash + go run main.go + ``` + +## 实现说明 + +两个示例都演示了: + +1. Provider设置 + ```go + // 初始化链路跟踪Provider + func InitTracer(opts ...trace.TracerProviderOption) (func(ctx context.Context), error) { + tracerProvider := trace.NewTracerProvider(opts...) + otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator( + propagation.TraceContext{}, + propagation.Baggage{}, + )) + otel.SetTracerProvider(tracerProvider) + return func(ctx context.Context) { + // 优雅关闭 + ctx, cancel := context.WithTimeout(ctx, time.Second) + defer cancel() + if err := tracerProvider.Shutdown(ctx); err != nil { + g.Log().Errorf(ctx, "关闭tracerProvider失败 err:%+v", err) + } + }, nil + } + ``` + +2. 资源配置 + - 服务标识 + - 主机信息 + - 进程属性 + - 自定义标签 + +3. 采样配置 + - 采样策略选择 + - 采样率配置 + - 父级上下文处理 + +4. 错误处理 + - 连接错误 + - 导出错误 + - 关闭处理 diff --git a/versioned_docs/version-2.8.x/examples/observability/trace/trace.md b/versioned_docs/version-2.8.x/examples/observability/trace/trace.md new file mode 100644 index 00000000000..cc9ef60fd99 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/observability/trace/trace.md @@ -0,0 +1,28 @@ +--- +title: 链路跟踪 +slug: /examples/observability/trace +keywords: [链路跟踪, 监控, opentelemetry, goframe] +description: GoFrame 框架中的链路跟踪功能示例 +hide_title: true +--- + +# 链路跟踪示例 + +Github Source: https://github.com/gogf/examples/tree/main/observability/trace + + +## 介绍 + +本分类包含了一些在 GoFrame 框架中链路跟踪相关的示例代码。这些示例展示了如何在不同场景下使用 GoFrame 的链路跟踪功能。 + + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/practices/injection/injection.md b/versioned_docs/version-2.8.x/examples/practices/injection/injection.md new file mode 100644 index 00000000000..98b0429e292 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/practices/injection/injection.md @@ -0,0 +1,152 @@ +--- +title: 依赖注入 +slug: /examples/practices/injection +keywords: [依赖注入, 测试, goframe, di] +description: 使用GoFrame框架和do包实现的依赖注入示例 +hide_title: true +--- + +# 依赖注入示例 + +Github Source: https://github.com/gogf/examples/tree/main/practices/injection + + +## 简介 + +本示例展示了如何在使用`GoFrame`的应用程序中实现依赖注入。 + +主要演示: +1. 使用大仓模式管理代码仓库 +2. 基础依赖注入设置 +3. 使用`DI`的服务层实现 +4. 集成`gRPC`的控制器层 +5. 使用模拟依赖的单元测试 + +实现重点在于使核心业务逻辑易于测试: +- 使用依赖注入分离关注点 +- 为服务依赖提供清晰的接口 +- 每一层都可以独立执行单元测试,保障代码质量 + +## 环境要求 + +- [Go](https://golang.org/dl/) `1.22` 或更高版本 +- [Git](https://git-scm.com/downloads) +- [GoFrame](https://goframe.org) +- [MongoDB](https://www.mongodb.com) +- [Redis](https://redis.io) +- [github.com/samber/do](https://github.com/samber/do) + +## 目录结构 + +```text +injection/ +├── app/ +│ ├── gateway/ # API网关服务,调用user grpc服务实现外部接口 +│ │ ├── api/ # API定义 +│ │ ├── internal/ # 内部实现 +│ │ │ ├── cmd/ # 命令行工具 +│ │ │ ├── controller/ # 控制器 +│ │ │ ├── model/ # 数据模型 +│ │ │ └── service/ # 业务逻辑 +│ │ └── manifest/ # 配置文件 +│ └── user/ # 用户服务 +│ ├── api/ # API定义 +│ │ ├── entity/ # 实体定义 +│ │ └── user/ # 用户API proto +│ ├── internal/ # 内部实现 +│ │ ├── cmd/ # 命令行工具 +│ │ ├── controller/ # 使用DI的控制器 +│ │ ├── dao/ # 数据访问对象 +│ │ ├── model/ # 数据模型 +│ │ └── service/ # 使用DI的业务逻辑 +│ └── manifest/ # 配置文件 +├── hack/ # 开发工具 +└── utility/ # 通用工具 + ├── injection/ # DI工具 + └── mongohelper/ # MongoDB辅助工具 +``` + +## 功能特性 + +- 依赖注入使用 +- `MongoDB`和`Redis`集成 +- `gRPC`服务实现 +- 完整的单元测试 +- 清理资源的关闭处理 +- 命名依赖支持 + +## 安装设置 + +1. 克隆仓库: + ```bash + git clone https://github.com/gogf/examples.git + cd examples/practices/injection + ``` + +2. 安装依赖: + ```bash + go mod tidy + ``` + +3. 使用`Docker`启动所需服务: + ```bash + # 启动MongoDB + docker run -d --name mongo -p 27017:27017 mongo:latest + + # 启动Redis + docker run -d --name redis -p 6379:6379 redis:latest + ``` + +## 使用方法 + +1. 运行`gRPC Server`服务: + ```bash + cd examples/practices/injection/app/user + go run main.go server + ``` + +2. 运行`HTTP Server`服务: + ```bash + cd examples/practices/injection/app/gateway + go run main.go server + ``` + +3. (可选)运行异步守护进程,仅演示多命令功能,无实际逻辑: + ```bash + cd examples/practices/injection/app/gateway + go run main.go worker + ``` + +4. (可选)运行测试: + ```bash + go test ./... + ``` + +## 实现细节 + +### 依赖注入设置 +- 使用`github.com/samber/do`包进行依赖管理 +- 支持命名和未命名依赖管理 +- 提供常用操作的辅助函数 + +### 服务层 + +- 清晰的关注点分离 +- 基于接口的设计 +- 易于使用模拟实现进行测试 + +### 控制器层 +- 支持`DI`的`gRPC`集成 +- 清晰的错误处理 +- 合理的资源管理 + +### 可测试性 +- 数据层(`dao`)、业务层(`service`)、接口层(`controller`)完整的单元测试 +- 依赖模拟,单元测试使用的数据库配置通过`manifest/config`管理 +- 通过配置文件管理微服务链接的服务地址或者域名,以便于控制依赖的服务模拟 + +## 注意事项 + +- 依赖注入的注册时,需要在`Shutdown`方法中正确清理资源 +- 使用依赖注入时,如果需要同类型的多个实例时使用命名依赖 + diff --git a/versioned_docs/version-2.8.x/examples/practices/practices.md b/versioned_docs/version-2.8.x/examples/practices/practices.md new file mode 100644 index 00000000000..9e21730afa2 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/practices/practices.md @@ -0,0 +1,10 @@ +--- +slug: '/examples/practices' +title: '工程实践' +hide_title: true +sidebar_position: 99 +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git a/versioned_docs/version-2.8.x/examples/registry/consul/consul.md b/versioned_docs/version-2.8.x/examples/registry/consul/consul.md new file mode 100644 index 00000000000..3ac84f5d0c4 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/consul/consul.md @@ -0,0 +1,95 @@ +--- +title: Consul +slug: /examples/registry/consul +keywords: [注册中心, consul, 服务发现, goframe] +description: GoFrame框架中的Consul服务注册与发现集成 +hide_title: true +sidebar_position: 1 +--- + +# 注册中心 - `Consul` 集成 + +Github Source: https://github.com/gogf/examples/tree/main/registry/consul + + +## 简介 + +本示例演示了如何在 `GoFrame` 应用程序中集成 `Consul` 服务注册中心。主要展示: +- 使用 `Consul` 注册服务 +- 使用 `Consul` 发现服务 +- 实现服务健康检查 +- 构建分布式系统 + +## 目录结构 + +```text +. +├── docker-compose/ # 运行Consul的Docker配置文件 +├── grpc/ # gRPC服务示例 +│ ├── client/ # gRPC客户端实现 +│ ├── controller/ # gRPC服务控制器 +│ ├── protobuf/ # Protocol buffer定义 +│ └── server/ # gRPC服务器实现 +│ ├── main.go # 服务器启动代码 +│ └── config.yaml # 服务器配置 +├── http/ # HTTP服务示例 +│ ├── client/ # HTTP客户端实现 +│ └── server/ # HTTP服务器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `Docker` (用于运行 `Consul`) +- `Consul` + +## 使用说明 + +1. 启动 `Consul` 服务: + ```bash + cd docker-compose + docker-compose up -d + ``` + +2. `HTTP` 服务示例: + ```bash + # 启动HTTP服务器 + cd http/server + go run server.go + + # 运行HTTP客户端 + cd http/client + go run client.go + ``` + +3. `gRPC` 服务示例: + ```bash + # 启动gRPC服务器 + cd grpc/server + go run server.go + + # 运行gRPC客户端 + cd grpc/client + go run client.go + ``` + +## 实现说明 + +1. `HTTP` 服务实现 + - 服务器使用 `g.Server` 创建 `HTTP` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Consul` 注册中心 + - 客户端使用 `g.Client()` 自动发现并访问服务 + +2. `gRPC` 服务实现 + - 服务器使用 `grpc.Server` 创建 `gRPC` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Consul` 注册中心 + - 客户端使用 `grpc.Client` 自动发现并访问服务 + +## 注意事项 + +1. 确保 `Consul` 服务正常运行在 `127.0.0.1:8500` +2. 服务注册时会自动添加健康检查 +3. 客户端会自动使用服务发现进行负载均衡 diff --git a/versioned_docs/version-2.8.x/examples/registry/etcd/etcd.md b/versioned_docs/version-2.8.x/examples/registry/etcd/etcd.md new file mode 100644 index 00000000000..67d50516d57 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/etcd/etcd.md @@ -0,0 +1,108 @@ +--- +title: Etcd +slug: /examples/registry/etcd +keywords: [注册中心, etcd, 服务发现, goframe] +description: GoFrame框架中的Etcd服务注册与发现集成 +hide_title: true +sidebar_position: 1 +--- + +# 注册中心 - `Etcd` 集成 + +Github Source: https://github.com/gogf/examples/tree/main/registry/etcd + + +## 简介 + +本示例演示了如何在 `GoFrame` 应用程序中集成 `Etcd` 服务注册中心。主要展示: +- 使用 `Etcd` 注册服务 +- 使用 `Etcd` 发现服务 +- 实现服务健康检查 +- 构建分布式系统 + +## 目录结构 + +```text +. +├── grpc/ # gRPC服务示例 +│ ├── client/ # gRPC客户端实现 +│ ├── controller/ # gRPC服务控制器 +│ ├── protobuf/ # Protocol buffer定义 +│ └── server/ # gRPC服务器实现 +│ ├── main.go # 服务器启动代码 +│ └── config.yaml # 服务器配置 +├── http/ # HTTP服务示例 +│ ├── client/ # HTTP客户端实现 +│ └── server/ # HTTP服务器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 服务注册 + - 自动服务注册 + - 基于TTL的健康检查 + - 元数据管理 + - 租约管理 + +2. 服务发现 + - 动态服务发现 + - 负载均衡 + - 故障转移支持 + - Watch机制监控 + +3. 协议支持 + - `HTTP` 服务注册与发现 + - `gRPC` 服务注册与发现 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `Docker` (用于运行 `Etcd`) +- `Etcd` 3.4+ + +## 使用说明 + +1. 启动 `Etcd` 服务: + ```bash + docker run -d --name etcd -p 2379:2379 -e ALLOW_NONE_AUTHENTICATION=yes bitnami/etcd:3.4.24 + ``` + +2. `HTTP` 服务示例: + ```bash + # 启动HTTP服务器 + cd http/server + go run server.go + + # 运行HTTP客户端 + cd http/client + go run client.go + ``` + +3. `gRPC` 服务示例: + ```bash + # 启动gRPC服务器 + cd grpc/server + go run server.go + + # 运行gRPC客户端 + cd grpc/client + go run client.go + ``` + +## 实现说明 + +1. `HTTP` 服务实现 + - 服务器使用 `g.Server` 创建 `HTTP` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Etcd` 注册中心 + - 客户端使用 `g.Client()` 自动发现并访问服务 + - 支持自动的健康检查和租约续期 + +2. `gRPC` 服务实现 + - 服务器使用 `grpc.Server` 创建 `gRPC` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Etcd` 注册中心 + - 客户端使用 `grpc.Client` 自动发现并访问服务 + - 支持自动的健康检查和租约续期 diff --git a/versioned_docs/version-2.8.x/examples/registry/file/file.md b/versioned_docs/version-2.8.x/examples/registry/file/file.md new file mode 100644 index 00000000000..8e754981a86 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/file/file.md @@ -0,0 +1,94 @@ +--- +title: File +slug: /examples/registry/file +keywords: [注册中心, 文件, 服务发现, goframe] +description: GoFrame框架中基于文件的服务注册与发现集成 +hide_title: true +sidebar_position: 1 +--- + +# 注册中心 - 文件系统集成 + +Github Source: https://github.com/gogf/examples/tree/main/registry/file + + +## 简介 + +本示例演示了如何在 `GoFrame` 应用程序中使用基于文件系统的服务注册中心。主要展示: +- 使用文件系统注册服务 +- 从文件中发现服务 +- 实现简单的服务发现 +- 构建基础的分布式系统 + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 基于文件的服务发现客户端实现 +├── server/ # 服务器示例 +│ └── server.go # 基于文件的服务注册服务器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 服务注册 + - 基于文件的服务注册 + - 简单的元数据存储 + - 本地文件系统集成 + - 自动的文件管理 + +2. 服务发现 + - 基于文件的服务发现 + - 本地服务查找 + - 基础负载均衡 + - 简单的故障转移支持 + +3. 协议支持 + - `HTTP` 服务支持 + - 可扩展支持其他协议 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- 本地文件系统读写权限 + +## 使用说明 + +1. 启动服务器: + ```bash + cd server + go run server.go + ``` + +2. 运行客户端: + ```bash + cd client + go run client.go + ``` + +## 实现说明 + +1. 服务器实现 + - 使用 `g.Server` 创建 `HTTP` 服务 + - 通过 `gsvc.SetRegistry` 设置文件注册中心 + - 服务信息存储在系统临时目录 + - 自动管理服务注册文件 + +2. 客户端实现 + - 使用相同的临时目录路径 + - 监控文件变化自动更新 + - 支持基本的负载均衡 + - 实现简单的故障转移 + + +## 注意事项 + +1. 确保服务器和客户端使用相同的注册目录 +2. 适合开发和测试环境,不建议用于生产环境 +3. 服务信息存储在本地文件系统,需要确保读写权限 +4. 支持基本的服务发现和负载均衡功能 diff --git a/versioned_docs/version-2.8.x/examples/registry/nacos/nacos.md b/versioned_docs/version-2.8.x/examples/registry/nacos/nacos.md new file mode 100644 index 00000000000..c32686ca36f --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/nacos/nacos.md @@ -0,0 +1,113 @@ +--- +title: Nacos +slug: /examples/registry/nacos +keywords: [注册中心, nacos, 服务发现, goframe] +description: GoFrame框架中的Nacos服务注册与发现集成 +hide_title: true +sidebar_position: 1 +--- + +# 注册中心 - `Nacos` 集成 + +Github Source: https://github.com/gogf/examples/tree/main/registry/nacos + + +## 简介 + +本示例演示了如何在 `GoFrame` 应用程序中集成 `Nacos` 服务注册中心。主要展示: +- 使用 `Nacos` 注册服务 +- 使用 `Nacos` 发现服务 +- 实现服务健康检查 +- 构建分布式系统 + +## 目录结构 + +```text +. +├── docker-compose/ # Docker配置文件 +│ ├── docker-compose.yml # Docker编排配置 +│ └── env/ # 环境变量配置 +│ └── nacos.env # Nacos环境配置 +├── grpc/ # gRPC服务示例 +│ ├── client/ # gRPC客户端实现 +│ ├── controller/ # gRPC服务控制器 +│ ├── protobuf/ # Protocol buffer定义 +│ └── server/ # gRPC服务器实现 +│ ├── main.go # 服务器启动代码 +│ └── config.yaml # 服务器配置 +├── http/ # HTTP服务示例 +│ ├── client/ # HTTP客户端实现 +│ └── server/ # HTTP服务器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 服务注册 + - 自动服务注册 + - 健康检查配置 + - 元数据管理 + - 命名空间和分组管理 + +2. 服务发现 + - 动态服务发现 + - 负载均衡 + - 故障转移支持 + - 服务订阅机制 + +3. 协议支持 + - `HTTP` 服务注册与发现 + - `gRPC` 服务注册与发现 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `Docker` (用于运行 `Nacos`) +- `Nacos` 2.1.2+ + +## 使用说明 + +1. 启动 `Nacos` 服务: + ```bash + cd docker-compose + docker-compose up -d + ``` + +2. `HTTP` 服务示例: + ```bash + # 启动HTTP服务器 + cd http/server + go run server.go + + # 运行HTTP客户端 + cd http/client + go run client.go + ``` + +3. `gRPC` 服务示例: + ```bash + # 启动gRPC服务器 + cd grpc/server + go run server.go + + # 运行gRPC客户端 + cd grpc/client + go run client.go + ``` + +## 实现说明 + +1. `HTTP` 服务实现 + - 服务器使用 `g.Server` 创建 `HTTP` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Nacos` 注册中心 + - 客户端使用 `g.Client()` 自动发现并访问服务 + - 支持自动的健康检查和服务发现 + +2. `gRPC` 服务实现 + - 服务器使用 `grpc.Server` 创建 `gRPC` 服务 + - 通过 `gsvc.SetRegistry` 设置 `Nacos` 注册中心 + - 客户端使用 `grpc.Client` 自动发现并访问服务 + - 支持自动的健康检查和服务发现 diff --git a/versioned_docs/version-2.8.x/examples/registry/polaris/polaris.md b/versioned_docs/version-2.8.x/examples/registry/polaris/polaris.md new file mode 100644 index 00000000000..ad1e7c70697 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/polaris/polaris.md @@ -0,0 +1,122 @@ +--- +title: Polaris +slug: /examples/registry/polaris +keywords: [注册中心, polaris, 服务发现, goframe] +description: GoFrame框架中的Polaris服务注册与发现集成 +hide_title: true +sidebar_position: 1 +--- + +# 注册中心 - `Polaris` 集成 + +Github Source: https://github.com/gogf/examples/tree/main/registry/polaris + + +## 简介 + +本示例演示了如何在 `GoFrame` 应用程序中集成 `Polaris` 服务注册中心。主要展示: +- 使用 `Polaris` 注册服务 +- 使用 `Polaris` 发现服务 +- 实现服务健康检查 +- 构建分布式系统 + +## 目录结构 + +```text +. +├── client/ # 客户端示例 +│ └── client.go # 基于Polaris的服务发现客户端实现 +├── server/ # 服务器示例 +│ └── server.go # 基于Polaris的服务注册服务器实现 +├── go.mod # Go模块文件 +└── go.sum # Go模块校验和 +``` + +## 功能特性 + +本示例展示了以下功能: + +1. 服务注册 + - 自动服务注册 + - 健康检查配置 + - 元数据管理 + - 服务路由 + - 限流控制 + +2. 服务发现 + - 动态服务发现 + - 负载均衡 + - 故障转移支持 + - 熔断机制 + - 容错处理 + +3. 协议支持 + - `HTTP` 服务 + - 自定义协议 + - 协议扩展 + +## 环境要求 + +- `Go` `1.22` 或更高版本 +- `Polaris` 服务器 +- 本地缓存目录读写权限 + +## 使用说明 + +1. 配置 `Polaris` 服务器地址: + ```go + conf := config.NewDefaultConfiguration([]string{"183.47.111.80:8091"}) + ``` + +2. 配置本地缓存: + ```go + conf.Consumer.LocalCache.SetPersistDir("/tmp/polaris/backup") + api.SetLoggersDir("/tmp/polaris/log") + ``` + +3. 启动服务器: + ```bash + cd server + go run server.go + ``` + +4. 运行客户端: + ```bash + cd client + go run client.go + ``` + +## 实现说明 + +1. 服务器实现 + - 使用默认配置创建 `Polaris` 客户端 + - 配置本地缓存提升性能 + - 支持服务路由和限流配置 + - 提供健康检查和故障恢复 + +2. 客户端实现 + - 自动服务发现和负载均衡 + - 支持熔断和容错机制 + - 本地缓存提供离线能力 + - 自动重试和故障转移 + +## 高级特性 + +1. 本地缓存 + - 服务信息持久化 + - 离线运行支持 + - 性能优化 + +2. 可靠性保障 + - 熔断保护 + - 限流控制 + - 故障转移 + - 容错处理 + +## 注意事项 + +1. 确保配置正确的Polaris服务器地址 +2. 本地缓存目录需要有读写权限 +3. 建议开启日志记录便于调试 +4. 合理配置熔断和限流参数 +5. 注意监控服务健康状态 diff --git a/versioned_docs/version-2.8.x/examples/registry/registry.md b/versioned_docs/version-2.8.x/examples/registry/registry.md new file mode 100644 index 00000000000..8ade7f84069 --- /dev/null +++ b/versioned_docs/version-2.8.x/examples/registry/registry.md @@ -0,0 +1,28 @@ +--- +title: 注册发现 +slug: /examples/registry +keywords: [注册中心, 服务发现, goframe] +description: GoFrame 框架中的注册中心功能示例 +hide_title: true +sidebar_position: 4 +--- + +# 注册发现示例 + +Github Source: https://github.com/gogf/examples/tree/main/registry + + +## 介绍 + +本分类包含了一些 `GoFrame` 框架中注册发现相关的示例代码。这些示例展示了如何在不同场景下使用 `GoFrame` 的注册发现功能。 + +## 注意事项 + +1. 示例代码主要用于演示目的 +2. 生产环境使用时需要根据实际需求进行调整 + +## 示例列表 + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" new file mode 100644 index 00000000000..65c01586df2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/Hello World.md" @@ -0,0 +1,80 @@ +--- +slug: '/quick/hello-world' +title: 'Hello World' +hide_title: true +sidebar_position: 2 +keywords: [GoFrame框架,Web Server,Go语言,模块化框架,路由绑定,自动接口文档,g.Server,网络请求,Server对象,GoFrame教程] +description: '使用GoFrame框架构建一个简单的Hello World Web Server。GoFrame是一款模块化的Go语言框架,提供方便的Web服务器构建方式。本文详细解析了代码示例,包括Server对象的创建、路由绑定、端口设置及运行结果的解读,为初学者提供了一条快速入门的路径。' +--- + +`GoFrame` 是一款基础设施建设比较完善的模块化框架, +`Web Server` 模块是其中比较核心的模块,我们这里将 `Web` 服务开发作为框架入门的选择,便于大家更容易学习和理解。 + +## Hello World + +我们先来开发一个简单的`Web Server`程序。 + +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Write("Hello World!") + }) + s.SetPort(8000) + s.Run() +} +``` +我们来看看这段代码: +- 任何时候,您都可以通过 `g.Server()` 方法获得一个默认的 `Server` 对象,该方法采用**单例模式**设计, + 也就是说,多次调用该方法,返回的是同一个 `Server` 对象。其中的`g`组件是框架提供的一个耦合组件,封装和初始化一些常用的组件对象,为业务项目提供便捷化的使用方式。 +- 通过`Server`对象的`BindHandler`方法绑定路由以及路由函数。在本示例中,我们绑定了`/`路由,并指定路由函数返回`Hello World`。 +- 在路由函数中,输入参数为当前请求对象`r *ghttp.Request`,该对象包含当前请求的上下文信息。在本示例中,我们通过`r.Response`返回对象直接`Write`返回结果信息。 +- 通过`SetPort`方法设置当前`Server`监听端口。在本示例中,我们监听`8000`端口,如果在没有设置端口的情况下,它默认会监听一个随机的端口。 +- 通过 `Run()` 方法阻塞执行 `Server` 的监听运行。 + + +## 执行结果 + +运行该程序,您将在终端看到类似以下日志信息: +```html +$ go run main.go +2024-10-27 21:30:39.412 [INFO] pid[58889]: http server started listening on [:8000] +2024-10-27 21:30:39.412 [INFO] {08a0b0086e5202184111100658330800} openapi specification is disabled + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-----------------|------------- + :8000 | ALL | / | main.main.func1 | +----------|--------|-------|-----------------|------------- +``` + +在默认的日志打印中包含以下信息: +- 当前进程号`58889`,以及监听的地址`:8000`(表示监听本机所有IP地址的`8000`端口)。 +- 由于框架带有自动接口文档生成功能,本示例中未启用,因此提示`openapi specification is disabled`。 + 关于接口文档的自动生成,在开发手册中对应章节会详细讲解,本示例不作介绍。 +- 最后会打印当前`Server`的路由列表。由于我们只监听了`/`路由,那么这里只打印了一个路由信息。在路由信息表中: + + | 路由字段 | 字段描述 | + |----------|----------| + | `ADDRESS` | 表示该路由的监听地址,同一个进程可以同时运行多个`Server`,不同的`Server`可以监听不同的地址。 | + | `METHOD` | 表示路由监听的`HTTP Method`信息,比如`GET/POST/PUT/DELETE`等。这里的`ALL`标识监听所有的`HTTP Method`。 | + | `ROUTE` | 表示监听的具体路由地址信息。 | + | `HANDLER` | 表示路由函数的名称。由于本示例使用的是闭包函数,因此看到的是一个临时函数名称`main.main.func1`。 | + | `MIDDLEWARE` | 表示绑定到当前路由的中间件函数名称,中间件是`Server`中一种经典的拦截器,后续章节中会有详细讲解,这里暂不做介绍。 | + +运行后,我们尝试访问 http://127.0.0.1:8000/ 您将在页面中看到输出 + +![img_1.png](img_1.png) + +## 学习小结 + +太棒了!您使用`GoFrame`框架开发第一个`Web Server`程序! + +下一步,我们将会学习如何在`Web Server`中获取客户端提交的参数数据。 + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" new file mode 100644 index 00000000000..058c27047ef Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730124029804.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" new file mode 100644 index 00000000000..91e150427bb Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/QQ_1730178667265.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" new file mode 100644 index 00000000000..18eac65019d --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/What's Next.md" @@ -0,0 +1,21 @@ +--- +slug: /quick-next +title: "What's Next" +hide_title: true +sidebar_position: 10 +keywords: [GoFrame,GoFrame框架,Web Server,开发手册,模块化设计,低耦合,视频教程,项目脚手架,业务项目,开发流程] +description: '快速了解Web Server接口的开发流程和特性,通过查看开发手册章节解决疑惑。GoFrame是低耦合、模块化设计的框架,各模块设计独立,文档独立编写。社区提供入门视频教程,后续将使用GoFrame框架项目脚手架开发完整业务项目。' +--- + +## 学习小结 +通过快速章节,您应当了解到了关于`Web Server`接口的完整开发流程,以及一些实用特性的掌握。在这些章节中,我们只是点到即止,更详细的讲解以及功能特性介绍请查看对应开发手册章节。当您对某一模块的使用感到疑惑不解时,建议仔细阅读对应的开发手册章节,里面对各个组件重要的功能特性、使用方式、常见问题做了详细的讲解。 + +:::info +如果您开始尝试翻阅开发手册,需要注意的是,由于`GoFrame`是低耦合、模块化设计的,每一个模块都相对独立,因此开发手册中的文档也是基于模块化编写的,每个章节只会介绍对应的组件本身的使用。 +::: + +接下来我们应该做什么呢? + +## 项目脚手架 + +在接下来的章节中,我们将尝试使用`GoFrame`框架提供的项目脚手架,开发一个简单的、较完整的业务项目。 diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" new file mode 100644 index 00000000000..9648141f9fe Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" new file mode 100644 index 00000000000..9d5dc1f50c0 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_1.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" new file mode 100644 index 00000000000..8255a733774 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_2.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" new file mode 100644 index 00000000000..4364353e498 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_3.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" new file mode 100644 index 00000000000..583a983cd4d Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_4.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" new file mode 100644 index 00000000000..8ee3e595775 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/img_5.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" new file mode 100644 index 00000000000..e5078984bfd --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\213\350\275\275\344\270\216\344\275\277\347\224\250.md" @@ -0,0 +1,70 @@ +--- +slug: '/quick/install' +title: '下载与使用' +hide_title: true +sidebar_position: 1 +keywords: [GoFrame,GoFrame框架,安装GoFrame,模块化框架,低耦合设计,Web Server接口开发,Go语言环境,项目框架,基础组件,HTTP Server] +description: 'GoFrame框架的快速开始指南。GoFrame是一款模块化、低耦合设计的开发框架,包含常用基础组件和开发工具,适用于完整业务项目和独立组件库。内容涵盖GoFrame的下载与安装、运行基本操作,并介绍如何开发简易Web Server接口应用。' +--- + +大家好,欢迎访问 `GoFrame` 框架的快速开始章节! +由于 `GoFrame` 是一款模块化、低耦合设计的开发框架,包含了常用的基础组件和开发工具,既可以作为完整的业务项目框架使用也可以作为独立的组件库使用。 + +我们本章节从框架的下载安装使用开始,到指导大家完成一个较完善的`Web Server`接口开发。 + + +:::info +如果您还未准备好`Go`开发环境,建议您在进一步开始之前,先查阅环境安装文档:[环境安装](../../docs/其他资料/准备开发环境/准备开发环境.md) +了解一些基础的`Go`语言知识再进行下一步。 + +如果您在快速开始的任何章节中遇到任何问题,欢迎在评论区留言💬,我们将尽可能及时为您提供解答🌟🌟。 +::: + +## 限制 + +为保证框架的稳定性和安全性,`GoFrame`框架要求的最低的基础`Go`语言版本通常会比最新的`Go`语言版本低`1~3`个版本。 + +当前最新框架版本要求的最低`Go`语言版本: +```bash +golang版本 >= 1.20 +``` + +## 安装 +初始化`go.mod`: +```bash +go mod init hello +``` + +下载框架最新版本: +```bash +go get -u -v github.com/gogf/gf/v2 +``` + +## 运行 + +我们尝试运行以下代码: +```go title="main.go" +package main + +import ( + "fmt" + + "github.com/gogf/gf/v2" +) + +func main() { + fmt.Println("Hello GoFrame:", gf.VERSION) +} +``` +这段简单的代码示例是打印当前引入的`GoFrame`框架版本。通过以下命令执行: +```bash +go run main.go +``` +您将会在终端看到: +```bash +Hello GoFrame: v2.8.3 +``` + +恭喜您!这表示您已经成功引用了`GoFrame`到当前项目中。 + +下一步,我们将使用`GoFrame`开发一个简单的`HTTP Server`服务。 diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" new file mode 100644 index 00000000000..5200b0d61c7 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\270\255\351\227\264\344\273\266\345\210\235\350\257\225\347\224\250.md" @@ -0,0 +1,133 @@ +--- +slug: '/quick/middleware' +title: '中间件初试用' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame,中间件,Web Server,ErrorHandler,请求拦截,前置中间件,后置中间件,自定义错误处理,GoFrame框架,请求流程控制] +description: '在GoFrame框架中使用中间件来拦截请求和返回结果,通过前置和后置中间件实现自定义的处理逻辑。示例代码展示了如何定义错误处理中间件,并在路由中绑定。中间件使得请求错误处理和输出格式统一化变得灵活且强大。' +--- + +为了解决上一章节遗留的疑问:如何捕获返回的错误对象并作自定义的错误处理。 +在本章节中,我们将会先简单介绍`Web Server`的中间件特性,再回答这个疑问。 + +## 中间件介绍 + +中间件是一种拦截器设计,在`Web Server`中可以拦截请求和返回结果,并在其前后进行自定义处理逻辑。 + +中间件的定义和普通的路由函数一样,但是可以在 `Request` 参数中使用 `Middleware` 属性对象来控制请求流程。 + +中间件的类型分为两种:**前置中间件**和**后置中间件**。前置即在路由服务函数调用之前调用,后置即在其后调用。 + +```go +func Middleware(r *ghttp.Request) { + // 前置中间件处理逻辑 + r.Middleware.Next() + // 后置中间件处理逻辑 +} +``` +在中间件中执行完成处理逻辑后,使用 `r.Middleware.Next()` 方法进一步执行下一个流程; +如果这个时候直接退出不调用 `r.Middleware.Next()` 方法的话,将会退出后续的执行流程(例如可以用于请求的鉴权处理)。 + +## 使用中间件 + +我们使用中间件对上一章节的程序进行简单的改造,如下: +```go title="main.go" +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"姓名"` + Age int `v:"required" dc:"年龄"` +} +type HelloRes struct{} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} + +func ErrorHandler(r *ghttp.Request) { + // 执行路由函数 + r.Middleware.Next() + // 判断是否产生错误 + if err := r.GetError(); err != nil { + r.Response.Write("error occurs: ", err.Error()) + return + } +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ErrorHandler) + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` +- 我们定义了一个错误处理的中间件`ErrorHandler`,在该中间件中我们先通过`r.Middleware.Next()`执行路由函数流程, + 随后通过`r.GetError()`获取路由函数是否有错误产生。如果产生错误,那么直接输出错误信息。 +- 在路由注册中,我们通过`group.Middleware(ErrorHandler)`给该分组路由下的所有注册的路由,都绑定了错误处理的中间件。 + +## 执行结果 + +运行后,终端输出: + +```text +2024-11-06 22:30:06.927 [INFO] pid[35434]: http server started listening on [:8000] +2024-11-06 22:30:06.927 [INFO] {905637567a67051830833b2189796dda} openapi specification is disabled + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|-------|-------------------|-------------------- + :8000 | GET | / | main.(*Hello).Say | main.ErrorHandler +----------|--------|-------|-------------------|-------------------- +``` +这里的`MIDDLEWARE`部分多了一个`main.ErrorHandler`方法,表示该路由绑定的中间件名称。 + + +我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。 + +![img.png](img.png) + +我们尝试一下错误的参数请求 http://127.0.0.1:8000/ 可以看到,页面也输出了错误的提示信息。 + +![img_4.png](img_4.png) + +## 学习小结 + +我们使用中间件对请求错误进行自定义的处理,并捕获校验错误返回自定义的错误信息。 +可以看到,中间件的功能非常灵活且强大,当然不仅仅局限于处理校验错误这种小场景。 + +试想一下,假如我们的项目中有很多接口,通常这些接口的输出格式都是固定的,例如都是`json`格式。 +那么能否直接通过中间件的方式,统一错误处理以及返回的数据结构呢?答案当然是肯定的,我们将在下一章节介绍。 + + + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" new file mode 100644 index 00000000000..93767d51f65 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\344\275\277\347\224\250\350\247\204\350\214\203\350\267\257\347\224\261.md" @@ -0,0 +1,123 @@ +--- +slug: '/quick/strict-router' +title: '使用规范路由' +hide_title: true +sidebar_position: 5 +keywords: [GoFrame,GoFrame框架,规范路由,路由注册,数据结构,路由对象管理,Go语言,web server,HTTP方法,路由回调] +description: '在GoFrame框架中使用规范路由,以简化路由注册,聚焦业务逻辑。通过定义请求和响应的数据结构,标准化路由注册,并使用对象化的方式管理路由,提升代码的可维护性。提供了完整的示例代码和执行结果指导读者在实际项目中应用。' +--- + +为了简化路由注册方式,避免一些繁琐的参数处理细节, +让开发者将精力聚焦于业务逻辑本身,`GoFrame`框架提供了规范化的路由注册方式。 +规范化的路由注册方式,我们为了见名知意,便命名为了**规范路由**。 + +## 数据结构定义 + +在规范路由中,我们同样定义一个请求的数据结构来接收客户端提交的参数信息,但同时需要定义一个返回对象。 +目的是为了未来返回参数扩展的需要,以及未来标准化接口文档生成的需要。 +```go +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"姓名"` + Age int `v:"required" dc:"年龄"` +} +type HelloRes struct {} +``` +简要介绍: +- 在请求对象中,我们多了一个`g.Meta`对象的引用,并给定了一些结构体标签。该对象为**元数据对象**,用于给结构体嵌入 + 一些定义的标签信息。例如在本示例中: + - `path`:表示注册的路由地址。 + - `method`:表示注册绑定的`HTTP Method`。 +- 在属性中同样出现两个新的标签名称: + - `v`:表示校验规则,为`valid`的缩写,用于自动校验该参数。这里使用`v:"required"`表示该参数为必需参数,如果客户端未传递该参数时,服务端将会校验失败。 + - `dc`:表示参数描述信息,为`description`的缩写,用于描述该参数的含义。 + +:::info +在开发手册的对应章节中,有关于全部标签信息以及校验组件的详细讲解,这里只需要了解其作用即可,不做过多介绍。 +::: + +## 路由对象管理 + +为了更好地管理路由注册,特别是接口比较多的场景下,如果手动一一去配置路由与路由函数关系太过于繁琐。 +我们通过对象化的形式来封装路由函数,通过对象化封装的方式来简化我们的路由管理。 +我们定义一个路由对象如下: + +```go +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} +``` + +- 我们定义了一个`Hello`对象,该对象用于封装路由函数,其所有定义的公开方法都将被作为路由函数进行注册。 +- 可以看到该路由对象的`Say`方法的路由函数的定义方式,相比较于`func(*ghttp.Request)`的路由函数定义方式,更符合业务逻辑函数的定义风格。 +- 在路由回调方法`Say`中,我们通过`g.RequestFromCtx`方法从`ctx`获取原始的`*ghttp.Request`请求对象,用于自定义返回内容数据。 + +## 完整示例代码 + +我们调整我们前面的`Web Server`程序如下: +```go title="main.go" +package main + +import ( + "context" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" dc:"姓名"` + Age int `v:"required" dc:"年龄"` +} +type HelloRes struct{} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + r := g.RequestFromCtx(ctx) + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + return +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` +在本示例中: +- 通过`s.Group`的分组路由方式定义一组路由注册,在其回调方法中注册的所有路由,都会带有其定义的分组路由前缀`/`。 +- 通过`group.Bind`方法注册路由对象,该方法将会遍历路由对象的所有公开方法,读取方法的输入输出结构体定义,并对其执行路由注册。 + +## 执行结果 + +运行后,我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。 + +![img.png](img.png) + +我们尝试一下错误的参数请求 http://127.0.0.1:8000/ 但我们发现,页面没有输出任何的结果? +**这是由于参数校验失败,并未进入到我们的路由函数中,而是被`Server`直接返回了。** + +## 学习小结 + +在本章节我们学会了规范的路由注册方式,但是还缺少对返回结果,特别是产生错误之后的统一处理控制。 + +那么,我们应该如何捕获校验失败错误并自定义返回数据呢?我们将在下一章节更进一步介绍。 diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" new file mode 100644 index 00000000000..7e2457b99e0 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\345\277\253\351\200\237\345\274\200\345\247\213.md" @@ -0,0 +1,12 @@ +--- +slug: '/quick' +title: '快速开始' +sidebar_position: 0 +hide_title: true +keywords: [快速开始,GoFrame框架,GoFrame教程,Web开发,框架使用,API参考,开发指南,项目搭建,编程入门,技术文档] +description: '通过本快速开始指南,掌握使用GoFrame框架进行Web开发的基本步骤和技巧。本文提供了详细的项目搭建流程和重要的API参考,帮助开发者迅速上手GoFrame框架并提升编程效率。' +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" new file mode 100644 index 00000000000..08d7f2d8413 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\224\237\346\210\220\346\216\245\345\217\243\346\226\207\346\241\243.md" @@ -0,0 +1,166 @@ +--- +slug: '/quick/api-doc' +title: '生成接口文档' +hide_title: true +sidebar_position: 8 +keywords: [GoFrame,GoFrame框架,接口文档生成,OpenAPIv3,Swagger UI,自动化接口文档,接口数据结构,GoFrame教程,web开发,API文档] +description: '使用GoFrame框架,可以轻松生成标准化的接口文档。本文介绍了如何通过完善接口定义,结合OpenAPIv3协议和Swagger UI,自动生成和展示接口文档。在代码示例中详细讲解了接口的数据结构定义、路由中间件的设置以及如何使用GoFrame框架特性优化接口文档生成过程。' +--- + +使用`GoFrame`框架自动化生成接口文档非常简单。 + +我们首先对前面章节的接口数据结构进行简单的完善,以便生成的接口文档更加优雅。 + +## 接口定义完善 + +```go +type HelloReq struct { + g.Meta `path:"/" method:"get" tags:"Test" summary:"Hello world test case"` + Name string `v:"required" json:"name" dc:"姓名"` + Age int `v:"required" json:"age" dc:"年龄"` +} +type HelloRes struct { + Content string `json:"content" dc:"返回结果"` +} +``` + +可以看到,基于前面章节的接口数据结构定义,我们在其`g.Meta`中增加了两个标签: +- `tags`: 该接口属于哪个分类,或者接口模块。 +- `summary`: 接口描述。 + +:::info +这些标签都是`OpenAPIv3`标准接口协议的规范字段,在开发手册对应章节有关于接口文档生成以及标签的详细讲解,我们这里不作过多介绍。 +::: + +## 完整代码示例 + +```go title="main.go" +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type Response struct { + Message string `json:"message" dc:"消息提示"` + Data interface{} `json:"data" dc:"执行结果"` +} + +type HelloReq struct { + g.Meta `path:"/" method:"get" tags:"Test" summary:"Hello world test case"` + Name string `v:"required" json:"name" dc:"姓名"` + Age int `v:"required" json:"age" dc:"年龄"` +} +type HelloRes struct { + Content string `json:"content" dc:"返回结果"` +} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} + +func ResponseMiddleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ResponseMiddleware) + group.Bind( + new(Hello), + ) + }) + s.SetOpenApiPath("/api.json") + s.SetSwaggerPath("/swagger") + s.SetPort(8000) + s.Run() +} +``` +在本示例中: +- 通过`s.SetOpenApiPath("/api.json")`启用`OpenAPIv3`的接口文档生成,并指定生成的文件路径`/api.json`。 +- 通过`s.SetSwaggerPath("/swagger")`启用内置的`Swagger`接口文档UI,并指定客访问的UI地址为`/swagger`。内置的`Swagger UI`可自定义修改,具体可参考开发手册相应章节。 + +### 关于`OpenAPIv3` + +`OpenAPIv3`是目前业内的接口文档标准协议,用于接口文档的定义,通常使用`json`格式生成。该接口文档`json`文件可以用许多接口管理工具打开,例如`Swagger UI/PostMan/APIFox`等等。 + +### 关于`Swagger` + +`Swagger`是常用的接口文档UI展示工具,支持多种接口文档格式,最常用的接口文档格式当然是`OpenAPIv3`。 + +在本示例中,如果需要使用内置的`Swagger UI`查看接口文档,需要同时启用`OpenAPIv3`和`Swagger UI`。即调用`SetOpenApiPath`和`SetSwaggerPath`方法设置对应的访问路径。 + +## 执行结果 + + +### 终端输出 + +执行后,终端输出: +```text +2024-10-28 21:56:42.747 [INFO] pid[87280]: http server started listening on [:8000] +2024-10-28 21:56:42.747 [INFO] {d84db2976ea202187ba40f5d0657e4d7} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-10-28 21:56:42.747 [INFO] {d84db2976ea202187ba40f5d0657e4d7} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | GET | / | main.(*Hello).Say | main.Middleware +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|-------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|-------------------- +``` + +我们可以看到,终端输出的提示信息发生了一些变化: +- 终端输出了`swagger`的地址`http://127.0.0.1:8000/swagger/`点击该地址可以直接打开`Swagger UI`界面。 +- 终端输出了`openapi`的接口文档地址`http://127.0.0.1:8000/api.json`,该地址文件可以使用很多接口文档UI查看工具打开。 +- 路由信息也多了两行地址,对应的是`swagger`和`openapi`的地址。其中`swagger`的路由地址使用了`Server`的`HOOK`钩子特性,`HOOK`是一种比中间件更加自由的请求拦截方式,可以同时拦截动态接口和静态资源请求。有固定的几种埋点`HOOK`,这里使用的埋点是`HOOK_BEFORE_SERVE`,具体的细节请查看开发手册中`Web Server`的相关章节,这里不展开介绍。 + +### `Swagger UI` + +我们点击`swagger`地址 http://127.0.0.1:8000/swagger/ 查看: + +![alt text](QQ_1730124029804.png) + + - 由于我们只定义了一个接口,所以`Swagger`页面只看一个接口定义,在`Test`分类下展示的具体的接口名称`Hello world test case`。 + +- 在接口详情中,展示了接口的输入与输出数据结构。甚至对于输入的数据结构,`GoFrame`生成的接口文档能够自动识别其校验规则,比如是否必需参数。并展示了各个参数的数据类型、参数描述。 + + +## 学习小结 + +通过本章节的学习,我们学习到了以下几点知识: +- 通过对路由接口进行规范化的输入输出数据结构定义,可以很好地维护接口,特别是业务较复杂、接口比较多的项目。 +- 通过`GoFrame`框架自动化接口文档生成特性,可以很方便地生成基于`OpenAPIv3`标准化的接口文档、`Swagger UI`展示页面,极大降低接口维护成本、提高服务间协作效率。 + + + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..7de1af0cc26 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\347\273\237\344\270\200\350\277\224\345\233\236\347\273\223\346\236\204.md" @@ -0,0 +1,186 @@ +--- +slug: '/quick/common-response' +title: '统一返回结构' +hide_title: true +sidebar_position: 7 +keywords: [GoFrame框架,统一返回结构,JSON格式,接口数据结构,路由函数,中间件定义,执行结果,错误处理,示例代码,接口文档生成] +description: '使用GoFrame框架统一接口返回结构,以JSON格式返回数据,定义接口数据结构和路由函数,使用中间件处理执行结果,并提供完整的示例代码。通过这些方法,可以在业务项目中实现统一的数据格式封装,简化接口文档的生成和维护过程。' +--- + +本章节我们将使用固定的数据格式返回结果,无论路由函数执行成功还是失败,我们以`json`格式返回结果给调用端。 + +## 返回格式定义 + +我们定义一个统一的数据结构: +```go +type Response struct { + Message string `json:"message" dc:"消息提示"` + Data interface{} `json:"data" dc:"执行结果"` +} +``` +由于我们需要返回`json`数据格式,因此我们给每个字段都加上`json`标签。 + + +## 接口数据结构 +```go +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" json:"name" dc:"姓名"` + Age int `v:"required" json:"age" dc:"年龄"` +} +type HelloRes struct { + Content string `json:"content" dc:"返回结果"` +} +``` +- 我们给`HelloRes`增加了一个`Content`属性,用于路由函数返回具体的数据。 +- 由于需要通过`json`数据格式返回数据,因此所有的属性字段均增加`json`标签。 + +## 路由函数 +```go +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} +``` +我们这里通过`HelloRes`返回数据结构返回执行结果,而不是像前面的示例那样通过`g.RequestFromCtx(ctx)`获取原始的请求对象直接`Write`结果。 + + +## 中间件定义 + +```go +func Middleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} +``` +在该中间件中: +- 通过`r.GetHandlerResponse()`方法获取路由函数的执行结果,即路由函数返回的第一个结果参数`*HelloRes`。 +- 通过`r.GetError()`获取路由函数的执行状态,即路由函数返回的第二个结果参数`error`,如果该结果不为`nil`,表示该路由函数执行产生了错误。 +- 通过`r.Response.WriteJson`将结果整合到统一的返回数据结构`Response`,并编码为`json`格式返回给调用端。 + +## 完整示例代码 + +```go title="main.go" +package main + +import ( + "context" + "fmt" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type Response struct { + Message string `json:"message" dc:"消息提示"` + Data interface{} `json:"data" dc:"执行结果"` +} + +type HelloReq struct { + g.Meta `path:"/" method:"get"` + Name string `v:"required" json:"name" dc:"姓名"` + Age int `v:"required" json:"age" dc:"年龄"` +} +type HelloRes struct { + Content string `json:"content" dc:"返回结果"` +} + +type Hello struct{} + +func (Hello) Say(ctx context.Context, req *HelloReq) (res *HelloRes, err error) { + res = &HelloRes{ + Content: fmt.Sprintf( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ), + } + return +} + +func ResponseMiddleware(r *ghttp.Request) { + r.Middleware.Next() + + var ( + msg string + res = r.GetHandlerResponse() + err = r.GetError() + ) + if err != nil { + msg = err.Error() + } else { + msg = "OK" + } + r.Response.WriteJson(Response{ + Message: msg, + Data: res, + }) +} + +func main() { + s := g.Server() + s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ResponseMiddleware) + group.Bind( + new(Hello), + ) + }) + s.SetPort(8000) + s.Run() +} +``` +该示例中的所有代码我们已经做过了介绍,我们直接执行即可。 + +## 执行结果 + +运行后,我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。 + +![img_3.png](img_3.png) + +我们尝试一下错误的参数请求 http://127.0.0.1:8000/ 可以看到,页面输出了错误的结果信息,返回数据类型也是`json`格式。 + +![img_5.png](img_5.png) + + +## 学习小结 + +在本章节中,我们学会了使用常用的`json`格式对接口进行统一的数据格式封装,这在有着成百上千接口的业务项目中是非常有必要的。 + +可以看到,我们已经将参数完整地进行了结构化,结构中带有详细的输入输出参数描述、类型定义甚至数据校验规则, +那么我们能否基于这些信息自动化地生成接口文档呢?答案也是肯定的,我们将在下一章节介绍。 + + + + + + + + + + + + + + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" new file mode 100644 index 00000000000..1c9e192b194 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\216\267\345\217\226\350\257\267\346\261\202\345\217\202\346\225\260.md" @@ -0,0 +1,65 @@ +--- +slug: '/quick/request-input' +title: '获取请求参数' +hide_title: true +sidebar_position: 3 +keywords: [GoFrame,GoFrame框架,请求参数,HTTP参数获取,Web Server开发,Query String,HTTP方法,参数处理,ghttp,Go编程] +description: '使用GoFrame框架在Web Server中获取客户端提交的请求参数,重点讲解通过r.Get方法处理Query String、Form和Body等HTTP方法提交的参数。学习内容包括参数默认值处理及参数类型自动识别。通过示例代码详细展示如何在GoFrame中接收和处理参数,并对常见问题进行分析,为后续章节对参数对象的结构化处理奠定基础。' +--- +在前面章节中,我们学会了如何开发一个简单的`Web Server`程序。 +在本章节中,我们将会尝试着在`Web Server`中获取客户端提交的参数信息。 +为简化示例,我们使用`Query String`的方式来传递请求参数。 + +## 参数示例 + +我们将之前的`Hello World`示例程序进行简单的改造: + +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + r.Response.Writef( + "Hello %s! Your Age is %d", + r.Get("name", "unknown").String(), + r.Get("age").Int(), + ) + }) + s.SetPort(8000) + s.Run() +} +``` +在`GoFrame`框架中,获取参数非常便捷。在本示例中,我们通过`r.Get`方法获取客户端提交的参数,该方法能够获取所有`HTTP Method`提交的参数, +比如`Query String/Form/Body`等,其内部将会根据客户端提交的类型自动识别解析,比如支持自动识别参数格式例如`json/xml`等。该方法的定义如下: +```go +func (r *Request) Get(key string, def ...interface{}) *gvar.Var +``` +可以看到,`Get`方法接受两个参数,第一个为参数名称,第二个参数为非必须参数,表示默认值。返回结果为一个`*gvar.Var`对象,该对象为`GoFrame`框架 +提供的运行时泛型对象,开发者可以根据业务场景需要将参数转换为各种类型。 + +## 执行结果 + +运行后,我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。 + +![img.png](img.png) + +我们访问 http://127.0.0.1:8000/ 可以看到,页面输出结果同样符合预期。当未传递`name`参数时,程序将会使用默认值`unknown`,并且`age`参数会使用`int`类型的默认值`0`。 + +![alt text](QQ_1730178667265.png) + +## 学习小结 + +在本章节中,我们学会了如何在HTTP路由函数中获取客户端提交的参数。 + +但大家可以看到,在本示例的代码中,存在一些显而易见的问题: +- 参数的接收使用了**硬编码的参数名称**,如果在真实的项目开发中,很容易由于名称拼写错误引发异常。 +- 硬编码的参数名称不能确定业务含义和数据类型,很难进行长期管理维护,例如难以对参数进行描述说明、类型校验等基本操作。 + +在下一章节中,我们尝试通过结构化的参数对象来规避这一问题。 + diff --git "a/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" new file mode 100644 index 00000000000..1de8856a4ce --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\345\277\253\351\200\237\345\274\200\345\247\213/\350\257\267\346\261\202\346\225\260\346\215\256\347\273\223\346\236\204.md" @@ -0,0 +1,91 @@ +--- +slug: '/quick/request-struct' +title: '请求数据结构' +hide_title: true +sidebar_position: 4 +keywords: [GoFrame,GoFrame框架,请求数据结构,结构化请求,参数映射,数据校验,Web Server,请求对象,参数名称硬编码,接口优化] +description: '通过数据结构化解决参数名称硬编码问题,介绍了如何定义请求对象以接收客户端参数,通过GoFrame框架实现参数映射与校验,提高代码可维护性。同时,示例程序展示了避免冗余校验逻辑的方法,探讨了更简洁的解决方案。' +--- + +在本示例,我们尝试通过数据结构化的方式,解决上一章节的**参数名称硬编码**问题。 + +## 请求对象 + +我们定义一个请求的数据结构来接收客户端提交的参数信息: +```go +type HelloReq struct { + Name string // 名称 + Age int // 年龄 +} +``` +太棒了,看起来我们既可以对参数进行注释描述,也能确定参数的类型,不再需要参数名称硬编码。 + +## 示例代码 +我们调整我们前面的`Web Server`程序如下: +```go title="main.go" +package main + +import ( + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/net/ghttp" +) + +type HelloReq struct { + Name string // 名称 + Age int // 年龄 +} + +func main() { + s := g.Server() + s.BindHandler("/", func(r *ghttp.Request) { + var req HelloReq + if err := r.Parse(&req); err != nil { + r.Response.Write(err.Error()) + return + } + if req.Name == "" { + r.Response.Write("name should not be empty") + return + } + if req.Age <= 0 { + r.Response.Write("invalid age value") + return + } + r.Response.Writef( + "Hello %s! Your Age is %d", + req.Name, + req.Age, + ) + }) + s.SetPort(8000) + s.Run() +} +``` +在本示例中: +- 我们通过`r.Parse`方法将请求参数映射到请求对象上,随后可以通过对象的方式来使用参数。 + `r.Parse`方法支持自动解析客户端提交参数,并赋值到指定对象上。 + 内部有固定的名称映射逻辑,您将在开发手册的类型转换组件中详细了解到,这里不作过多介绍。 +- 同时,我们在本示例中增加了校验逻辑,`Name`及`Age`参数不能为空。 + +## 执行结果 + +运行后,我们访问 http://127.0.0.1:8000/?name=john&age=18 可以看到,页面输出结果符合预期。 + +![img.png](img.png) + +我们尝试一下错误的参数请求 http://127.0.0.1:8000/ 可以看到,参数被按照预期校验,页面输出结果同样符合预期 + +![img_2.png](img_2.png) + +## 学习小结 + +在本章节中,我们学会了通过结构化请求对象的方式来规避参数名称硬编码的问题, +也能很好地维护参数的名称、描述和类型定义。 +同时,我们也对参数增加了必要的校验逻辑,以保障参数的完整性。 + +但在该示例代码中,同样有值得改进的地方: +- 其中的`r.Parse`是属于业务无关的操作,理应当独立于业务逻辑之外处理。 +- 如果接口比较多,所有的接口中都要重复进行`r.Parse`操作,操作比较冗余。 +- 针对数据校验,如果接口的参数比较多,进行大量的`if`数据校验操作太过于繁琐。 + +是否有更好的方式来简化并解决这些问题呢?答案是肯定的,我们下一个章节解决这一问题。 diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" new file mode 100644 index 00000000000..6ac6eb56956 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731652866651.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" new file mode 100644 index 00000000000..513c6fd5b35 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731653678736.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" new file mode 100644 index 00000000000..b0fd96b1cfd Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655173428.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" new file mode 100644 index 00000000000..f3e887216c3 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655216354.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" new file mode 100644 index 00000000000..ec7dd64fccf Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655423345.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" new file mode 100644 index 00000000000..184c48833eb Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731655571221.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" new file mode 100644 index 00000000000..57f8d3438eb Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657619286.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" new file mode 100644 index 00000000000..5826c132a2d Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657717720.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" new file mode 100644 index 00000000000..377ec8f63e4 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/QQ_1731657799765.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" new file mode 100644 index 00000000000..bc75c7645b2 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/What's Next.md" @@ -0,0 +1,63 @@ +--- +slug: /quick/scaffold-next +title: "What's Next" +hide_title: true +sidebar_position: 3 +keywords: [GoFrame框架,Web项目,微服务开发,核心组件,业务项目,API项目,服务组件,配置组件,数据库组件,框架学习] +description: '总结了使用GoFrame框架进行业务项目搭建、项目启动、配置组件使用、数据库组件使用的基础知识,并提供了Web项目和微服务开发的学习路径。建议通过丰富的示例项目,提高对GoFrame框架的掌握,特别是其核心组件的使用。' +--- + +## 简单总结 + +好的,当您已经学习完前面的基础章节后,你应该学习到以下的内容: + +1. 业务项目搭建 +2. 项目启动逻辑 +3. 了解框架设计的整体思路 +4. 编写一个简单的 `api` 项目 + + +## 接下来的学习 + +那么接下来,我们应该怎么进一步学习框架内容呢? + +### 社区教程 + +`GoFrame`社区的老师们为大家分享了一些不错的学习资料,感兴趣的同学可以进一步了解🚀:[社区教程](../../course/社区教程.md) + +### 服务组件的进一步学习 + +大部分的同学学习框架是为了更好地开发自己的业务项目,大部分的业务项目基于两种类型:Web项目或微服务。 + +#### Web项目学习 + +如果是 `HTTP WEB` 项目,那么请进入该章节学习: [WEB服务开发](../../docs/WEB服务开发/WEB服务开发.md) + +从该章节开始,会循序渐进地介绍WEB服务的开发以及相关组件的使用。 + +#### 微服务学习 + +如果是微服务项目,那么请进入该章节学习: [微服务开发](../../docs/微服务开发/微服务开发.md) + +该章节主要是介绍如何使用框架来实现微服务开发。 + +### 框架组件的进一步学习 + +如果在开发和学习的过程中遇到某个组件不太会使用,那么可以先阅读源码,或者查看官网中相应的组件介绍章节。此外,另外一部分同学是将框架作为基础组件库来使用,那么也可以单独去查看对应组件的介绍使用章节。 + +#### 核心组件 + +核心组件是构成框架不可或缺的部分,业务项目基本都会用得到,也是框架学习的重点,章节地址: [核心组件(🔥重点🔥)](../../docs/核心组件/核心组件.md) + +#### 组件列表 + +组件列表是展示了框架所有组件的汇总章节,章节地址: [组件列表](../../docs/组件列表/组件列表.md) + +## 重要的Tips + +基于示例来学习框架使用比较事半功倍,框架提供了丰富的示例项目: + +- `Web API` 开发: [https://github.com/gogf/gf-demo-user](https://github.com/gogf/gf-demo-user) +- `Web MVC` 开发: [https://github.com/gogf/gf-demo-chat](https://github.com/gogf/gf-demo-chat) +- 微服务开发: [https://github.com/gogf/gf-demo-grpc](https://github.com/gogf/gf-demo-grpc) +- 更多示例项目: [https://github.com/gogf/awesome-gf](https://github.com/gogf/awesome-gf) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" new file mode 100644 index 00000000000..9b34abd7c03 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\345\237\272\346\234\254\344\273\213\347\273\215.md" @@ -0,0 +1,163 @@ +--- +slug: '/quick/scaffold-index' +title: '基本介绍' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame,GoFrame框架,业务项目,脚手架,安装工具,工程目录,项目模板,运行项目,接口文档,版本升级] +description: '使用GoFrame框架的脚手架来构建一个简单且完整的业务项目。内容涵盖框架工具的安装与验证、创建工程项目模板、项目模板的运行以及如何升级框架版本等重要步骤。项目模板默认支持HTTP Web Server,并提供接口文档展示与Swagger页面查看功能。' +--- + +从本章节开始,我们主要主要以完整的业务项目为主,介绍如何使用框架提供的脚手架完成一个简单的接口项目。 + +:::warning +**在进一步开始之前请您注意**,由于 `GoFrame` 算得上真正意义的 `Golang` 企业级、工程化的开发框架,她设计严谨、易于使用、文档丰富且社区活跃, **一旦您学习并掌握了框架的使用,您将难以回到过去刀耕火种的开发年代**。 + +如果您在快速开始的任何章节中遇到任何问题,欢迎在评论区留言💬,我们将尽可能及时为您提供解答🌟🌟。 +::: + + +## 安装框架工具 + +框架提供的开发工具为开发者提供了便捷的开发指令简化开发工作,提供了例如工程脚手架、代码自动生成、工具及框架更新等实用命令。工具下载地址: [https://github.com/gogf/gf/releases](https://github.com/gogf/gf/releases) + +### 预编译安装 + +预编译安装是开源社区预编译好的二进制文件,供开发者直接使用,二进制文件托管存放到 `github` 仓库中。 + + +#### `Mac` 环境 + +```bash +wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf +``` + +您可能会遇到以下常见问题: + +- 如果当前系统没有安装 `wget` 命令,那么请使用 `brew install wget` 安装后继续执行该命令。 +- 🔥如果您使用的是 `zsh` 终端,可能会存在 `gf` 别名冲突( `git fetch` 快捷指令),那么安装后(至少执行一次)请 **重启终端软件** 来继续使用。 +- 您的`Golang` +开发环境未准备好,缺少`GOOS`或`GOARCH`环境变量,建议您先查阅环境安装文档:[环境安装](../../docs/其他资料/准备开发环境/准备开发环境.md)。 +- 若无法解决的其他问题,那么可以使用后续的【源码编译安装】方式。 + +#### `Linux` 环境 + +```bash +wget -O gf "https://github.com/gogf/gf/releases/latest/download/gf_$(go env GOOS)_$(go env GOARCH)" && chmod +x gf && ./gf install -y && rm ./gf +``` + +如果系统没有安装 `wget` 命令: + +- `Ubuntu/Debian` 系统请使用 `apt-get install wget -y` +- `CentOS/RedHat` 系统请使用 `yum install wget -y` + + +#### `Windows` 环境 + +手动下载对应的二进制文件,双击按照终端指令安装即可。如果双击安装失败,请采用最后万能的【源码编译安装】方式。 + +### 源码编译安装 + +通过下载源码到本地编译生成二进制后安装到系统目录,使用的是`go install`命令,这是万能的安装方式: + +```bash +go install github.com/gogf/gf/cmd/gf/v2@latest +``` + +### 验证安装成功 + +执行 `gf -v` 指令如果能打印出例如以下信息,表示您已成功安装好了框架脚手架工具 👍 + +```html +$ gf -v +v2.7.4 +Welcome to GoFrame! +Env Detail: + Go Version: go1.23.1 darwin/arm64 + GF Version(go.mod): cannot find go.mod +CLI Detail: + Installed At: /Users/john/go/bin/gf + Built Go Version: go1.23.1 + Built GF Version: v2.7.4 +Others Detail: + Docs: https://goframe.org + Now : 2024-10-29T13:30:30+08:00 +``` + +注意其中的 `Go/GF Version` 是当前编译的二进制文件使用的 `Golang` 及 `GoFrame` 框架版本,其中的 `GoFrame Version` 是当前项目使用的 `GoFrame` 框架版本(自动检测当前目录下的 `go.mod`)。 + +:::info +常见问题注意事项:🔥如果您使用的是 `zsh` 终端,可能会存在 `gf` 别名冲突( `git fetch` 快捷指令),那么安装后(至少执行一次)请 **重启终端软件** 来继续使用。 +::: + +## 创建项目模板 + +```bash +gf init demo -u +``` + +该命令创建一个工程脚手架目录,项目名称是 `demo`,其中的 `-u` 参数用户指定是否更新项目中使用的 `goframe` 框架为最新版本。 +`GoFrame`框架有独特的项目工程结构,工程目录结构介绍具体请参考: [工程目录设计](../../docs/框架设计/工程开发设计/工程目录设计.md)。 + +框架的脚手架目录是按照通用性设计的,可以满足WEB、终端、微服务等业务开发场景。默认会生成一个 `HTTP Web Server` 的模板项目。在理解完成目录含义后,如果其中有不需要的目录,可以自行删除。 + +![](/markdown/4590d75ced1c7976fb64103d7b543758.png) + +## 运行项目模板 + +项目模板可以执行以下命令运行: + +```bash +cd demo && gf run main.go +``` + +其中的 `gf run` 是框架开发工具的动态编译命令,也可以替换为 `go run` 命令。 + +执行后,终端输出: + +```text +$ cd demo && gf run main.go +build: main.go +go build -o ./main main.go +./main +build running pid: 76159 +2022-08-22 12:20:59.058 [INFO] swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2022-08-22 12:20:59.058 [INFO] openapi specification is serving at address: http://127.0.0.1:8000/api.json +2022-08-22 12:20:59.059 [INFO] pid[76159]: http server started listening on [:8000] + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /* | github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing | GLOBAL MIDDLEWARE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | demo/internal/controller.(*cHello).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-----------------------------------------------------------------|---------------------------------- +``` + +默认情况下项目会设置Web服务端口为 `8000`、开启 `OpenAPI` 接口文档、展示 `Swagger` 接口文档页面,这些关键信息都会展示到终端。默认情况下,会打印所有的路由信息到终端,该项目模板只会添加一个仅供演示的路由: `/hello`。 + +默认路由: [http://127.0.0.1:8000/hello](http://127.0.0.1:8000/hello) + +![](/markdown/b5926140d8b840d44e15996bd019677a.png) + +`Swagger` 接口文档页面: + +![](/markdown/e59aa12576f6d575b2abf0fb8ebbf19d.png) + +## 升级框架版本 + +随时可以在项目根目录下(目录下有 `go.mod`),执行以下命令更新使用最新的框架版本: + +```bash +gf up -a +``` + + +## 学习小结 + +通过本章节的学习,我们知道如何下载和安装脚手架的`cli`工具,并通过该工具创建一个脚手架项目模板。 + +在下一章节,我们简单介绍一下这个脚手架项目模板的项目启动流程。 + diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" new file mode 100644 index 00000000000..65776d79096 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731678085194.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" new file mode 100644 index 00000000000..089282cef42 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731680426319.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" new file mode 100644 index 00000000000..5b93b250648 Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731747246720.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" new file mode 100644 index 00000000000..70854700f7c Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1731806701346.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" new file mode 100644 index 00000000000..c194d7b1c3a Binary files /dev/null and "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/QQ_1732094808338.png" differ diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" new file mode 100644 index 00000000000..aad8276a7ed --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step1 - \350\256\276\350\256\241\346\225\260\346\215\256\350\241\250.md" @@ -0,0 +1,75 @@ +--- +slug: '/quick/scaffold-api-sql' +title: 'Step1 - 设计数据表' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame,数据表设计,MySQL数据库,Docker,SQL语句,InnoDB,自动增量,varchar,数据库连接,用户状态] +description: '设计和应用MySQL数据表。我们通过定义用户信息的数据表结构并结合Docker运行MySQL进行数据表的实践操作,包括创建表结构和应用SQL语句,帮助您快速掌握数据库操作技能。' +--- + + +## 设计数据表 + +我们先定义一个数据表,以下是本章节示例会用到的数据表`SQL`文件: + +```sql +CREATE TABLE `user` ( + `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id', + `name` varchar(45) DEFAULT NULL COMMENT 'user name', + `status` tinyint DEFAULT NULL COMMENT 'user status', + `age` tinyint unsigned DEFAULT NULL COMMENT 'user age', + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +``` + +## 应用数据表 + +我们需要把这个数据表应用到`mysql`数据库中,便于后续的使用。如果你本地没有`mysql`数据库服务,那么这里使用`docker`运行一个吧: + +```bash +docker run -d --name mysql \ + -p 3306:3306 \ + -e MYSQL_DATABASE=test \ + -e MYSQL_ROOT_PASSWORD=12345678 \ + loads/mysql:5.7 +``` + +启动后,连接数据库,将数据表创建`sql`语句应用进去: +```text +$ mysql -h 127.0.0.1 -P 3306 -u root -p +mysql: [Warning] Using a password on the command line interface can be insecure. +Enter password: +Welcome to the MySQL monitor. Commands end with ; or \g. +Your MySQL connection id is 57 +Server version: 9.0.1 Homebrew + +Copyright (c) 2000, 2024, Oracle and/or its affiliates. + +Oracle is a registered trademark of Oracle Corporation and/or its +affiliates. Other names may be trademarks of their respective +owners. + +Type 'help;' or '\h' for help. Type '\c' to clear the current input statement. + +mysql> use test; +Database changed +mysql> CREATE TABLE `user` ( + -> `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'user id', + -> `name` varchar(45) DEFAULT NULL COMMENT 'user name', + -> `status` tinyint DEFAULT NULL COMMENT 'user status', + -> `age` tinyint unsigned DEFAULT NULL COMMENT 'user age', + -> PRIMARY KEY (`id`) + -> ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +Query OK, 0 rows affected, 2 warnings (0.02 sec) + +mysql> +``` + + + +## 学习小结 + +在接口开发之前先设计数据库表是比较好的开发习惯。这里我们使用的是`mysql`数据库,是需要先搭建/运行数据库服务。 + +在设计完成数据库表后,我们下一步可以使用脚手架工具自动去生成对应的数据库操作相关文件。 + diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" new file mode 100644 index 00000000000..315440bab0c --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step2 - \347\224\237\346\210\220dao_do_entity.md" @@ -0,0 +1,156 @@ +--- +slug: '/quick/scaffold-api-gen-dao' +title: 'Step2 - 生成/dao/do/entity' +hide_title: true +sidebar_position: 2 +keywords: [GoFrame,CLI工具,数据访问对象,自动化生成,数据模型,数据库配置,Make命令,数据转换,代码生成,ORM组件] +description: '使用GoFrame框架中的脚手架工具进行数据访问对象的自动生成,确保CLI工具配置正确,然后通过命令执行代码生成,创建数据库表后生成相应的dao、do和entity文件,以简化数据表的CRUD操作。展示了具体的文件结构和使用方式,以及不同类型文件的生成原理。' +--- + + + +## 检查工具配置 +在使用脚手架工具之前,请检查本地的`cli`工具配置是否正确。默认的配置如下: +```yaml title="hack/config.yaml" + +# CLI tool, only in development environment. +# https://goframe.org/docs/cli +gfcli: + gen: + dao: + - link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" + descriptionTag: true + + docker: + build: "-a amd64 -s linux -p temp -ew" + tagPrefixes: + - my.image.pub/my-app +``` +- 其中的`dao`部分配置即本次将要执行的命令的配置,其中的`link`为需要连接的数据库配置。`descriptionTag`表示为生成的`entity`代码文件增加字段描述到`description`标签中,如果数据表的`entity`对象被用到了接口`api`定义中,该标签则可作为参数描述。我们需要将此`link`配置改为我们的数据库连接地址。关于`link`配置项的详细介绍请参考章节 [ORM使用配置-配置文件](../../../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置-配置文件.md)。 +- 其中`docker`配置项是模板默认提供的配置,用于镜像编译。这里不做详解,感兴趣可以参考开发手册中开发工具相关章节。 + +## 执行代码生成 +将数据表创建好后,我们在项目根目录下执行`make dao`自动生成对应的`dao/do/entity`文件。 + +```text +$ make dao +generated: /Users/john/Temp/demo/internal/dao/user.go +generated: /Users/john/Temp/demo/internal/dao/internal/user.go +generated: /Users/john/Temp/demo/internal/model/do/user.go +generated: /Users/john/Temp/demo/internal/model/entity/user.go +done! +``` + +:::tip +`make`命令是在`*nix`系统下默认提供的指令,包括`Linux/MacOS`系统。如果是其他系统,例如`Windows`,默认不支持`make`命令,可以使用`gf gen dao`命令来替换。 +::: + +![goframe dao、do、entity](QQ_1731806701346.png) + +每张表将会生成三类`Go`文件: +- `dao`:通过对象方式访问底层数据源,底层基于`ORM`组件实现。 +- `do`:数据转换模型,用于业务模型到数据模型的转换,由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。 +- `entity`:数据模型,由工具维护,用户不能修改。工具每次生成代码文件将会覆盖该目录。 + +更详细的介绍请参考章节 [数据规范-gen dao](../../../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + +:::info +由脚手架工具生成的代码中,都会带有顶部文件注释。如果文件注释中带有`Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.`的注释描述,那么表示该文件是由脚手架工具维护,每一次代码生成都会覆盖它。 +::: + +### dao +生成的`dao`文件有两个: +- `internal/dao/internal/user.go`用于封装对数据表`user`的访问。该文件自动生成了一些数据结构和方法,简化对数据表的`CRUD`操作。该文件每次生成都会覆盖,由开发工具自动维护,开发者无需关心。 +- `internal/dao/user.go`其实是对`internal/dao/internal/user.go`的进一步封装,用于供其他模块直接调用访问。该文件开发者可以随意修改,或者扩展`dao`的能力。 + +由于生成的`internal/dao/internal/user.go`文件完全由开发工具维护,那么我们只需关心`internal/dao/user.go`这个生成的源码文件,后续如果有需要可以在这个文件上做功能扩展。 + +示例源码:https://github.com/gogf/quick-demo/blob/main/internal/dao/user.go + +```go title="internal/dao/user.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package dao + +import ( + "demo/internal/dao/internal" +) + +// internalUserDao is internal type for wrapping internal DAO implements. +type internalUserDao = *internal.UserDao + +// userDao is the data access object for table user. +// You can define custom methods on it to extend its functionality as you wish. +type userDao struct { + internalUserDao +} + +var ( + // User is globally public accessible object for table user operations. + User = userDao{ + internal.NewUserDao(), + } +) + +// Fill with you ideas below. + +``` + + + +### do + +示例源码:https://github.com/gogf/quick-demo/blob/main/internal/model/do/user.go + +```go title="internal/model/do/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package do + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// User is the golang structure of table user for DAO operations like Where/Data. +type User struct { + g.Meta `orm:"table:user, do:true"` + Id interface{} // user id + Name interface{} // user name + Status interface{} // user status + Age interface{} // user age +} +``` +关于`do`数据转换模型的使用,我们将在后续代码逻辑中做展示。 + +### entity + +示例源码:https://github.com/gogf/quick-demo/blob/main/internal/model/entity/user.go + +```go title="internal/model/entity/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package entity + +// User is the golang structure for table user. +type User struct { + Id uint `json:"id" orm:"id" description:"user id"` // user id + Name string `json:"name" orm:"name" description:"user name"` // user name + Status int `json:"status" orm:"status" description:"user status"` // user status + Age uint `json:"age" orm:"age" description:"user age"` // user age +} +``` + +可以看到该`entity`数据结构定义与数据表字段一一对应。 + + +## 学习小结 + +可以感受到,使用`GoFrame`框架便捷的脚手架工具,我们从一些重复性的代码劳动中解放了出来,极大地提高了生产效率。针对数据库的操作将会变得非常简单。 + +在下一步,我们将设计`CRUD`接口,一起看看在`GoFrame`框架中是如何快速定义接口的吧。 diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" new file mode 100644 index 00000000000..c695cb85257 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step3 - \347\274\226\345\206\231api\346\216\245\345\217\243\345\256\232\344\271\211.md" @@ -0,0 +1,117 @@ +--- +slug: '/quick/scaffold-api-definition' +title: 'Step3 - 编写api接口定义' +hide_title: true +sidebar_position: 3 +keywords: [GoFrame,接口定义,RESTful,HTTP Method,版本控制,参数校验,用户管理,元数据管理,数据返回,Golang] +description: '在项目的api目录下定义CRUD接口,采用RESTful风格的接口设计,使用HTTP Method来规范接口请求。接口定义中使用g.Meta管理元数据信息,包括路由地址、请求方式和接口描述。请求参数和返回数据结构体定义了详细的参数校验规则。接口版本控制上开始使用v1版本,以便维护未来的兼容性。接口参数采用灵活接收方式,满足接口请求的多样化需求。' +--- + +在项目的`api`目录下,我们开始定义我们的`CRUD`接口。 +- 接口我们使用`RESTful`风格设计,充分使用`GET/POST/PUT/DELETE`的`HTTP Method`,这样规范设计的接口会非常优雅。 +- 同样的,我们默认开始使用`v1`版本。使用版本号做为良好的开发习惯,有利于未来接口的兼容性维护。 + +![user api definition](QQ_1732094808338.png) + +## 创建接口 +```go title="api/user/v1/user.go" +type CreateReq struct { + g.Meta `path:"/user" method:"post" tags:"User" summary:"Create user"` + Name string `v:"required|length:3,10" dc:"user name"` + Age uint `v:"required|between:18,200" dc:"user age"` +} +type CreateRes struct { + Id int64 `json:"id" dc:"user id"` +} +``` +简要介绍: +- 接口定义中,使用`g.Meta`来管理接口的元数据信息,这些元数据信息通过标签的形式定义在`g.Meta`属性上。这里的元数据信息包括:`path`路由地址、`method`请求方式、`tags`接口分组(用于生成接口文档)、`summary`接口描述。这些元数据信息都是`OpenAPIv3`里面的东西,我们这里不做详细介绍,大家了解即可,感兴趣可以参考章节:[接口文档-OpenAPIv3](../../../docs/WEB服务开发/接口文档/接口文档-OpenAPIv3.md)。 +- 这里的`Name`和`Age`属性即是咱们接口的参数定义。其中`dc`标签是`description`的缩写,表示参数的含义;`v`标签是`valid`得缩写,表示参数的校验规则。我们这里使用到了`3`条内置的校验规则: + - `required`:该参数是必需参数。 + - `length`:参数的长度校验。 + - `between`:参数的大小校验。 + - 这里仅做了解即可,更多的校验规则请参考章节 [数据校验-校验规则](../../../docs/核心组件/数据校验/数据校验-校验规则.md)。 +- 请求的参数结构体`CreateReq`中,我们并没有定义参数的接收方式,因为`GoFrame`框架支持非常强大灵活的参数接收方式,能够自动识别`Query String/Form/Json/Xml`等提交方式,并将提交参数自动映射到请求参数接收对象上。 +- 只有返回的参数结构体中带有`json`标签,因为返回的数据往往需要转换为`json`格式给前端使用,通过`snake`的参数命名的方式更符合前端命名习惯。 + + +:::tip +在`RESTful`风格的接口设计中,我们通常使用`HTTP Method`中的`POST`来表示写入操作,而使用`PUT`来表示更新操作。 +::: + +## 删除接口 + +```go title="api/user/v1/user.go" +type DeleteReq struct { + g.Meta `path:"/user/{id}" method:"delete" tags:"User" summary:"Delete user"` + Id int64 `v:"required" dc:"user id"` +} +type DeleteRes struct{} +``` + +这里的路由标签`path`使用的`/user/{id}`,其中的`{id}`表示一个字段匹配路由,该参数通过`URL Path`的方式传递,参数名称为`id`。可以看到,我们在请求参数对象中正好定义了一个`Id`参数,是的,从路由中匹配到的`id`参数会**不区分参数字母大小写**直接映射到该`Id`上。 + +举个例子:路由`/user/1`中,`id`参数的值便是`1`;在路由`/user/100`中,`id`参数的值便是`100`。 + +## 更新接口 + +```go title="api/user/v1/user.go" +// Status marks user status. +type Status int + +const ( + StatusOK Status = 0 // User is OK. + StatusDisabled Status = 1 // User is disabled. +) + +type UpdateReq struct { + g.Meta `path:"/user/{id}" method:"put" tags:"User" summary:"Update user"` + Id int64 `v:"required" dc:"user id"` + Name *string `v:"length:3,10" dc:"user name"` + Age *uint `v:"between:18,200" dc:"user age"` + Status *Status `v:"in:0,1" dc:"user status"` +} +type UpdateRes struct{} +``` + +在这里: +- 我们这里定义了一个用户状态类型`Status`,采用的是`Golang`里面约定俗成的`enums`定义方式。这里大家了解即可。 +- 对`Status`参数的校验使用了`in:0,1`校验规则,该规则将会校验传递的`Status`的值必需是我们定义的常量的两个值`StatusOK/StatusDisabled`,即`0/1`。 +- 接口参数我们使用了指针来接收,目的是避免类型默认值对我们修改接口的影响。举个例子,假如`Status`不定义为指针,那么它就会有默认值`0`的影响,那么在处理逻辑中,很难判断到底调用端有没有传递该参数,是否要真正修改数值为`0`。但我们使用指针后,当用户没有传递该参数时,该参数的默认值就是`nil`,处理逻辑便很好做判断。 + +## 查询接口(单个) + +```go title="api/user/v1/user.go" +type GetOneReq struct { + g.Meta `path:"/user/{id}" method:"get" tags:"User" summary:"Get one user"` + Id int64 `v:"required" dc:"user id"` +} +type GetOneRes struct { + *entity.User `dc:"user"` +} +``` + +这里的返回结果我们使用了`*entity.User`结构体,该结构是前面我们通过`make dao`命令生成的`entity`,该数据结构与数据表字段一一对应。 + +## 查询接口(列表) + +```go title="api/user/v1/user.go" +type GetListReq struct { + g.Meta `path:"/user" method:"get" tags:"User" summary:"Get users"` + Age *uint `v:"between:18,200" dc:"user age"` + Status *Status `v:"in:0,1" dc:"user status"` +} +type GetListRes struct { + List []*entity.User `json:"list" dc:"user list"` +} +``` +该接口可以根据`Age`和`Status`进行查询,返回的是多条记录`List []*entity.User`。 + + +## 学习小结 + +本章节的示例源码:https://github.com/gogf/quick-demo/blob/main/api/user/v1/user.go + +可以看到,在`GoFrame`框架的脚手架项目定义`api`接口相当优雅,并且支持自动的数据校验、元数据注入、灵活的路由配置等实用特性。这种接口定义方式,可以自动化地生成接口文档,代码即文档,我们也可以保证代码和文档的一致性。 + +并且,这还并不是`GoFrame`魅力的全部,只是玫瑰上的一片花瓣。下一步,我们将使用脚手架工具,自动化地帮我们生成对应的`controller`控制代码。 diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" new file mode 100644 index 00000000000..d8d4c157c61 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step4 - \347\224\237\346\210\220controller\344\273\243\347\240\201.md" @@ -0,0 +1,151 @@ +--- +slug: '/quick/scaffold-api-controller' +title: 'Step4 - 生成controller代码' +hide_title: true +sidebar_position: 4 +keywords: [GoFrame,API代码生成,控制器代码,生成控制器,代码自动生成,接口实现,GoFrame框架,路由对象管理,接口路由实现,代码模板] +description: '根据API定义生成控制器代码,包括API接口文件、路由对象管理及路由实现代码等内容,利用GoFrame框架的命令工具快速生成相关代码模板,确保接口的完整实现,并展示如何通过一个个文件实现具体业务逻辑。' +--- + + +## 根据`api`生成代码 + +当`api`定义完成后,我们通过`make ctrl`命令(或者`gf gen ctrl`)生成控制器代码。 + +```text +$ make ctrl +generated: /Users/john/Temp/demo/api/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user_new.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_create.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_update.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_delete.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_one.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_list.go +done! +``` + +![goframe api interface controller](QQ_1731678085194.png) + +生成的代码主要包含`3`类文件。 + +## `api`接口抽象文件 + +定义了`api interface`,用于保证控制器实现的接口完整性,避免`controller`缺失部分接口实现的问题。由于`GoFrame`是一款严谨的开发框架,这种细节控制得比较好,至于该特性开发者使用与否,可以根据自身场景和需要来选择。 + +```text +/Users/john/Temp/demo/api/user/user.go +``` + +该文件由开发工具维护,开发者无需关心。 + +内容如下: +```go title="api/user/user.go" +// ================================================================================= +// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT. +// ================================================================================= + +package user + +import ( + "context" + + "demo/api/user/v1" +) + +type IUserV1 interface { + Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) + Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) + Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) + GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) + GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) +} +``` + +## `controller`路由对象管理 + +用于管理控制器的初始化,以及一些控制内部使用的数据结构、常量定义。 + +```text +generated: /Users/john/Temp/demo/internal/controller/user/user.go +generated: /Users/john/Temp/demo/internal/controller/user/user_new.go +``` + +其中`internal/controller/user/user.go`是一个空的源码文件,可用于定义一些控制器内部使用的数据结构、常量等内容。 +```go title="internal/controller/user/user.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package user + +``` + +另一个`internal/controller/user/user_new.go`文件是自动生成的路由对象创建文件。 +```go title="internal/controller/user/user_new.go" +// ================================================================================= +// This is auto-generated by GoFrame CLI tool only once. Fill this file as you wish. +// ================================================================================= + +package user + +import ( + "demo/api/user" +) + +type ControllerV1 struct{} + +func NewV1() user.IUserV1 { + return &ControllerV1{} +} +``` +这两个文件都只会生成一次,随后开发者可以随意修改、扩展。 + +:::tip +如果后续我们需要定义`v2`接口,`make ctrl`命令会类似生成`type ControllerV2 struct{}`结构体定义,以及`func NewV2() user.IUserV2`初始化方法。 +::: + + +## `controller`路由实现代码 + +用于具体的`api`接口实现的代码文件。默认情况下,会按照一个`api`接口一个源码文件的形式生成代码。当然,也可以控制按照`api`文件定义的接口聚合生成到对应的一个源码文件中。具体的命令介绍及配置请参考章节 [接口规范-gen ctrl](../../../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md)。 + +```text +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_create.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_update.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_delete.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_one.go +generated: /Users/john/Temp/demo/internal/controller/user/user_v1_get_list.go +``` + +我们打开一个文件查看生成的代码模板: + +```go title="internal/controller/user/user_v1_create.go" +package user + +import ( + "context" + + "github.com/gogf/gf/v2/errors/gcode" + "github.com/gogf/gf/v2/errors/gerror" + + "demo/api/user/v1" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + return nil, gerror.NewCode(gcode.CodeNotImplemented) +} +``` +可以看到,这只是我们定义的创建接口的实现模板,我们完善这个路由函数的具体业务逻辑即可。 + +## 学习小结 + +本章节的示例源码:https://github.com/gogf/quick-demo/tree/main/internal/controller/user + +`GoFrame`脚手架工具,帮助我们将一切与业务逻辑无关的代码都生成好了,我们只需要关注业务逻辑实现即可。并且,这些自动生成的代码文件,除了开发者可以扩展的个别代码文件,大部分代码文件都完全由工具自动维护,我们也无需关心其未来的维护。 + +是的,`GoFrame`脚手架工具的目标,就是让开发者可以将精力聚焦于业务逻辑本身,而业务逻辑以外的工作,都交给开发框架和脚手架工具来完成。 + +这样的开发方式简直太方便了,不要太舒爽!你以为这就是全部了吗?当然不是,在快速开始章节我们只会介绍部分入门级功能。当你深入地接触她,你会发现她更多的好、她的善解人意。 + +下一步,我们将完成接口的业务逻辑实现,感受下`GoFrame`数据库`ORM`组件的魅力。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" new file mode 100644 index 00000000000..aa2673c02c8 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step5 - \345\256\214\346\210\220\346\216\245\345\217\243\351\200\273\350\276\221\345\256\236\347\216\260.md" @@ -0,0 +1,168 @@ +--- +slug: '/quick/scaffold-api-implements' +title: 'Step5 - 完成接口逻辑实现' +hide_title: true +sidebar_position: 5 +keywords: [GoFrame,CRUD逻辑,接口创建,参数校验,更新接口,删除接口,查询接口,数据表操作,脚手架工具,业务逻辑实现] +description: '使用GoFrame框架完成接口逻辑实现。通过项目脚手架,预先生成了与项目业务逻辑无关的代码,集中在业务逻辑实现。介绍了CRUD操作的具体实现过程,包括创建、更新、删除和查询接口的实现方法。详细阐述了数据操作过程中对参数的校验、表单数据的插入与更新、数据的智能映射和校验机制,以及如何高效地利用GoFrame框架的功能进行开发。' +--- + + +可以看到,通过项目脚手架工具,很多与项目业务逻辑无关的代码都已经预先生成好,我们只需要关注业务逻辑实现即可。我们接下来看看如何实现`CRUD`具体逻辑吧。 + + +## 创建接口 + +### 创建逻辑实现 +```go title="internal/controller/user/user_v1_create.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) { + insertId, err := dao.User.Ctx(ctx).Data(do.User{ + Name: req.Name, + Status: v1.StatusOK, + Age: req.Age, + }).InsertAndGetId() + if err != nil { + return nil, err + } + res = &v1.CreateRes{ + Id: insertId, + } + return +} +``` +在`Create`实现方法中: +- 我们通过`dao.User`通过`dao`组件操作`user`表。 +- 每个`dao`操作都需要传递`ctx`参数,因此我们通过`Ctx(ctx)`方法创建一个`gdb.Model`对象,该对象是框架的模型对象,用于操作特定的数据表。 +- 通过`Data`传递需要写入数据表的数据,我们这里使用`do`转换模型对象输入我们的数据。`do`转换模型会自动过滤`nil`数据,并在底层自动转换为对应的数据表字段类型。在绝大部分时候,我们都使用`do`转换模型来给数据库操作对象传递写入/更新参数、查询条件等数据。 +- 通过`InsertAndGetId`方法将`Data`的参数写入数据库,并返回新创建的记录主键`id`。 + +### 参数校验实现 + +等等,大家可能会问,为什么这里没有校验逻辑呢?因为校验逻辑都已经配置到请求参数对象`CreateReq`上了。还记得前面介绍的`v`标签吗?我们再来看看这个请求参数对象: +```go title="api/user/v1/user.go" +type CreateReq struct { + g.Meta `path:"/user" method:"put" tags:"User" summary:"Create user"` + Name string `v:"required|length:3,10" dc:"user name"` + Age uint `v:"required|between:18,200" dc:"user age"` +} +type CreateRes struct { + Id int64 `json:"id" dc:"user id"` +} +``` +这里的`required/length/between`校验规则在调用路由函数`Create`之前就已经由`GoFrame`框架的`Server`自动执行了。 +如果请求参数校验失败,会立即返回错误,不会进入到路由函数。`GoFrame`框架的这种机制极大地简便了开发流程, +开发者在这个路由函数中,仅需要关注业务逻辑实现即可。 +:::info +当然,如果有一些额外的、定制化的业务逻辑校验,是需要在路由函数中自行实现的哟。 +::: +## 删除接口 + +```go title="internal/controller/user/user_v1_delete.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" +) + +func (c *ControllerV1) Delete(ctx context.Context, req *v1.DeleteReq) (res *v1.DeleteRes, err error) { + _, err = dao.User.Ctx(ctx).WherePri(req.Id).Delete() + return +} +``` +删除逻辑比较简单,我们这里用到一个`WherePri`方法,该方法会将给定的参数`req.Id`作为主键进行`Where`条件限制。 + + +## 更新接口 + +```go title="internal/controller/user/user_v1_update.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) Update(ctx context.Context, req *v1.UpdateReq) (res *v1.UpdateRes, err error) { + _, err = dao.User.Ctx(ctx).Data(do.User{ + Name: req.Name, + Status: req.Status, + Age: req.Age, + }).WherePri(req.Id).Update() + return +} +``` +更新接口也比较简单,除了已经介绍过的`WherePri`方法,在更新数据时也需要通过`Data`方法传递更新的数据。 + + +## 查询接口(单个) + +```go title="internal/controller/user/user_v1_get_one.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" +) + +func (c *ControllerV1) GetOne(ctx context.Context, req *v1.GetOneReq) (res *v1.GetOneRes, err error) { + res = &v1.GetOneRes{} + err = dao.User.Ctx(ctx).WherePri(req.Id).Scan(&res.User) + return +} +``` +数据查询接口中,我们使用了`Scan`方法,该方法可以将查询到的单条数据表记录智能地映射到结构体对象上。大家需要注意这里的`&res.User`中的`User`属性对象其实是没有初始化的,其值为`nil`。如果查询到了数据,`Scan`方法会对其做初始化并赋值,如果查询不到数据,那么`Scan`方法什么都不会做,其值还是`nil`。 + + +## 查询接口(列表) + +```go title="internal/controller/user/user_v1_get_list.go" +package user + +import ( + "context" + + "demo/api/user/v1" + "demo/internal/dao" + "demo/internal/model/do" +) + +func (c *ControllerV1) GetList(ctx context.Context, req *v1.GetListReq) (res *v1.GetListRes, err error) { + res = &v1.GetListRes{} + err = dao.User.Ctx(ctx).Where(do.User{ + Age: req.Age, + Status: req.Status, + }).Scan(&res.List) + return +} +``` +查询列表数据我们同样使用到了`Scan`方法,这个方法是非常强大的。同查询单条数据的逻辑一样,它仅会在查询的数据时才会初始化这里的`&res.List`。 + + +## 学习小结 + +本章节的示例源码:https://github.com/gogf/quick-demo/tree/main/internal/controller/user + +可以看到,使用`GoFrame`数据库`ORM`组件可以非常快速、高效地完成接口开发工作。整个`CRUD`接口开发下来,开发者需要实现的业务逻辑仅需要几行代码😼。 + +开发效率的提升,除了归功于脚手架工具自动生成的`dao`和`controller`代码之外,强大的数据库`ORM`组件也是功不可没。可以看到,我们在对数据库表进行操作时,代码量非常简洁优雅,但在数据库`ORM`组件的内部设计中,涉及很多精细的设计、严格的代码测试、年复一年的功能迭代的沉淀结果。 + +接口逻辑开发完了,在下一步,我们需要做一些数据库配置和路由注册的操作,同样也是非常简便,一起看看吧。 + diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" new file mode 100644 index 00000000000..e83e34d331b --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step6 - \351\205\215\347\275\256\344\270\216\350\267\257\347\224\261.md" @@ -0,0 +1,111 @@ +--- +slug: '/quick/scaffold-api-config-and-route' +title: 'Step6 - 配置与路由' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame,GoFrame框架,数据库驱动,mysql,路由,配置,API,服务器配置,日志模块,业务模块] +description: '引入mysql数据库驱动、添加数据库配置和路由注册步骤。详细说明了脚手架项目模板中的配置,包括工具配置和业务配置,以及如何进行配置文件的修改。' +--- + + +## 引入数据库驱动 + +`GoFrame`的数据库组件使用了接口化设计,接口与实现是分离的,这样能提供更好的抽象和扩展性。我们这里使用了`mysql`数据库,那么需要引入具体的`mysql`驱动实现。我们在`main.go`中加上`_ "github.com/gogf/gf/contrib/drivers/mysql/v2"`即可。 + +示例源码:https://github.com/gogf/quick-demo/blob/main/main.go + +```go title="main.go" +package main + +import ( + _ "demo/internal/packed" + + _ "github.com/gogf/gf/contrib/drivers/mysql/v2" + + "github.com/gogf/gf/v2/os/gctx" + + "demo/internal/cmd" +) + +func main() { + cmd.Main.Run(gctx.GetInitCtx()) +} +``` +数据库的驱动,项目只需要引入这一次即可,后续都不需要做调整。 +更多关于数据库驱动的支持以及详细介绍,请参考章节 [数据库ORM](../../../docs/核心组件/数据库ORM/数据库ORM.md)。 + +如果在没有引入数据库驱动的情况下执行数据库操作,数据库ORM组件会报如下的错误提示: +```text +cannot find database driver for specified database type "mysql", did you misspell type name "mysql" or forget importing the database driver? possible reference: https://github.com/gogf/gf/tree/master/contrib/drivers +``` + +:::tip +在脚手架项目模板`main.go`的`import`中有一段`_ "demo/internal/packed"`,表示`GoFrame`框架的资源管理,这是一个高级特性。该特性可以将任何资源打包进二进制文件,这样我们在发布的时候,仅需要发布一个二进制文件即可。我们这里没有用到该特性,因此大家了解即可,感兴趣可以后续查阅开发手册相关章节。 +::: + + +## 添加数据库配置 + +在脚手架项目模板中主要有两个配置文件。 + +### 工具配置 `hack/config.yaml` +在前面的章节我们已经有过介绍。这个配置文件主要是本地开发时候使用,当`cli`脚手架工具执行时会自动读取其中的配置内容。 + +示例源码:https://github.com/gogf/quick-demo/blob/main/hack/config.yaml + +### 业务配置 `manifest/config/config.yaml` +主要维护业务项目的组件配置信息、业务模块配置,完全由开发者自行维护。在程序启动时会读取该配置文件。该业务 +默认的脚手架项目模板提供的业务配置如下: +```yaml title="manifest/config/config.yaml" +# https://goframe.org/docs/web/server-config-file-template +server: + address: ":8000" + openapiPath: "/api.json" + swaggerPath: "/swagger" + +# https://goframe.org/docs/core/glog-config +logger: + level : "all" + stdout: true + +# https://goframe.org/docs/core/gdb-config-file +database: + default: + link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test" +``` + +默认提供了`3`项组件的配置,分别为: +- `server`:`Web Server`的配置。这里默认配置的监听地址为`:8000`,并启用了接口文档特性。 +- `logger`:默认日志组件的配置。这里的日志级别是所有日志都会打印,并且都会输出到标准输出。 +- `database`:数据库组件的配置。这只是一个模板,需要我们根据实际情况修改链接地址。 + +每一项组件配置的注释上提供了官网文档的配置参考链接。我们这里需要修改数据库配置中的链接信息,为我们真实可用的链接信息。关于数据库的配置详细介绍,感兴趣请参考章节:[ORM使用配置-配置文件](../../../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置-配置文件.md) + +示例源码:https://github.com/gogf/quick-demo/blob/main/manifest/config/config.yaml + +## 添加路由注册 + +添加我们新填写的`api`到路由非常简单,如下: + +![goframe路由注册](QQ_1731680426319.png) + +在分组路由的`group.Bind`方法中,通过`user.NewV1()`添加我们的路由对象即可。 + +示例源码:https://github.com/gogf/quick-demo/blob/main/internal/cmd/cmd.go + +到目前为止,我们的接口已经完全开发完了,下一步,我们将启动服务,并做一些接口测试,查看效果。 + +## 学习小结 + + +当我们在使用数据库功能的时候,需要引入特定的数据库驱动。在`GoFrame`官方仓库中,通过社区组件的形式提供了常用数据库的驱动实现。我们的程序主要使用的是业务配置,并且需要将其中的数据库连接地址修改为我们搭建的数据库地址。 + +路由注册就太简单了,添加一个`controller`对象到分组路由注册中`group.Bind`即可。 + +目前为止,我们已经将`CRUD`接口开发完成啦👏👏。可以看到,我们做的事情主要是这几项: +- 数据库表设计 +- `api`接口定义 +- 接口的业务逻辑实现 +- 简单的配置和路由注册 + +下一步,我们启动程序看看效果吧。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" new file mode 100644 index 00000000000..0ce111086de --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/Step7 - \345\220\257\345\212\250\344\270\216\346\265\213\350\257\225.md" @@ -0,0 +1,147 @@ +--- +slug: '/quick/scaffold-api-run-and-test' +title: 'Step7 - 启动与测试' +hide_title: true +sidebar_position: 6 +keywords: [GoFrame,启动服务,接口文档,接口测试,RESTful API,CRUD,接口校验,数据查询,数据修改,数据删除] +description: '通过命令行启动服务,并使用swagger生成接口文档。支持创建用户、查询用户信息、修改用户数据以及删除用户的RESTful API接口。同时支持使用curl命令进行接口测试,提供详细的校验规则和错误码以确保数据的准确性和可靠性。' +--- + +## 启动服务 + +我们可以使用命令行或者`IDE`自带的启动工具来启动服务,为了简化示例,我们这里直接在命令行中使用`go run main.go`来启动我们的服务: + +```text +$ go run main.go +2024-11-16 16:40:07.394 [INFO] pid[39511]: http server started listening on [:8000] +2024-11-16 16:40:07.394 [INFO] {18594fad2e66081870e88c6e1440060b} swagger ui is serving at address: http://127.0.0.1:8000/swagger/ +2024-11-16 16:40:07.394 [INFO] {18594fad2e66081870e88c6e1440060b} openapi specification is serving at address: http://127.0.0.1:8000/api.json + + ADDRESS | METHOD | ROUTE | HANDLER | MIDDLEWARE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /api.json | github.com/gogf/gf/v2/net/ghttp.(*Server).openapiSpec | +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /hello | demo/internal/controller/hello.(*ControllerV1).Hello | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | ALL | /swagger/* | github.com/gogf/gf/v2/net/ghttp.(*Server).swaggerUI | HOOK_BEFORE_SERVE +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /user | demo/internal/controller/user.(*ControllerV1).GetList | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | POST | /user | demo/internal/controller/user.(*ControllerV1).Create | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | DELETE | /user/{id} | demo/internal/controller/user.(*ControllerV1).Delete | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | GET | /user/{id} | demo/internal/controller/user.(*ControllerV1).GetOne | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- + :8000 | PUT | /user/{id} | demo/internal/controller/user.(*ControllerV1).Update | ghttp.MiddlewareHandlerResponse +----------|--------|------------|-------------------------------------------------------|---------------------------------- +``` + +可以看到,我们开发的`CRUD`接口已经成功注册到`Web Server`上,并正确显示到了终端。同时,我们启用了接口文档特性,我们看看自动生成的接口文档。 + +## 接口文档 + +根据终端打印的地址,我们访问:http://127.0.0.1:8000/swagger/ + +![goframe api swagger](QQ_1731747246720.png) + +:::tip +自动化的接口文档生成也是`GoFrame`框架提供的非常强大的特性之一,我们这里不做详细的介绍,感兴趣的朋友可以参考章节:[接口文档](../../../docs/WEB服务开发/接口文档/接口文档.md) +::: + +## 接口测试 + +为了简化测试操作,我们使用`curl`命令来做测试,并使用`json`格式提交和接受返回参数。 + +:::tip +以下执行命令中前面的`$`符号,表示终端命令行工具的提示符号,并不是我们输入命令的一部分,不同的终端命令行提示符号不同。 +::: + +### 创建接口 + +创建请求需要使用`POST`方式提交。 + +```bash +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"john","age":20}' +{"code":0,"message":"","data":{"id":1}} + +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"alice","age":18}' +{"code":0,"message":"","data":{"id":2}} +``` +这里返回的`code`为`0`表示执行成功。 + +我们构造不符合校验规则的请求,看看效果: + +```bash +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"smith","age":16}' +{"code":51,"message":"The Age value `16` must be between 18 and 200","data":null} + +$ curl -X POST 'http://127.0.0.1:8000/user' -d '{"name":"sm","age":18}' +{"code":51,"message":"The Name value `sm` length must be between 3 and 10","data":null} +``` + +可以看到,校验规则起了作用,返回了具体的校验错误信息,错误码`code`为`51`,这个是框架内置的错误码,表示校验错误,开发者也可以自定义错误码。更多的错误码介绍请查看开发手册相关章节。 + +### 查询接口 + +#### 查询单条数据 +```bash +$ curl -X GET 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":{"id":1,"name":"john","status":0,"age":20}} + +$ curl -X GET 'http://127.0.0.1:8000/user/2' +{"code":0,"message":"","data":{"id":2,"name":"alice","status":0,"age":18}} +``` + +#### 查询数据列表 + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user' +{"code":0,"message":"","data":{"list":[{"id":1,"name":"john","status":0,"age":20},{"id":2,"name":"alice","status":0,"age":18}]}} + +$ curl -X GET 'http://127.0.0.1:8000/user?age=18' +{"code":0,"message":"","data":{"list":[{"id":2,"name":"alice","status":0,"age":18}]}} +``` + +### 修改接口 + +创建请求需要使用`PUT`方式提交。 + +```bash +$ curl -X PUT 'http://127.0.0.1:8000/user/1' -d '{"age":26}' +{"code":0,"message":"","data":null} +``` + +执行成功后,我们再查询对应的数据,看看是否已经修改成功: + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":{"id":1,"name":"john","status":0,"age":26}} +``` + +### 删除接口 + +创建请求需要使用`DELETE`方式提交。 + +```bash +$ curl -X DELETE 'http://127.0.0.1:8000/user/1' +{"code":0,"message":"","data":null} +``` + +执行成功后,我们再查询所有的数据列表,看看是否已经被删除: + +```bash +$ curl -X GET 'http://127.0.0.1:8000/user' +{"code":0,"message":"","data":{"list":[{"id":2,"name":"alice","status":0,"age":18}]}} +``` + +可以看到,数据已经被成功删除了。 + + + +## 学习小结 + +可以看到,通过`GoFrame`框架的脚手架工具生成的项目模板,我们可以很高效、快速地完成接口开发工作,并且能自动生成接口文档,方便前后端协作。 + + +到此为止,一个使用`GoFrame`框架的`CRUD`接口项目便快速完成了。但`GoFrame`框架的优秀之处,还远不止于此,她的更多特性,等待着您的进一步探索。 diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" new file mode 100644 index 00000000000..d0636b2c553 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\346\216\245\345\217\243\345\274\200\345\217\221/\346\216\245\345\217\243\345\274\200\345\217\221.md" @@ -0,0 +1,18 @@ +--- +slug: '/quick/scaffold-api' +title: '接口开发🌟' +hide_title: true +sidebar_position: 1 +keywords: [接口开发,Golang,CRUD接口,GoFrame,GoFrame框架,API开发,脚手架工具,快速演示,源码示例,Go开发] +description: '学习使用GoFrame框架提供的工具开发CRUD接口,包括API开发的基本步骤和技巧,并提供完整示例源码以供参考,帮助初学者快速上手接口开发。' +--- + +在本章节,我们将学会`Golang`工程师的基本讨饭技能,开发`CRUD`接口。使用`GoFrame`框架提供的脚手架工具开发`API`接口非常简单,我们一起来吧,`Let's Go!` + +:::tip +该章节的完整示例源码可以在这里找到:https://github.com/gogf/quick-demo +::: + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" new file mode 100644 index 00000000000..73b1c4b2ffc --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\345\220\257\345\212\250.md" @@ -0,0 +1,123 @@ +--- +slug: '/quick/scaffold-boost' +title: '项目启动' +hide_title: true +sidebar_position: 0 +keywords: [GoFrame框架,项目启动,程序启动,main.go,引导启动,路由注册,HTTP Server,分组路由,路由对象,阻塞运行] +description: '当您开始第一个项目时,可能对工程目录感到困惑,但通过本章节,您可以了解项目的启动过程及其涉及的目录。主要程序入口为main.go,通过调用internal/cmd包引导程序启动。默认创建的HTTP Server支持多种路由注册,使项目启动简单快捷。具体使用细节可参阅GoFrame框架的相关文档。' +--- + +当您开始第一个项目的时候,可能对工程下面有这么多目录感觉困惑,没关系,您可以通过这个章节 [工程目录设计🔥](../../docs/框架设计/工程开发设计/工程目录设计.md) 先了解一下各个目录的作用。接下来我们会介绍一下项目是如何启动的,一个程序的启动串联了哪些目录,让大家对程序整体启动的经脉有所了解。 + + +## main.go + +所有的程序入口都是由 `main.go` 进入,该文件主要是调用 `internal/cmd` 包的对应命令引导程序启动。在项目模板中,默认会执行 `internal/cmd` 包的 `Main` 对象 `Run` 命令引导程序启动。 + +项目的所有的核心业务逻辑都是放到了`internal`目录下,该目录是`Golang`的特性,用于对外隐藏可见性。`internal`目录下面的代码内容将无法被外部通过`import`的方式引用,提高项目的安全性和简洁性。 + +:::tip +框架的核心组件均需要传递 `context` 上下文参数,这里使用 `gctx.GetInitCtx` 表示承接父进程的链路跟踪信息,如果没有父进程那么会创建一个带链路跟踪特性的 `context` 上下文对象给下游链路。 +::: + +![main.go](QQ_1731652866651.png) + +## 引导启动 + +`Main` 对象的 `Run` 命令的主要作用是做引导启动,将一些动态初始化的逻辑放到 `Main` 的 `Run` 方法中。在项目模板中,默认创建一个 `HTTP Server`,然后通过分组路由的方式注册路由,并启动 `HTTP Server`。随后 `HTTP Server` 将会阻塞运行,它同时也会异步监听系统信号,直至收到退出信号后,它会优雅关闭连接随后退出进程。 + +:::tip +框架的命令行管理默认采用了结构化的对象管理方式,详细介绍感兴趣请参考章节:[命令管理](../../docs/核心组件/命令管理/命令管理.md) +::: + +![main command](QQ_1731653678736.png) + +## 路由注册 + +在项目模板中使用了 `Group` 方法创建了分组路由,框架的 `HTTP Server` **支持多种路由注册方式**,而分组路由也是最常见的路由注册方式。 + +```go +s := g.Server() +s.Group("/", func(group *ghttp.RouterGroup) { + group.Middleware(ghttp.MiddlewareHandlerResponse) + group.Bind( + hello.NewV1(), + ) +}) +``` + +- 在分组路由的闭包方法内部,通过 `Middleware` 方法注册了一个中间件,该中间件是 `HTTP Server` 组件用于规范化路由的数据返回。 +- 随后通过 `Bind` 方法的规范化路由方式绑定一个 `hello.NewV1()` 返回的路由对象,该路由对象下的所有公开方法均会被自动注册到路由。我们的项目脚手架支持接口的版本管理,默认情况下我们的路由对象都是`v1`版本,并且通过`NewV1`的方式创建。 + +:::tip +详细的HTTP Server路由介绍请参考章节: [路由管理🔥](../../docs/WEB服务开发/路由管理/路由管理.md) +::: + +## 路由对象 + + +### 对象创建 +路由对象由`hello.NewV1()`方法返回,其定义如下: + +![alt text](QQ_1731655173428.png) + +可以看到`NewV1`方法其实返回的是一个接口,而不是具体的对象。该接口的定义如下: + +![alt text](QQ_1731655571221.png) + +为什么这里不直接返回`ControllerV1`对象,而是返回`hello.IHelloV1`接口呢? + +试想一下,假如咱们定义了很多`api`接口,但是具体实现的`controller`只实现了其中一部分,而在运行时才能发现有缺失。那么我们如何能提前发现这个问题呢?对的,就是依赖这个接口实现。假如`controller`只实现了部分接口,那么在编译阶段就会展示错误,并且现代的编辑器中也会提前发现告警。 + +:::tip +首先,并不是必需这么做,脚手架模板提供的是比较严谨的代码编写方式。 + +其次,这里的绝大部分代码,是通过`cli`工具的`gf gen ctrl`命令自动解析`api`目录的接口定义自动生成的。 +::: + +### 函数定义 +对应的路由函数定义如下: + +![alt text](QQ_1731655216354.png) + +`Hello` 方法对应的路由信息是定义到 `HelloReq` 输入参数对象中的,该对象的数据结构定义如下: + +![alt text](QQ_1731655423345.png) + +:::tip +这种通过统一的中间件返回统一的数据结构,统一路由对象的方法管理路由的方式,叫做**规范路由**。 +更详细的介绍请参考章节:[路由注册-规范路由](../../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md) 。 +::: + +## 运行服务 + +### 阻塞运行 + +通过 `HTTP Server` 的 `Run` 方法启动 `HTTP Server`,随后 `HTTP Server` 将会阻塞运行接收客户端请求,并监听进程信号,用于 `HTTP Server` 重启/关闭。 + +运行后,终端输出如下: + +![alt text](QQ_1731657619286.png) + +可以看到,我们启用了`API`接口文档以及`Swagger UI`。 + + +### 查看效果 + +我们访问 http://127.0.0.1:8000/hello 查看接口效果: + +![demo hello world](QQ_1731657717720.png) + +我们访问 http://127.0.0.1:8000/swagger 查看`Swagger UI`: + +![demo swagger ui](QQ_1731657799765.png) + + + +## 学习小结 + +通过本章节的学习,我们了解了脚手架项目模板程序的执行流程。 +这里其实有比较多的细节没有详细讲解,感兴趣可以了解下对应栏目下的关联文档资料。 + +在下一章节,我们将尝试着通过脚手架项目模板编写简单的`CRUD`接口,实现对数据库表的增删查改。 + diff --git "a/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" new file mode 100644 index 00000000000..9f01231afa9 --- /dev/null +++ "b/versioned_docs/version-2.8.x/quick/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266/\351\241\271\347\233\256\350\204\232\346\211\213\346\236\266.md" @@ -0,0 +1,12 @@ +--- +slug: '/quick/scaffold' +title: '项目脚手架🌟' +sidebar_position: 1 +hide_title: true +description: '利用GoFrame框架搭建项目脚手架,尤其适合希望通过Go语言与GoFrame框架高效结合,实现前端与后端技术的无缝集成的开发者,助力快速启动与灵活高效的项目开发。' +keywords: [GoFrame,GoFrame框架,项目脚手架,快速构建Web应用,Go语言,前端集成,后端技术,高效项目开发,灵活开发,Web应用] +--- + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/QQ_1731813654454.png b/versioned_docs/version-2.8.x/release/QQ_1731813654454.png new file mode 100644 index 00000000000..6f01877e72b Binary files /dev/null and b/versioned_docs/version-2.8.x/release/QQ_1731813654454.png differ diff --git a/versioned_docs/version-2.8.x/release/v2.0 2022-03-09.md b/versioned_docs/version-2.8.x/release/v2.0 2022-03-09.md new file mode 100644 index 00000000000..04d8b1ceaff --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.0 2022-03-09.md @@ -0,0 +1,271 @@ +--- +slug: '/release/v2.0.0' +title: 'v2.0 2022-03-09' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,v2.0发布,全链路跟踪,ORM改进,组件接口化,全新功能,开发效率,错误堆栈,接口化设计] +description: 'GoFrame v2.0版本发布,增强了工程设计和全链路跟踪功能,提供了严格规范的命名和参数传递方式。新增特性包括全面的错误码支持和组件接口化设计,提升了扩展性和开发效率。同时,ORM和日志组件功能得到重大改进,框架更加易用。' +--- + +大家好啊!万众瞩目的 `GoFrame v2` 版本终于发布了正式版本!本次版本包含了大量改进以及新特性,同时新增了一些开创性的功能特性。 + +去年夏天到今年春天,一路以来的努力,希望大家满意。 + +感谢所有社区小伙伴的贡献,感谢社区朋友们的支持! + +新的一年,我们继续,脚踏实地,不忘初心! + +升级指导: [如何从v1愉快升级到v2](../docs/其他资料/如何从v1愉快升级到v2.md) + +## 一、重要特性 + +### 1、新版工程设计 + +- 更加严谨规范 +- 命名风格的规范 +- 指针与值传递参数的规范 +- 进一步简便、提高开发效率 +- 新版开发工具支持工程规范准确落地 +- `Entity/DAO/DO` 特性 +- 面向接口化设计 +- 更多详细介绍: [工程开发设计(🔥重点🔥)](../docs/框架设计/工程开发设计/工程开发设计.md) + +### 2、全链路跟踪特性 + +- 可观测性更进一步:大胆的前瞻以及决心 +- 框架默认启用 `OpenTelemetry` 特性 +- 框架默认创建 `TraceID`,按照 `OpenTelemetry` 生成标准 +- 框架核心组件均支持链路跟踪信息传递 +- 日志组件支持链路信息打印 +- 更多详细介绍: [全链路跟踪设计](../docs/框架设计/全链路跟踪设计.md) + +### 3、规范路由注册特性 + +- 规范化API按照结构化编程设计 +- 规范化API接口方法参数风格定义 +- 更加简化的路由注册与维护 +- 统一接口返回数据格式设计 +- 自动的API参数对象化接收与校验 +- 自动生成基于标准 `OpenAPIv3` 协议的接口文档 +- 自动生成 `SwaggerUI` 页面 +- 更多详细介绍: [路由注册-规范路由](../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md) + +### 4、全错误堆栈特性 + +- 框架层面所做的重大决定 +- 框架 **所有** 组件错误均支持错误堆栈 +- 详细介绍: [全错误堆栈设计](../docs/框架设计/全错误堆栈设计.md) + +### 5、全新错误码特性 + +- 采用接口化设计,扩展性高 +- 提供可供选择的常见错误码 +- 框架核心组价底层已增加错误码支持,例如根据 `error` 中的错误码可以识别是否 `DB` 执行错误 +- 更多详细介绍: [错误处理-错误码特性](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码特性.md) + +### 6、组件接口化设计 + +- 自顶向下统一化的接口化设计 +- 核心组件均采用接口化设计 +- 更高的扩展性、可定制性 +- 更多详细介绍: [接口化与泛型设计](../docs/框架设计/接口化与泛型设计.md) + +### 7、框架泛型的支持 + +- 什么是框架 `gvar` 泛型? +- 框架 `gvar` 泛型在框架核心组件中的大量使用 +- 框架 `gvar` 泛型的重要价值 +- 为什么不建议在顶层业务中使用泛型 +- 更多详细介绍: [接口化与泛型设计](../docs/框架设计/接口化与泛型设计.md) + +### 8、ORM的大量改进 + +- 详细介绍: [数据库ORM🔥](../docs/核心组件/数据库ORM/数据库ORM.md) + +### 9、其他重要改进 + +#### 1)日志组件 `Handler` 特性 + +- 采用中间件设计 +- 支持多个 `Handler` 处理 +- 为开发者自定义日志处理提供了更灵活强大的支持 +- 更多详细介绍: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + +#### 2)日志组件颜色打印 + +- 在终端中默认输出颜色打印 +- 默认不同级别不同的颜色,可配置 +- 输出到文件/自定义 `Writer` 默认关闭,可通过相关配置开启 +- 更多详细介绍: [日志组件-颜色打印](../docs/核心组件/日志组件/日志组件-颜色打印.md) + +#### 4)调试模式介绍完善 + +- 更多详细介绍: [调试模式](../docs/核心组件/调试模式.md) + +## 二、功能改进 + +### 1、数据组件 + +1. `/database/gdb` +1. 废弃 `Table` 方法,统一使用 `Model` 方法创建 `Model` 对象。 +2. 废弃 `Model` 中的 `Struct/Structs` 方法,统一使用 `Scan` 方法执行查询结果到 `Struct` 对象/对象数组映射转换: [ORM查询-Scan](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Scan映射.md) +3. 废弃 `BatchInsert/BatchReplace/BatchSave` 方法,统一使用 `Insert/Replace/Save` 方法实现,内部自动实现参数类型识别采用单条写入还是批量写入: [ORM链式操作-写入保存](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) +4. 增加 `DoFilter` 接口方法,用于 `ORM` 提交执行 `SQL&Args` 到底层 `driver` 之前的 `SQL&Args` 自定义过滤: [ORM接口开发-回调处理](../docs/核心组件/数据库ORM/ORM接口开发/ORM接口开发-回调处理.md) +5. 增加 `DoCommit` 接口方法,用于 `ORM` 提交执行 `SQL&Args` 到底层 `driver` 之前的自定义处理 +6. 增加 `ConvertDataForRecord` 接口方法,用于自定义的数据转换处理。 [ORM接口开发-回调处理](../docs/核心组件/数据库ORM/ORM接口开发/ORM接口开发-回调处理.md) +7. 增加 `Raw` 方法,用于通过原始 `SQL` 语句构建 `Model` 对象,随后可以使用 `Model` 的链式操作以及各种特性: [ORM链式操作-模型创建](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型创建.md) +8. 增加 `Handler` 特性,用于自定义的 `Model` 对象修改,并返回新的 `Model` 对象,可轻松地复用常见的逻辑: [ORM链式操作-Handler特性](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Handler特性.md) +9. 增加 `Union/UnionAll` 特性,用于多条 `SQL/Model` 的查询结果合并: [ORM查询-Union/UnionAll](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-UnionUnionAll.md) +10. 增加 `With` 特性对条件查询以及排序语句的配置支持: [模型关联-静态关联-With特性](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) +11. 增加 `OnDuplicate/OnDuplicateEx` 方法,用于指定 `Save` 方法的更新/不更新字段: [ORM链式操作-写入保存](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) +12. 增加 `Wheref/WhereOrf` 方法,用于带有格式化字符串语句的条件传递: [ORM查询-Where条件](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +13. 增加 `WhereLT/WhereLTE/WhereGT/WhereGTE` 以及 `WhereOrLT/WhereOrLTE/WhereOrGT/WhereOrGTE` 方法,用以为ORM添加常见的比较条件:https:// +14. 增加 `WherePrefix/WhereOrPrefix` 方法,用以在为条件字段加上表前缀,常用于关联查询中 +15. 增加 `FieldsPrefix/FieldsExPrefix` 方法,用于为查询的字段增加自定义的表前缀,常用于关联查询中 +16. 增加 `FieldsCount/FieldsSum/FieldsMin/FieldsMax/FieldsAvg` 方法,用于增加常见的统一查询条件 +17. 增加 `LeftJoinOnField/RightJoinOnField/InnerJoinOnField` 方法,用于便捷关联带有相同字段名称的表 +18. 增加 `OmitEmptyWhere/OmitEmptyData` 方法,用于特定过滤 `Where` 条件和 `Data` 数据中的空值数据: [ORM链式操作-字段过滤](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) +19. 增加 `OmitNil/OmitNilWhere/OmitNilData` 方法,用于特定过滤 `Where` 条件和 `Data` 数据中的 `nil` 数据: [ORM链式操作-字段过滤](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) +20. 增加 `TimeZone` 配置项,用于数据库查询的自定义时区转换(目前支持 `mysql/pgsql`): [ORM使用配置](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +21. 改进 `Cache` 缓存特性,支持增加准确的缓存参数控制 +22. 增加 `Close` 方法,用于手动关闭数据库连接 +23. 去掉 `ORM` 在使用没有自定义配置时默认 `100` 连接数的配置限制。 +24. 改进时间维护特性,不再自动过滤开发者提交的 `CreatedAt/UpdatedAt/DeletedAt` 相关参数,意味着开发者可以在 `ORM` 操作中自定义相关时间字段的更新。 +25. 改进数据库执行的SQL日志记录,增加影响行数记录 +26. 接口方法 `HandleSqlBeforeCommit` 名称修改为了 `DoCommit`。 +27. 数据库方法操作统一增加 `context.Context` 作为第一必须参数。 +28. 修复 `gdb` 组件的 `With` 特性多层级查询失效问题。 +29. 删除查询结果类型 `Record/Result` 的所有已废弃的方法。 +30. 单元测试完善。 +31. `/database/gredis` +32. 采用适配器模式,以接口化设计重构该组件,以提高扩展性: [Redis-接口化设计](../docs/组件列表/NoSQL%20Redis/Redis-接口化设计.md) + +33. 默认提供基于第三方 `goredis` 包的适配器实现,增加了对 `Redis` 集群的支持: [Redis-配置管理](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) +34. 由于集群特性的支持,配置文件格式发生改变: [Redis-配置管理](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) + +### 2、网络组件 + +1. `/net/ghttp` +1. 新增路由注册方式: [路由注册-规范路由](../docs/WEB服务开发/路由管理/路由管理-路由注册/路由注册-规范路由/路由注册-规范路由.md) +2. 默认将 `Request` 对象注入到 `ctx` 上下文对象中,并增加 `RequestFromCtx/g.RequestFromCtx` 方法获取 `ctx` 中的 `Request` 对象。 +3. 将 `Client` 功能特性进行抽离,封装为 `gclient` 组件: [HTTPClient](../docs/WEB服务开发/HTTPClient/HTTPClient.md) +4. `Server` 日志增加对 `ctx` 上下文链路信息打印的支持,并改进日志格式: [链路跟踪](https://johng.cn/observability/tracing) +5. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 +6. 废弃 `ghttp` 中相关的 `HTTP Client` 直接操作方法,必须通过创建 `Client` 对象来实现客户端访问操作。 +7. 废弃 `Controller` 路由注册方式,并删除相关实现逻辑代码。 +2. `/net/gtrace` +1. 升级 `go.opentelemetry.io/otel` 到最新的正式版。 +2. 完善全新的链路跟踪使用文档: [服务链路跟踪](../docs/服务可观测性/服务链路跟踪/服务链路跟踪.md) + +### 3、系统组件 + +1. `/os/glog` + 1. 为推进可观测性特性,落实链路跟踪规范,所有日志打印方法均增加 `context.Context` 参数。 + 2. 日志组件增加了 `Handler` 特性,采用中间件设计、支持多个 `Handler` 处理,为开发者自定义日志处理提供了更灵活强大的支持: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 3. 日志组件增加了对内容的颜色打印特性支持,在终端中默认输出颜色打印,输出到文件/自定义 `Writer` 默认关闭、可通过相关配置开启: [日志组件-颜色打印](../docs/核心组件/日志组件/日志组件-颜色打印.md) + 4. 废弃 `Println` 方法。 + 5. 文档更新: [日志组件](../docs/核心组件/日志组件/日志组件.md) +2. `/os/gres` + 1. 新增 `Export` 方法用于将资源组件中的文件导出到本地磁盘: [资源管理-方法介绍](../docs/核心组件/资源管理/资源管理-方法介绍.md) +3. `/os/gfile` + 1. 新增 `SizeFormat` 方法用于获取指定文件格式化后的大小字符串。 + 2. 文档更新: [文件管理-gfile](../docs/组件列表/系统相关/文件管理-gfile.md) +4. `/os/gcache` + 1. 采用适配器模式,以接口化设计重构该组件,以提高扩展性: [缓存管理-接口设计](../docs/核心组件/缓存管理/缓存管理-接口设计.md) + 2. 默认提供了基于进程内存的缓存实现: [缓存管理-内存缓存](../docs/核心组件/缓存管理/缓存管理-内存缓存.md) + 3. 所有操作方法增加了 `context.Context` 上下文参数。 + 4. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 + 5. 增加 `Must*` 方法,用以直接获取参数并在产生错误时直接 `panic`。 +5. `/os/gcfg` + 1. 采用适配器模式,以接口化设计重构该组件,以提高扩展性: [配置管理-接口化设计](../docs/核心组件/配置管理/配置管理-接口化设计/配置管理-接口化设计.md) + 2. 默认提供了基于文件系统的配置管理实现: [配置管理](../docs/核心组件/配置管理/配置管理.md) + 3. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 + 4. 所有操作方法增加了 `context.Context` 上下文参数。 + 5. 增加 `GetWithEnv` 方法,当配置适配器中无法查找到对应的参数时,将会自动读取环境变量中的相应参数: [配置管理-配置读取](../docs/核心组件/配置管理/配置管理.md) + 6. 增加 `GetWithCmd` 方法,当配置适配器中无法查找到对应的参数时,将会自动读取命令行参数中的相应参数: [配置管理-配置读取](../docs/核心组件/配置管理/配置管理.md) + 7. 增加 `Must*` 方法,用以直接获取参数并在产生错误时直接 `panic`。 + 8. 配置组件易用性改进,通过单例对象访问配置组件将会按照 `toml/yaml/yml/json/ini/xml` 文件后缀自动检索配置文件: [配置管理](../docs/核心组件/配置管理/配置管理.md) +6. `/os/gcmd` + 1. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 + 2. 全新的多层级命令行管理方式,支持自动生成命令行使用提示: [命令管理-命令行对象](../docs/核心组件/命令管理/命令管理-命令行对象.md) + 3. 增加基于对象的命令行管理方式,更适合大量的终端命令场景: [命令管理-结构化参数](../docs/核心组件/命令管理/命令管理-结构化参数.md) +7. `/os/genv` + 1. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 +8. `/os/gcron` + 1. 定时任务方法定义增加 `context.Context` 参数。 + 2. 所有创建定时任务方法增加 `context.Context` 参数。 + 3. 文档更新: [定时任务-gcron](../docs/组件列表/系统相关/定时任务-gcron/定时任务-gcron.md) +9. `/os/gtime` + 1. 废弃 `Second/Millisecond/Microsecond/Nanosecond` 包方法,使用 `Timestamp/TimestampMilli/TimestampMicro/TimestampNano` 方法替代。 + 2. 文档更新: [时间管理-gtime](../docs/组件列表/系统相关/时间管理-gtime/时间管理-gtime.md) +10. `/os/gtimer` + 1. 定时器方法定义增加 `context.Context` 参数。 + 2. 所有创建定时器方法增加 `context.Context` 参数。 + 3. 改进基于优先级队列数据结构存储的定时任务执行检测机制,提高执行性能。 + 4. 文档更新: [定时器-gtimer](../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +11. `/os/grpool` + 1. 回调方法定义增加 `context.Context` 参数。 + 2. `goroutine` 池任务添加方法增加 `context.Context` 参数。 + 3. 文档更新: [协程管理-grpool](../docs/组件列表/系统相关/协程管理-grpool.md) +12. `/os/gsession` + 1. `gsession.Storage` 接口增加 `ctx` 上下文参数输输入,用于承接上下文信息、实现完整的链路跟踪。并未保证严谨性增加 `error` 返回参数: [Session](../docs/WEB服务开发/Session/Session.md) + 2. 参数获取返回统一使用 `*gvar.Var` 泛型对象。 +13. `/os/gview` + 1. 模板解析方法统一增加 `context.Context` 参数。 + 2. 增加 `plus/minus/times/divide` 四则运算内置模板方法。 + 3. 文档更新: [模板引擎](../docs/核心组件/模板引擎/模板引擎.md) +14. `/os/gstructs` + 1. 将框架 `internal` 中的 `structs` 包开放,命名为 `gstructs`,用于 `struct` 反射操作的高级使用包: [对象信息-gstructs](../docs/组件列表/系统相关/对象信息-gstructs.md) + +### 4、错误处理 + +1. `/errors/gerror` + + +1. 增加 `Message` 方法,用于获取指定错误码的错误信息。 +2. 增加 `CodeMessage` 方法,用于获取指定错误的错误码信息。 +3. 增加 `NewOption` 方法,用于自定义配置的错误对象创建,献给框架高级玩家。 +4. 增加 `HasStack` 方法,用于判断给定的error接口对象是否实现(包含)了堆栈信息。 +5. 错误码从整型改为接口对象,以实现可定制性并提高可扩展性,详情参考 `gcode` 组件介绍: [错误处理-错误码特性](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) +6. 提高易用性,改进 `NewCode/NewCodeSkip/WrapCode/WrapCodeSkip` 方法,调增 `text` 输入参数为非必须,默认使用对应错误码的 `Message` 信息。 +2. `/errors/gcode` +1. 增加 `gcode` 错误码组件,提供可定制型和扩展性极强的错误码管理,结合 `gerror` 组件实现强大的错误处理: [错误处理-错误码使用](../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) + +### 5、其他组件 + +1. `/container/garray` +1. 各数组类型统一增加 `At` 方法,用于直接获取返回索引位置的数据。 +2. 文档更新: [数组类型-方法介绍](../docs/组件列表/数据结构/数组类型-garray/数组类型-方法介绍.md) +2. `/debug/gdebug` +1. 增加 `TestDataContent` 方法,用于直接获取测试包下 `testdata` 目录下指定路径文件内容。 +2. 文档更新: [调试功能-gdebug](../docs/组件列表/功能调试/调试功能-gdebug.md) +3. `/encoding/gjson` +1. 废弃大部分的 `Get*` 方法,统一使用 `Get` 方法获取指定 `pattern` 的内容,并统一返回 `*gvar.Var` 泛型对象,开发者根据业务场景自行通过对应方法便捷转换为特定类型变量。 +2. 增加若干 `Must*` 方法。 +3. 使用文档全面更新 +4. `/frame/g` +1. 增加 `ModelRaw` 方法,用于便捷创建基于原生 `SQL` 的数据库 `Model` 对象。 +2. 为通过 `/frame/g` 模块创建的 `ORM` 对象增加 `logger` 配置,通过自动读取配置文件,自动初始化: [ORM使用配置](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +3. 为通过 `/frame/g` 模块创建的 `Server` 对象增加 `logger` 配置,通过自动读取配置文件,自动初始化: [服务配置](../docs/WEB服务开发/服务配置/服务配置.md) +5. `/frame/gmvc` +1. 标记废除 `gmvc` 耦合模块,未来不再进一步支持。 +6. `/util/gutil` +1. 改进实现 `Dump` 方法,不再使用 `json` 包实现类型打印,而是自实现了对任意类型的打印特性,并且支持打印详细的数据类型: [工具方法-gutil](../docs/组件列表/实用工具/工具方法-gutil.md) +2. 增加 `SliceToMapWithColumnAsKey` 方法,用以将 `Slice` 按照一定规则转换为 `Map`。 +7. `/utils/gvalid` +1. 增加 `bail` 校验规则,以及 `Bail` 链式操作方法,用以在数据校验不通过时直接退出校验,不再执行后续校验规则。 +2. 增加 `datetime` 校验规则,用以校验常用日期时间类型,其中日期之间支持的连接符号只支持 `-`,格式如: `2006-01-02 12:00:00`。 +3. 去掉包校验方法,统一使用链式操作实现数据校验。 +4. 所以校验方法增加 `context.Context` 参数。 +5. 全新、超完善的数据校验组件使用文档: [数据校验](../docs/核心组件/数据校验/数据校验.md) + +其他大量的改进细节,这里不再赘述,感兴趣的小伙伴可参阅官网 [goframe.org](https://goframe.org) + +## 三、CLI工具链 + +1. 采用全新 `gcmd` 命令行对象封装重构实现。 +2. 改进 `init` 命令,支持 `SingleRepo/MonoRepo` 两种仓库初始化。并且项目初始化不再依赖远端仓库。 +3. 改进 `gen dao` 命令,采用全新的 `V2` 工程化设计,自动生成 `entity/dao/do` 代码文件。 +4. 去掉 `update` 命令,工具的更新统一走 [https://github.com/gogf/gf/tree/master/cmd/gf](https://github.com/gogf/gf/tree/master/cmd/gf) +5. 去掉 `get` 命令。 +6. 全新文档: [开发工具](../docs/开发工具/开发工具.md) \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.1 2022-06-22.md b/versioned_docs/version-2.8.x/release/v2.1 2022-06-22.md new file mode 100644 index 00000000000..36f54696465 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.1 2022-06-22.md @@ -0,0 +1,152 @@ +--- +slug: '/release/v2.1.0' +title: 'v2.1 2022-06-22' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,版本更新,特性改进,Bug修复,数据库组件,服务注册,负载均衡,网络组件,系统组件,编解码支持] +description: '本次GoFrame v2.1版本发布新增了多个业务实用特性,包括数据库组件和网络组件的改进,支持更灵活的SQL条件组合和自定义钩子事件处理。服务注册、负载均衡和系统组件功能得到增强。同时优化内存使用和改进服务发现逻辑。' +--- + +大家好,本次发布的 `v2.1` 版本包含一些与业务实践相关的功能特性、改进以及Bug Fix,建议大家升级。 + +视频介绍: [2022-06-22 GoFrame v2.1功能特性&使用答疑](../community/社区交流/技术分享交流/5-2022-06-22%20GoFrame%20v2.1功能特性&使用答疑.md) + +## 新特性 + +1. 开发工具新增 `gen service` 命令,支持自动化地根据 `logic` 层级代码,生成 `service` 接口代码、实现注入: [模块规范-gen service](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +2.  数据库组件特性: +1. 新增 `WhereBuilder` 特性,用于更加灵活的 `SQL` 条件语句组合: [ORM查询-Where条件](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +2. 新增 `Hook` 特性,用于自定义钩子事件处理: [ORM链式操作-Hook特性](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Hook特性.md) +3. 框架新增 `DeepCopy` 特性,用于类型的深度拷贝: +1. 新增 `gutil.Copy` 方法,用于深度拷贝指定内容。 +2. 泛型类型新增 `Copy` 方法,用于深度拷贝自身内容。 +3. 框架部分数据类型已支持深度拷贝特性,例如: `gvar, garray, gmap` 等基础容器类型。 + +## 主要改进 + +### 社区组件 + +**ORM驱动实现** + +1. 新增 `drivers/clickhouse`,用于对接 `clickhouse` 到 `goframe ORM` 组件。 +2. 完善 `clickhouse/mssql/pgsql/sqlite/oracle` 组件单元测试。 +3. 将 `mysql` 驱动从主库迁移到社区模块,便于将 `mysql` 从主库解耦。因此从后续版本开始,开发者需要手动引入驱动依赖: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +**注册发现实现** + +1. 新增 `polaris` 北极星服务注册接口实现: [https://github.com/gogf/gf/tree/master/contrib/registry/polaris](https://github.com/gogf/gf/tree/master/contrib/registry/polaris) +2. 改进 `etcd` 服务注册发现接口实现组件: [https://github.com/gogf/gf/tree/master/contrib/registry/etcd](https://github.com/gogf/gf/tree/master/contrib/registry/etcd) + +### 注册发现 + +1. 将 `Service` 实现对象改进为接口定义,并提供默认的 `Service` 实现,提高扩展性和易用性。 +2. 改进 `HTTP/GRPC Client&Server` 对接实现。 + +### 负载均衡 + +1. 改进 `Node` 接口定义,新增 `Nodes` 接口定义。 +2. 修复 `HTTP Client` 下的服务发现负载均衡问题。 + +### 网络组件 + +1. `gclient` +1. 改进服务发现实现逻辑。 +2. 修复客户端关闭错误,引起的连接池无法复用问题。 +2. `ghttp` +1. 改进 `Request.GetUrl` 方法对 `URL Schema` 获取细节。 +2. 参数接收支持 `UploadFile` 属性自动接收。 +3. 新增接口文档自定义UI指导文档: [接口文档-自定义UI](../docs/WEB服务开发/接口文档/接口文档-自定义UI.md) +4. 接口文档默认依赖的外部 `JS CDN` 改为 `unpkg.com`。 +5. 改进服务注册实现逻辑。 +6. 改进内部细节实现逻辑。 +7. 修复参数为空判断问题。 +3. `goai` +1. 改进更规范化符合 `OpenAPIV3` 协议实现。 +2. 支持所有 `x-` 开头的自定义标签,自动添加 `OpenAPIV3` 的结果中。 +3. 组件从 `protocol` 分类迁移到了 `net` 分类下, `import` 路径发生改变。 + +### 系统组件 + +1. `gcfg` +1. 默认的文件系统接口实现新增对 `property` 文件格式的支持。 +2. `gcmd` +1. 参数解析新增 `CaseSensitive` 配置,默认不区分大小写解析,特别针对结构化参数接收影响较大: [命令管理-结构化参数](../docs/核心组件/命令管理/命令管理-结构化参数.md) +2. 新增跨进程的链路跟踪特性: [命令管理-链路跟踪](../docs/核心组件/命令管理/命令管理-链路跟踪.md) +3. `glog` +1. 新增全局的 `Handler` 设置功能,开发者可以全局自定义处理 `glog` 组件的所有日志,例如全局输出 `JSON` 文件格式: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) +2. 新增默认的 `JSON` 格式 `Handler` 供开发者使用: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) +4. `gsession` +1. 解决当访问用户过多造成的内存占用过大的问题。 +5. `gproc` +1. 新增跨进程的链路跟踪特性: [进程管理-链路跟踪](../docs/组件列表/系统相关/进程管理-gproc/进程管理-链路跟踪.md) + +### 容器组件 + +1. `garray` +1. 改进 `Unique` 方法性能,增加 `DeepCopy` 接口实现。 +2. `glist` +1. 增加 `DeepCopy` 接口实现。 +3. `gmap` +1. 增加 `DeepCopy` 接口实现。 +4. `gset` +1. 增加 `DeepCopy` 接口实现。 +5. `gtype` +1. 增加 `DeepCopy` 接口实现。 +6. `gvar` +1. 增加 `Copy` 方法,用于深度拷贝当前泛型对象。 +2. 增加 `DeepCopy` 接口实现。 + +### 数据库组件 + +1. `gdb` +1. 新增 `WhereBuilder` 特性,用于更加灵活的 `SQL` 条件语句组合: [ORM查询-Where条件](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Where条件.md) +2. 新增 `HOOK` 特性,用于自定义钩子事件处理: [ORM链式操作-Hook特性](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-Hook特性.md) +3. 改进数据提交到底层 `driver` 前的数据转换处理逻辑。 +4. 将 `mysql` 驱动从主库迁移到社区模块,便于将 `mysql` 从主库解耦。因此从后续版本开始,开发者需要手动引入驱动依赖: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +### 编解码组件 + +1. `gproperty` +1. 新增 `gproperty` 组件,用于解析 `Java Property` 格式文件。 +2. `gjson` +1. 新增对 `property` 文件格式的编解码、数据操作支持: [通用编解码-gjson](../docs/组件列表/编码解码/通用编解码-gjson/通用编解码-gjson.md) +2. 修复对大整形数据读取的精度丢失问题。 + +### 文本处理 + +1. `gstr` +1. 改进 `WordWrap` 方法,使得对 `Unicode` 特别是中文换行更加友好。 +2. 修复 `RepliceI` 忽略大小写字符串替换在特定场景下的问题。 + +### 错误处理 + +1. `gerror` +1. 新增 `Unwrap` 方法(同 `Next` 方法),用以支持 `Golang` 新版本的 `Unwrap` 错误接口。 +2. 新增 `Equal` 方法,用于判断两个错误是否相等: [错误处理-错误比较](../docs/核心组件/错误处理/错误处理-错误比较.md) +3. 新增 `Is` 方法,用于支持 `Golang` 新版本的 `Is` 错误接口: [错误处理-错误比较](../docs/核心组件/错误处理/错误处理-错误比较.md) + +### 工具方法 + +1. `gconv` +1. 去掉整型转换时对八进制字符串的支持。 +2. 改进内部实现逻辑,提高可读性保障可维护性。 +2. `gutil` +1. 新增 `gutil.Copy` 方法,用于深度拷贝指定内容。 +2. 改进 `gutil.Dump` 方法。 + +## 开发工具 + +相对于主库稳定的代码组件,CLI开发工具在近期的版本发布有一些非兼容更新,各位在升级时注意发布记录,细节请查看源码调整。 + +1. 改进 `build` 命令,支持指定 `pack` 代码文件的生成目录,参数有个别调整。 +2. 改进 `docker` 命令,支持多个 `docker tag` 的重命名及仓库自动推送。 +3. 改进 `gen dao` 命令,支持自定义 `dao/do/entity` 代码生成目录,不再强制生成到 `service/internal` 目录下: [数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +4. 新增 `gen service` 命令,支持自动化地根据 `logic` 层级代码,生成 `service` 接口代码: [模块规范-gen service](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +5. 修复 `run` 命令自定义程序启动参数问题、 `gofmt/goimports` 程序路径带空格问题。 + +## 不兼容事项 + +1. 将 `mysql` 驱动从主库迁移到社区模块,便于将 `mysql` 从主库解耦。因此从后续版本开始,开发者需要手动引入驱动依赖: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) +2. `ghttp.Response.WriteJson/Xml` 等方法不再返回 `error`。根据编译错误调整即可。 +3. `goai` 组件从 `protocol` 分类迁移到了 `net` 分类下, `import` 路径发生改变。根据编译错误调整即可。 +4. 数据库 `ORM` 操作参数中带有 `gtime.Time` 类型,为了解决时间精度丢失的问题,将会自动转换为 `time.Time` 类型再提交给底层的数据库 `driver`。也就是说, `gtime.Time` 类型参数也会受到数据库配置的时区配置参数影响。具体请参考: [ORM时区处理](../docs/核心组件/数据库ORM/ORM时区处理.md) \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.2 2022-10-11.md b/versioned_docs/version-2.8.x/release/v2.2 2022-10-11.md new file mode 100644 index 00000000000..ebb703300b8 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.2 2022-10-11.md @@ -0,0 +1,145 @@ +--- +slug: '/release/v2.2.0' +title: 'v2.2 2022-10-11' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,v2.2.0,校验组件,数据库组件,达梦数据库,Apollo,Polaris,Kubernetes ConfigMap,open source] +description: 'GoFrame框架v2.2.0版本发布,重构校验组件和改进数据库组件接口设计,支持达梦数据库,并新增多种配置中心实现。感谢超过44位代码贡献者的支持,使得GoFrame框架更加强大与灵活。' +--- + +👋 Hi,朋友们! `GoFrame` 框架 `v2.2.0` 版本在今天正式发布啦! + +本次版本的最大的看点在于: + +- 重构并改进了开发者最频繁使用的校验组件,使得社区的开发者可以很方便地开发和维护内置校验规则。并且扩增了一些常用校验规则,使得组件内置规则更加丰富强大。 +- 改建了数据库组件接口设计,使得社区开发者可以更简单地新增一个数据库类型的 `driver`,目前框架通过社区组件的方式提供了 `9` 种数据库类型的 `driver` 实现,满足绝大部分业务项目的需求。尤其是本次版本新增了对国产达梦数据库的支持,未来我们期望社区的开发者能提供更多国产数据库类型的 `driver` 实现,贡献给开源社区。 +- 社区组件的丰富,本次主要新增了 `3` 种类型的配置中心接口实现,支持 `Apollo/Polaris/Kubernetes ConfigMap`。 `GoFrame` 框架采用了模块化低耦设计,组件分为框架主库与社区组件。框架主库提供核心通用轻量的基础组件,而社区组件是与框架主库解耦的单独组件包,保证主库通用轻量的同时为框架扩充了更多的能力。 +- 本次版本的代码贡献者超过 `44` 位,框架的贡献者达到了 `107` 位。感谢大家为社区付出的努力和贡献!💖 + +Github ChangeLog: [https://github.com/gogf/gf/releases/tag/v2.2.0](https://github.com/gogf/gf/releases/tag/v2.2.0) + +## 新特性 + +1. 重构校验组件的内置校验规则管理器,增加并支持到了 `59` 种常用内置校验规则: [数据校验-校验规则](../docs/核心组件/数据校验/数据校验-校验规则.md) +2. 新增社区组件 `contrib/config/kubecm`,实现基于 `kubernetes configmap` 的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) +3. 新增社区组件 `contrib/config/apollo`,实现基于 `apollo` 配置中心的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) +4. 新增社区组件 `contrib/config/polaris`,实现基于 `polaris` 配置中心的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) +5. 新增 `contrib/drivers/dm` **国产达梦数据库** 支持: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +## 主要改进 + +### 社区组件 + +#### ORM驱动实现 + +1. 改进 `contrib/drivers/pgsql` 在ORM组件层面支持更多 `pgsql` 内置的数据类型。 +2. 改进 `contrib/drivers/pgsql` 支持写入操作下的 `LastInsertId` 特性。 +3. 改进 `contrib/drivers/clickhouse` 支持 `decimal.Decimal` 数据类型。 +4. 新增 `contrib/drivers/dm` **国产达梦数据库** 支持: [https://github.com/gogf/gf/tree/master/contrib/drivers](https://github.com/gogf/gf/tree/master/contrib/drivers) + +#### 注册发现组件 + +1. 改进 `contrib/registry/etcd` 实现,将日志对象改为接口属性,允许外部注册自定义的日志对象。 + +#### 配置组件 + +1. 新增社区组件 `contrib/config/kubecm`,实现基于 `kubernetes configmap` 的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/kubecm](https://github.com/gogf/gf/tree/master/contrib/config/kubecm) +2. 新增社区组件 `contrib/config/apollo`,实现基于 `apollo` 配置中心的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/apollo](https://github.com/gogf/gf/tree/master/contrib/config/apollo) +3. 新增社区组件 `contrib/config/polaris`,实现基于 `polaris` 配置中心的配置组件 `Adapter` 实现: [https://github.com/gogf/gf/tree/master/contrib/config/polaris](https://github.com/gogf/gf/tree/master/contrib/config/polaris) + +### 数据库ORM + +1. 统一不同数据库类型下,单行字符串配置管理格式,并兼容支持已有不同数据库类型特定的配置格式: [ORM使用配置](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +2. 改进接口设计,简化 `driver` 实现逻辑,后续新增更多的数据库 `driver` 支持更加简便。 +3. 新增 `ToSQL` 方法,用于将ORM操作生成可供调试的 `SQL` 语句,并不真正执行 `SQL`。 +4. 新增 `CatchSQL` 方法,用于通过闭包方法获取内部执行的 `SQL` 语句列表。 +5. 废弃 `Core` 对象中的 `GetStruct/GetStructs` 方法,统一使用 `Scan` 方法操作,以提高易用性。 +6. 数据库对象使用日志对象改为接口属性,允许外部注册自定义的日志对象。 +7. 新增 `Extra` 及 `Protocol` 配置,用于设置额外的配置参数以及链接协议,默认通过 `Link` 配置自动解析。 +8. 去掉 `Filtered` 接口,采用默认实现,简化复杂度、提高易用性。 +9. 新增 `ConvertValueForLocal` 及 `CheckLocalTypeForField` 接口,用于自定义的数据类型转换及数据类型获取,并提供默认实现。 +10. 新增 `ClearTableFields` 方法,用于清理特定数据表的数据结构缓存: [ORM高级特性-字段映射](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-字段映射.md) +11. 新增 `ClearTableFieldsAll` 方法,用于清理当前数据库对象所有的数据表数据结构缓存: [ORM高级特性-字段映射](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-字段映射.md) +12. 新增 `ClearCache` 方法,用于清理特定数据表的所有查询缓存: [ORM链式操作-查询缓存](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) +13. 新增 `ClearCacheAll` 方法,用于清理当前数据库对象所有的查询缓存: [ORM链式操作-查询缓存](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-查询缓存.md) +14. 其他一些改进。 + +### 编解码组件 + +1. `gcompress` +1. 新增 `GzipPathWriter` 方法没用与将特定路径下的文件压缩到指定的 `io.Writer` 中。 +2. 新增ZipPathContent方法,用于使用 `zip` 算法打包压缩文件/目录并返回字节内容。 +2. `gjson` +1. 将 `ContentTypeJson` 等字符串参数调整为 `ContentType` 类型 + +### 错误处理 + +1. 增加命令行参数 `--gf.gerror.brief` 及 `GF_GERROR_BRIEF` 环境变量开关,控制是否在错误堆栈打印时过滤框架堆栈: [错误处理-其他特性](../docs/核心组件/错误处理/错误处理-其他特性.md) +2. 其他一些细节改进。 + +### 网络组件 + +1. `ghttp` +1. 新增常用中间件 `MiddlewareJsonBody` 用于校验请求 `Body` 是否 `JSON` 格式。 +2. 新增 `GetListenedAddress` 方法,用于获取 `HTTP Server` 在指定 `:0` 监听端口时系统随机分配的监听地址。 +3. 当服务端执行出错时,修改返回HTTP状态码为 `500`。 +2. `gtcp` +1. 新增 `GetListenedAddress/GetListenedPort` 方法,用于获取 `TCP Server` 在指定 `:0` 监听端口时系统随机分配的监听地址/端口。 +3. `gudp` +1. 新增 `GetListenedAddress/GetListenedPort` 方法,用于获取 `UDP Server` 在指定 `:0` 监听端口时系统随机分配的监听地址/端口。 +4. `goai` +1. 生成接口文档时,支持内嵌结构体定义的属性。 +2. 去掉接口文档中重复的参数说明,特别是在 `URL` 和 `Body` 中存在相同参数时。 +5. `gtrace` +1. 改进 `WithTraceID` 方法的错误提示为更明确的信息。 +2. 新增 `WithUUID` 方法,用于将标准的 `UUID` 转换为 `OpenTelemetry` 的 `TraceID`。 + +### 系统组件 + +1. `gcfg` +1. 调整 `Available` 接口方法定义,将 `resource` 参数改为非必须参数。 +2. `gcron` +1. 针对定时任务延迟的场景,增加时间差异计算逻辑,尽可能减少对定时任务的影响。 +3. `gctx` +1. 新增跨进程的链路跟踪支持。 +2. 新增 `GetInitCtx/SetInitCtx` 方法,用于 `main` 包及包 `init` 包初始化方法执行时的 `context` 获取和设置。 +4. `glog` +1. 新增 `ILogger` 接口定义,用于跨组件使用日志组件时的接口化解耦。 +2. 其他细节改进。 +5. `gres` +1. 资源 `File` 对象新增 `Export` 方法,用于将该对象关联的资源导出到指定的磁盘路径。 +6. `gstructs` +1. 将 `RecursiveOption` 从 `int` 类型改进为自定义类型,并调整对应的方法参数定义。 + +### 文本处理 + +1. 新增 `gstr.IsGNUVersion` 方法,用于判断给定的字符串是否满足 `GNU` 版本规则。 + +### 工具方法 + +1. `gconv` +1. 改进对 `NaN` 特殊字符串的 `int64/uint64` 转换支持。 +2. `gutil` +1. 新增 `GetOrDefaultStr/GetOrDefaultAny` 方法,用于默认值和非必须参数的便捷处理。 +3. `gvalid` +1. 重构内置校验规则管理器,使得新增一个内置校验非常简便。 +2. 增加并支持到了 `59` 种常用内置校验规则: [数据校验-校验规则](../docs/核心组件/数据校验/数据校验-校验规则.md) + +## 功能修复 + +1. 修复 `garray/gmap/gset/glist/gtype/gvar` 在容器对象为 `nil` 场景下 `DeepCopy` 的 `panic` 问题。 +2. 修复 `gtime` 在对象为 `nil` 场景下 `DeepCopy` 的 `panic` 问题。 +3. 修复数据库ORM链式操作 `Group` 方法在给定多个排序条件时的覆盖问题。 +4. 修复 `HTTP Server` 在 `JSON` 格式字符串返回时的内容重复输出问题。 +5. 修复 `gstr.Nl2Br` 方法在部分场景下由于逻辑判断引起的数组访问越界问题。 +6. 修复查询数据表字段信息时表名为空场景的错误打印问题。 +7. 修复 `Req` 对象属性为 `*gjson.Json` 类型的参数接收问题。 + +## 开发工具 + +1. 改进 `gen dao` 命令,增加 `clear` 参数,用于自动清理目标数据库中不存在的本地数据模型Go文件: [数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +2. 改进 `gen service` 命令: [模块规范-gen service](../docs/开发工具/代码生成-gen/模块规范-gen%20service.md) +1. 按照业务模块对结构体对象生成的接口进行整合。 +2. 增加 `clear` 参数,用于自动清理在 `logic` 没有对应的接口代码及文件。 +3. 其他一些细节改进。 +3. 改进 `run` 命令,增加自定义的程序运行参数。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.3 2023-01-18.md b/versioned_docs/version-2.8.x/release/v2.3 2023-01-18.md new file mode 100644 index 00000000000..51c9d562539 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.3 2023-01-18.md @@ -0,0 +1,175 @@ +--- +slug: '/release/v2.3.0' +title: 'v2.3 2023-01-18' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame框架,接口化设计,微服务开发,redis组件,数据库ORM,配置管理,注册发现,nacos,zookeeper,ghttp] +description: '此次的GoFrame框架更新包括接口化设计以减少对第三方组件耦合,提升系统灵活性。同时引入如redis的社区组件,增强易用性和性能。新增nacos和zookeeper组件接口实现以支持常用服务,升级工具提升框架使用体验。这些更新为未来微服务组件的拓展铺平了道路。' +--- + +大家好啊!本次版本是 `2022` 年最后一个版本,也是 `2023` 年的第一个版本。该版本主要的目标: + +- 采用接口化设计解决了主框架对第三方开源组件 `go-redis` 耦合的问题,使得主框架更加轻量,对工具化使用框架的场景更加友好。 +- 通过开发工具提供升级 主框架、社区组件、开发工具 的命令,提高框架整体易用性,并解决常见的社区组件与主框架版本不一致问题。 +- 进一步按照接口与实现分离设计,通过社区组件方式,完善注册发现、配置管理对常用服务的对接,为下一步发布微服务组件做准备。 + +完整变更列表: [https://github.com/gogf/gf/compare/v2.2.0...v2.3.1](https://github.com/gogf/gf/compare/v2.2.0...v2.3.1) + +## 新特性 + +1. 将耦合较重的 `redis` 组件从主框架中解耦,作为社区组件提供。原有主框架增加 `redis` 接口定义,社区组件 `redis` 提供具体的接口实现。因此,请注意,主框架 `gredis` 组件的使用方式发生了一些变更,在依赖 `redis` 的项目中需要引入社区组件 `redis` 实现,否则方法执行将会返回错误。保留兼容原有基础的 `Do/DoVar` 方法,并增加了 `100+` 项常用 `redis` 操作方法: [NoSQL Redis](../docs/组件列表/NoSQL%20Redis/NoSQL%20Redis.md) +2. 配置管理及注册发现组件新增常用服务接口实现: + - **配置管理**( `nacos`): [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) + - **注册发现**( `zookeeper`): [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) +3. 新增工具命令 `gf up`,用于便捷的框架升级操作,具体介绍请查看: [框架升级-up](../docs/开发工具/框架升级-up.md) + +## 功能改进 + +### 社区组件 + +#### 配置管理 + +1. 新增 `nacos` 接口实现: [https://github.com/gogf/gf/tree/master/contrib/config/nacos](https://github.com/gogf/gf/tree/master/contrib/config/nacos) + +#### 注册发现 + +1. 新增 `zookeeper` 接口实现: [https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper](https://github.com/gogf/gf/tree/master/contrib/registry/zookeeper) + +#### 数据库驱动 + +1. 修复 `clickhouse` 在配置 `Charset` 参数时的报错问题。 +2. 改进 `clickhouse` 获取数据表结构结果,返回的 `Index` 顺序统一从 `0` 开始。 +3. 改进 `oracle` 表结构获取SQL,以支持 `float64` 数据类型。 +4. 修复 `pgsql` 对接口 `CheckLocalTypeForField` 的实现,名称被错误定义为了 `CheckLocalTypeForValue`,造成生成 `dao` 代码文件时的属性字段类型错误。 +5. 改进 `pgsql` 增加对 `schema` 的支持,由于 `shcema` 在大部分数据库服务中代表"数据库名称",并且也为了兼容旧版本,因此增加 `Namespace` 配置参数用以表示 `pgsql` 的 `Schema`,而原有的 `Schema` 对象代表 `pgsql` 的 `catalog`。 + +#### NoSQL组件 + +1. 新增 `redis` 社区组件,实现 `gredis` 相关接口: [https://github.com/gogf/gf/tree/master/contrib/nosql/redis](https://github.com/gogf/gf/tree/master/contrib/nosql/redis) + +### 数据库组件 + +1. `gdb` +1. 为提高扩展性, `TX` 事务对象改为了接口定义,原有的 `TX` 对象改为了 `TXCore` 方便自定义接口实现对象的嵌套: [ORM事务处理](../docs/核心组件/数据库ORM/ORM事务处理/ORM事务处理.md) +2. 增加 `Namespce` 配置项,用以支持个别数据库服务 `Catalog&Schema` 区分的问题,原有的 `Schema` 继续代表数据库名称,而新增的 `NameSpace` 代表个别数据库服务的 `Schema` 配置: [ORM使用配置](../docs/核心组件/数据库ORM/ORM使用配置/ORM使用配置.md) +3. 改进数据库名称配置,增加对中文数据库名称的支持。 +4. 执行 `SQL` 日志中增加当前执行的数据库名称打印。 +5. 修复 `Count` 方法的缓存问题。 +2. `gredis` +1. 新增 `redis` 社区组件,将耦合较重的 `redis` 组件从主框架中解耦,作为社区组件提供。原有主框架增加 `redis` 接口定义,而社区组件 `redis` 提供具体的接口实现。因此,请注意,主框架 `gredis` 组件的使用方式发生了一些变更,在依赖 `redis` 的项目中需要引入社区组件 `redis` 实现,否则方法执行将会返回错误。保留兼容原有的 `Do/DoVar` 方法,并增加了 `100+` 项常用 `redis` 操作方法: [NoSQL Redis](../docs/组件列表/NoSQL%20Redis/NoSQL%20Redis.md) + +### 容器组件 + +1. `gpool` +1. 增加 `MustPut` 方法,在 `Put` 执行出错时直接 `panic` 而不是返回错误对象 +2. `gqueue` +1. 改进 `Len/Size` 方法,解决可能存在的队列计数不准确问题。 +2. 改进 `Len/Size` 方法,返回参数类型由 `int` 改为 `int64`。 + +### 错误处理 + +1. `gcode` +1. 增加 `CodeNecessaryPackageNotImport` 错误码。 +2. `gerror` +1. 改进堆栈打印,统一使用空格替代 `\t`,以保证打印格式兼容不同的展示终端。 + +### 对象管理 + +1. `gins` +1. 单例对象增加分组锁机制,提升在高并发下的锁机制性能。 + +### 网络组件 + +1. `ghttp` +1. 支持在中间件中增加对当前执行路由方法的获取。 +2. 当路由方法执行的结果不为 `200` 时,支持在中间件中通过 `Request.GetError` 方法获取内部错误。 +3. 新增 `Response.ServeContent` 方法,用于自定义的内容输出接口实现。 +4. 改进反向代理支持,并增加反向代理示例: [https://github.com/gogf/gf/blob/master/example/httpserver/proxy/main.go](https://github.com/gogf/gf/blob/master/example/httpserver/proxy/main.go) +5. 改进错误日志输出,使用 `error` 错误级别,便于开发者在自定义日志 `Handler` 中识别日志类型。 +2. `goai` +1. 新增对 `security` 标签的支持,用以配置 `OpenAPIv3` 安全密钥。 +2. 改进对带有 `json` 标签带有 `,` 符号时结构体属性的名称获取。 +3. `gtcp` +1. `SetSendDeadline` 方法名称修改为 `SetDeadlineSend`。 +2. `SetReceiveDeadline` 方法名称修改为 `SetDeadlineRecv`。 +3. `SetReceiveBufferWait` 方法名称修改为 `SetBufferWaitRecv`。 +4. `gudp` +1. `SetSendDeadline` 方法名称修改为 `SetDeadlineSend`。 +2. `SetReceiveDeadline` 方法名称修改为 `SetDeadlineRecv`。 +3. `SetReceiveBufferWait` 方法名称修改为 `SetBufferWaitRecv`。 + +### 系统组件 + +1. `gcache` +1. 修复 `MustGetOrSetFunc` 方法逻辑问题。 +2. 改进 `LRU` 缓存过期回收机制实现。 +2. `gcmd` +1. 改进结构化命令行对象生成,当 `brief` 标签为空时,自动读取 `dc` 标签内容作为 `brief`,以保证命令行与接口定义标签习惯相同。 +3. `gcron` +1. 改进日志处理,当给定的定时任务方法执行 `panic` 并且开发者没有设置 `Logger` 接口时,将会使用 `glog.DefaultLogger` 输出错误日志。 +2. 改进定时触发判断逻辑,解决在底层定时器执行间隔不准确时引起的定时任务触发问题。 +4. `glog` +1. 改进在滚动切分特性开启时的初始化逻辑,解决在个别场景下的初始化失败导致滚动切分执行失败问题。 +2. 改进 `Clone` 方法,进一步浅拷贝提高链式操作性能。 +3. 新增 `LevelPrint` 配置,用以控制默认日志 `Handler` 是否打印日志级别字符串。 +5. `gres` +1. 新增 `Pack*WithOption` 方法,用以提供更细致的资源打包选项控制。 +2. 标记方法废弃: `Pack/PackToFile/PackToGoFile`。 +3. 新增 `KeepPath` 打包选项,用以控制是否在打包文件中保留给定的相对路径,而不是使用带有本地打包目录前缀的路径(相当于去掉了目录前缀)。 +6. `grpool` +1. 新增 `supervisor` 特性,解决在 `worker` 数量较少的场景下,低概率下同时退出的问题。 +7. `gstructs` +1. 新增 `Tag*` 方法,用以获取常见的标签值。 +8. `gtime` +1. 改进 `Equal/After/Sub` 方法,解决在个别场景下的细节问题。 +2. 改进 `EndOf*` 方法,返回的时间对象由开发者控制 `EndOf` 计算的粒度。调整默认粒度,由纳秒改为秒粒度进行计算。 +3. 改进 `SetTimeZone` 时区设置方法,以实现不同系统下的兼容性,该方法只允许全局设置一次时区,多次调用设置不同时区将会返回错误: [时间管理-时区设置](../docs/组件列表/系统相关/时间管理-gtime/时间管理-时区设置.md) + +### 文本处理 + +1. `gstr` +1. 修复 `IsSubDomain` 判断主域名为子域名的子域名问题。 +2. 改进 `SubStr/SubStrRune` 方法,支持以负数的 `start` 参数指定从右截取字符串。 + +### 工具组件 + +1. `gconv` +1. 新增 `Ptr*` 方法,用以任意类型到指定类型指针变量的转换。 +2. 改进 `Map*` 转换方法对递归转换的处理,默认只会递归转换嵌套的结构体属性。 +3. 改进 `Scan` 方法,解决属性同类型以及同类型指针到目标对象/指针的转换问题。 +2. `gtag` +1. 将框架中所有的标签名称统一到该模块下通过常量维护。 +2. 增加 `SetOver/SetsOver` 方法,用于覆盖设置自定义的标签键值对。 +3. `gutil` +1. 改进 `Dump*` 方法,支持循环嵌套的对象指针打印。 +2. 修复 `Dump*` 方法在部分场景下的反射报错问题。 +3. 新增 `OriginValueAndKind/OriginTypeAndKind` 方法,用于获取给定变量的反射值/类型对象,以及在指针变量下对应的原始反射值/类型对象。 + +## 功能修复 + +1. 修复工具在部分环境下安装失败问题。 +2. 修复 `New*ArrayRange` 方法创建数组对象时,在部分场景下数组越界问题。 +3. 修复 `contrib/drivers/clickhouse` 在配置 `Charset` 参数时的报错问题。 +4. 修复 `pgsql` 数据库生成 `dao` 代码文件时的属性字段类型错误问题。 +5. 修复数据库ORM组件中 `Count` 方法的缓存问题。 +6. 修复 `gstr.IsSubDomain` 判断主域名为子域名的子域名问题。 +7. 修复 `gutil.Dump*` 方法在部分场景下的反射报错问题。 + +## 开发工具 + +1. 增加 `gf fix` 命令,用于低版本升级高版本自动更新本地代码不兼容变更: [兼容修复-fix](../docs/开发工具/兼容修复-fix.md) +2. 增加 `gf up` 命令,用以将本地的框架低版本升级到最新的框架版本: [框架升级-up](../docs/开发工具/框架升级-up.md) +3. 改进 `gf build` 命令,在构建之前增加环境变量信息打印。 +4. 改进 `gf pack` 命令,增加 `KeepPath` 参数,用以控制资源打包后是否保留相对路径: [资源打包-pack](../docs/开发工具/资源打包-pack.md) +5. 改进 `gf gen dao` 命令,生成的 `Transaction` 方法中 `tx` 参数从对象指针改为了接口。 + +## 兼容提示 + +1. `Redis` 的使用方式发生变更,旧版方法保持兼容,但需要额外添加社区组件的引入(接口与实现分离),具体请查看文档。 +2. 数据库 `ORM` 的 `TX` 对象从具体实现改为了接口,这块通过开发工具新增的 `up` 或者 `fix` 命令即可自动升级修复。 + +## 下一版本目标 + +- 完善并发布 `grpcx` 社区组件,实现对 `grpc` 接口协议的扩展支持,并提高微服务开发易用性。 +- 官网新增【微服务开发】系列章节,主要以 `grpc` 开发为主介绍使用 `goframe` 进行微服务开发。 +- 主框架去掉对第三方开源组件 `gorilla/websocket` 的耦合,将 `WebSocket` 的支持接口化,按照框架的通用解耦设计,通过社区组件提供具体实现,提供扩展性和灵活性。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.4 2023-04-24.md b/versioned_docs/version-2.8.x/release/v2.4 2023-04-24.md new file mode 100644 index 00000000000..6722e2cb21a --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.4 2023-04-24.md @@ -0,0 +1,123 @@ +--- +slug: '/release/v2.4.0' +title: 'v2.4 2023-04-24' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,微服务,开发工具,工程脚手架,数据库组件,网络组件,系统组件,社区组件,服务注册发现,框架更新] +description: 'GoFrame框架发布v2.4.0版本,重点推出微服务开发功能特性及工具,丰富的开发文档强调框架的灵活性与扩展性。该版本修复多项功能改进,对数据库、网络和系统组件进行优化。新增命令帮助开发者更高效地构建微服务应用。' +--- + +大家好啊, `GoFrame` 框架今天发布了 `v2.4.0` 正式版本啦!👏👏👏👏 + +该版本最大的亮点在于提供了 **微服务开发的功能特性、开发工具以及工程脚手架**,并且提供了完善的 **微服务开发文档**!!微服务的组件设计仍然以接口设计为主,以保证良好的灵活性和扩展性,接口维护在框架主库中;具体的接口实现仍然以务实为主,并在社区组件中提供了常用的接口实现。 + +其实该版本特性功能早已 `Ready`,但由于文档工作量较大,陆续花了 `1` 个月左右才相对完善,因此版本的发布时间也相应做了调整。我们认为文档和代码同等重要,是发布里程碑不可分割的部分。我们知道怎么用好,也希望能告诉大家怎么用好,才能更务实地帮助到更多的开发者。这也是 `GoFrame` 框架的文档能够逐步沉淀丰富的原因之一。此外,详尽的源码注释依旧英文为主,以帮助到海外使用者。官网文档仍然以中文为主,帮助到主要的大中国区开发团队。 + +赶快来看看我们都更新了什么东西吧! `Enjoy!` 🍺🍺🍺🍺🍺🍺🍺🍺 + +完整代码变更:[v2.3.0...v2.4.0](https://github.com/gogf/gf/compare/v2.3.0...v2.4.0) ,感谢本次所有的贡献开发者: + +[![](/markdown/9c63586b568a8e84872c67b58aa9e559.png)](https://github.com/gogf/gf/releases/tag/v2.4.0) + +## 新特性 + +正式发布微服务开发特性,并新增完整的微服务开发官网章节: [微服务开发](../docs/微服务开发/微服务开发.md) + +## 功能改进 + +### 数据库组件 + +1. `gdb` +1. 修复跨库操作时自动查询表结构失败的问题: [https://github.com/gogf/gf/issues/2338](https://github.com/gogf/gf/issues/2338) +2. 修复 `Namespace` 配置在 `pgsql` 下未生效的问题。 +3. 修复使用新版统一配置,无法打开 `sqlite` 数据库文件的问题: [https://github.com/gogf/gf/issues/2435](https://github.com/gogf/gf/issues/2435) +4. 改进底层数据库操作返回逻辑,将底层错误通过 `gerror.Wrap` 后返回,以保证上层能获取底层自定义错误对象。 +5. 修复查询数据表时,底层 `unsigned` 整形字段转换为了 `signed` 整形类型的问题: [https://github.com/gogf/gf/issues/2356](https://github.com/gogf/gf/issues/2356) +6. 修复子查询时,多层 `Model` 作为子查询参数的解析问题: [https://github.com/gogf/gf/issues/2339](https://github.com/gogf/gf/issues/2339) +7. 改进时间维护功能,写入/更新/删除时间支持完整的时间(粒度到纳秒)写入。 +8. 修复在软删除场景下,给定空 `Where` 条件的未限制执行问题: [https://github.com/gogf/gf/issues/2427](https://github.com/gogf/gf/issues/2427) +2. `gredis` +1. 修复对象创建时的配置处理及对象初始化问题。 + +### 容器组件 + +1. `garray` +1. 增加 `Filter` 方法,用于自定义遍历并过滤数组元素项。 +2. 增加 `RemoveValues` 方法,支持按照参数值批量删除元素项。 +3. 改进 `InsertBefore` 方法,支持批量的参数插入能力。 +2. `gmap` +1. 增加 `IsSubOf` 方法,用于判断当前 `map` 是否是指定 `map` 的子集。 +3. `gqueue` +1. 修复 `Len/Size` 长度计算问题: [https://github.com/gogf/gf/issues/2509](https://github.com/gogf/gf/issues/2509) +2. 修复 `Close` 方法的并发安全问题: [https://github.com/gogf/gf/issues/2015](https://github.com/gogf/gf/issues/2015) + +### 网络组件 + +1. `gclient` +1. 增加 `SetDiscovery` 及 `SetBuilder` 方法,允许调用者自定义客户端的服务发现及负载均衡接口实现。 +2. `ghttp` +1. 改进支持参数接收时从 `Header/Cookie` 中读取指定参数,支持规范路由中的 `in` 标签定义读取 `Header/Cookie`( `in:header/cookie`)。 +2. 改进 `ResponseWriter` 实现 `http.Flusher` 接口,简化使用者的 `Stream` 输出开发逻辑: [数据返回-Stream返回](../docs/WEB服务开发/数据返回/数据返回-Stream返回.md) +3. 改进链路跟踪实现逻辑,避免内部读取提交内容发生错误时被忽略的问题。 +4. 改进参数读取逻辑,避免 `r.GetRequestMap()` 返回内容包含 `form-data` 表单 `body` 信息的问题: [https://github.com/gogf/gf/issues/2261](https://github.com/gogf/gf/issues/2261) +5. 改进内部上下文接收逻辑: + 1. 原有逻辑:去掉忽略底层 `Request` 的 `ctx` 并新建支持链路跟踪的 `ctx` + 2. 最新逻辑:继承底层 `Request` 的 `ctx` 对象,并扩展该 `ctx` 支持链路跟踪特性 +6. 改进优雅关闭进程逻辑,允许自定义优雅关闭的超时时间。 +7. 改进配置功能,允许开发者配置自定义的服务注册接口实现对象。 + +### 系统组件 + +1. `gcmd` +1. 改进 `AddObject` 方法,允许直接将指定的 `*Command` 或者规范编写的 `Object` 对象添加为子级命令。 +2. `gctx` +1. 修复 `GetInitCtx` 方法缺失 `TraceID` 的问题: [上下文-gctx](../docs/组件列表/系统相关/上下文-gctx.md) +3. `gfile` +1. 改进 `Temp` 方法,保持基础逻辑实现与标准库 `os.TempDir` 一致,避免单机多人协作时的临时目录冲突问题。 +4. `gtimer` +1. 创建定时器新增 `Quick` 选项,用以实现是否在添加定时器时同时执行指定的回调方法。 + +### 工具组件 + +1. `gconv` +1. 修复 `Scan` 方法转换在部分场景下,如果属性为整型/浮点型数组时的转换失败问题: [https://github.com/gogf/gf/issues/2391](https://github.com/gogf/gf/issues/2391) +2. 修复 `Interfaces` 方法在属性为0的场景下,转换结构体直接返回 `[]` 的问题: [https://github.com/gogf/gf/issues/2395](https://github.com/gogf/gf/issues/2395) +3. 修复当 `json tag` 为 `,omitempty` 不带变量名称的结构体转换问题。 +4. 修复当转换目标类型为自定义基本类型的指针,而转换失败的问题。 +5. 修复 `gvar.Var` 类型转换为常见第三方包 `decimal.Decimal` 类型失败的问题: [https://github.com/gogf/gf/issues/2584](https://github.com/gogf/gf/issues/2584) +6. 改进 `Struct` 方法,解决当结构体的属性类型为 `time.Time/*time.Time`,给定的转换值为非标准库支持的字符串如 `2022-12-15 16:11:34` 而引发的转换失败问题: [https://github.com/gogf/gf/issues/2371](https://github.com/gogf/gf/issues/2371) +2. `gtag` +1. 增加 `SetGlobalEnums/GetEnumsByType` 方法,用以实现自动化的 `Golang Enums` 管理。需要结合 `gf gen enums` 命令使用。 +3. `gutil` +1. 修复 `Dump` 方法在个别场景下的空指针报错问题: [https://github.com/gogf/gf/issues/2487](https://github.com/gogf/gf/issues/2487) +4. `gvalid` +1. 自定义校验方法参数新增 `Field` 字段,表示校验字段的名称,用以解决自定义校验错误提示的字段丢失问题: [https://github.com/gogf/gf/issues/2499](https://github.com/gogf/gf/issues/2499) + +### 社区组件 + +#### 数据库驱动 + +1. 改进 `dm/mysql` 组件,解决当时区配置中带有特殊字符(例如 `/`)的 `QueryEscape` 问题。 + +#### NoSQL适配 + +1. 修复 `redis` 组件配置中缺失的连接池配置参数。 + +#### 服务注册发现 + +1. 增加 `file` 注册发现组件,用于本地基于文件的服务注册发现,通常用于单节点测试。 +2. 完善 `etcd/polaris/zookeeper` 实现细节,并完善单测用例。 + +#### 微服务脚手架 + +1. 新增 `grpcx` 微服务组件,用于 `grpc` 通信协议的微服务开发: [微服务开发](../docs/微服务开发/微服务开发.md) + +## 开发工具 + +1. 增加 `gf gen pb` 命令,用于编译 `proto` 文件生成 `go pb` 文件: [协议编译-gen pb](../docs/开发工具/代码生成-gen/协议编译-gen%20pb.md) +2. 增加 `gf gen pbentity` 命令,用于自动生成数据库表的 `proto` 数据结构定义文件: [数据表PB-gen pbentity](../docs/开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) +3. 增加 `gf gen enums` 命令,用于解析指定目录 `go` 文件,并根据 `enum` 定义规范自动生成 `enums` 数据文件,主要用于 `OpenAPI` 接口文档展示(实验特性): [枚举维护-gen enums](../docs/开发工具/代码生成-gen/枚举维护-gen%20enums.md) +4. 改进 `gf up` 命令,增加自动升级工具 `CLI` 的功能。 +5. 改进 `gf gen service` 命令,当方法注释后不再自动生成到接口定义文件中。 +6. 改进 `gf build` 命令,增加 `DumpENV` 选项,用于控制在编译时是否打印编译使用的环境信息,默认关闭。 +7. 改进 `gf docker` 命令,增加 `Tag` 选项,用以兼容旧版本。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.5 2023-07-17.md b/versioned_docs/version-2.8.x/release/v2.5 2023-07-17.md new file mode 100644 index 00000000000..64ab94aee93 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.5 2023-07-17.md @@ -0,0 +1,85 @@ +--- +slug: '/release/v2.5.0' +title: 'v2.5 2023-07-17' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,v2.5,Golang,gf gen ctrl,API接口,代码生成,数据库,日志组件,OpenTelemetry] +description: 'GoFrame框架发布了v2.5.0版本,提升开发工具和功能组件,新增gf gen ctrl命令以规范化API接口开发和代码生成,支持Golang工程界面规范化,并改进日志、数据库等核心模块。社区组件亦有多项优化。' +--- + +大家好啊, `GoFrame` 框架今天发布了 `v2.5.0` 正式版本啦!👏👏👏👏 + +本次版本主要是对已有功能组件以及开发工具上的改进工作。其中: + +- 开发工具新增了 `gf gen ctrl` 命令,以规范化定义、开发API接口,增加控制器、SDK的代码生成提高开发效率,以解决 `Golang` 工程开发中接口层面的规范和效率问题,详情请参考: [接口规范-gen ctrl](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md)。 +- 原有 `gf gen dao` 命令新增了 `TypeMapping` 特性,允许开发者自定义生成的数据表实体对象属性字段的 `Golang` 类型: [数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + +其他变更内容请参考以下 `change log`。 `Enjoy!` 🍺🍺🍺🍺🍺🍺🍺🍺 + +`Github ChangeLog`: [https://github.com/gogf/gf/releases/tag/v2.5.0](https://github.com/gogf/gf/releases/tag/v2.5.0) + +完整代码变更:[v2.4.0...v2.5.0](https://github.com/gogf/gf/compare/v2.4.0...v2.5.0),感谢本次所有的贡献开发者: + +[![](/markdown/3b87419b0ede464629f3813de922d965.png)](https://github.com/gogf/gf/releases/tag/v2.4.0) + +## 功能改进 + +1. `gdb` + 1. 改进 `ORM SQL` 日志记录,操作的字段按照数据表字段定义顺序进行操作。 + 2. 改进 `HOOK` 方法实现,支持修改 `in` 参数的 `Table` 字段后修改执行的表名。 + 3. 新增 `AllAndCount/ScanAndCount` 方法,用于实现便捷的分页查询场景。 + 4. 新增 `Model.WhereOrNot/WhereOrPrefixNot` 条件方法。 +2. `gi18n` + 1. 改进支持中文( `Unicode`)作为转译的键名。 +3. `gclient` + 1. 新增 `Discovery` 链式操作方法,用于设置本次请求的服务发现组件。 +4. `ghttp` + 1. 改进请求 `Context` 上下文处理,每次 `Context` 的变更将会影响底层的 `http.Request` 对象。以支持自定义 `HTTP Handler` 的数据交互场景。 + 2. 新增 `Endpoints` 配置项支持,用于自定义 `Server` 的服务注册发现地址,而可以使用当前监听的地址。 +5. `goai` + 1. 改进参数校验识别,如果参数为必须参数,则在 `OpenAPIv3` 结果中进行标记。 +6. `gsel` + 1. 修复 `RoundRobin` 实现中 `Endpoints` 更新的锁机制问题。 +7. `glog` + 1. 新增 `TimeFormat` 配置,用于自定义日志输出的时间格式: [日志组件-配置管理](../docs/核心组件/日志组件/日志组件-配置管理.md) + 2. 改进 `Rotation` 实现,支持短运行程序的日志文件切分。 +8. `gtag` + 1. 新增 `GetGlobalEnums` 方法,用于获取全局注册的枚举类型。 +9. `gutil` + 1. 新增 `DumpJson` 方法,用于将任意类型变量按照 `JSON` 格式化打印到终端,便于人工阅读。 +10. `gvalid` + 1. 新增 `enums` 校验规则,用于实现枚举类型的自动识别和校验: [数据校验-校验规则](../docs/核心组件/数据校验/数据校验-校验规则.md) + +## 社区组件 + +1. 修复 `contrib/registry/polaris` 组件在多个服务端时的负载均衡问题。 +2. 改进 `contrib/drivers/pgsql` 在 `TableFields` 返回的 `Index` 字段序号统一从 `0` 开始。 +3. 改进 `contrib/nosql/redis` 新增用户配置项支持。 +4. 改进 `contrib/rpc/grpcx` 组件, `grpcx.Server` 新增 `Endpoints` 配置项支持,用于自定义服务注册发现的地址。 +5. 新增 `contrib/sdk/httpclient` 组件,用于本次版本新增的 `gf gen ctrl` 命令生成的 `HTTP SDK` 代码文件依赖库。 +6. 新增 `contrib/trace/otlpgrpc` 及 `contrib/trace/otlphttp` 组件,用以实现基于 `OpenTelemetry` 的链路跟踪统一对接组件。 + +## 开发工具 + +1. 新增 `gf gen ctrl` 命令,用于编译 `api` 定义目录,自动生成规范的 `controller`、 `HTTP SDK` 代码: [接口规范-gen ctrl](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md) +2. 改进 `gf gen dao` 命令,新增 `TypeMapping` 特性,开发者可自定义数据表字段类型与生成的 `Go` 实体数据结构属性类型映射,并且可以方便引入第三方包类型(如 `decimal` 包以支持高精度类型): [数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +3. 改进 `gf gen enums` 命令,其中的 `Prefix` 参数改为 `Prefixes`,以支持多个生成枚举类型的包前缀指定: [枚举维护-gen enums](../docs/开发工具/代码生成-gen/枚举维护-gen%20enums.md) +4. 改进 `gf gen service` 命令: + - 生成的 `service` 文件中,增加方法注释生成。 + - 当生成的 `service` 文件中存在 `import` 冲突时,自动生成 `import alias`。 +5. 改进命令行封装,暴露 `gfcmd.Command` 类型,便于开发者可以继承扩展自定义命令行功能。 +6. 改进 `gf docker` 命令,将构建文件参数设置为非必须(考虑兼容),未来将会只用于 `Docker` 构建,不再耦合二进制构建功能。如果有完整构建需求,建议未来结合 `gf build` 功能共同使用。并更新项目工程模板的 `make image` 命令,使用 `gf build+gf docker` 命令实现。 +7. 改进 `gf init` 命令,修复在部分场景下初始化项目覆盖已存在的 `.git/.gitignore` 目录及文件问题或权限报错问题。 +8. 改进 `gf up` 命令,修复在部分场景下的框架版本更新问题,以及在 `windows` 系统下的下载安装问题。 +9. 改进 `gf version` 命令,修复在部分场景下的框架版本识别问题。 +10. 修复 `gf gen pbentity` 命令,生成的 `proto` 文件实体数据结构的 `float32/float64/[]byte` 类型修改为 `float/double/bytes` 类型。 +11. 改进开发工具,部分命令可以不用显示配置 `importPrefix` 参数,如: `gf gen dao/service` + +## 兼容提示 + +1. `ghttp.Request` 中的 `Context` 及 `GetCtx` 方法返回的 `context.Context` 继承去掉了 `NeverDoneCtx` 的嵌套,意味着控制器中默认传递的 `ctx` 上下文对象完全继承于标准库的 `http.Request` 中的 `ctx`。在请求结束时将会自动调用 `Done` 方法结束掉,不能将该 `ctx` 传播给需要进一步执行的异步流程。因此,从这个版本开始,使用者可能会遇到以下两个问题: + - 如果需要传播给 **异步流程或者保持和之前逻辑兼容**:增加一个中间件,在中间件中调用 `r.SetCtx(r.GetNeverDoneCtx())` 全局覆盖后续使用的 `ctx` 为不会结束的 `ctx`。 + +![](/markdown/f9b3d06ba28250f95ac7c5c87df1d680.png) + +- 当客户端主动取消请求后,服务端可能会遇到 `context canceled` 的错误。这个属于正常现象,当客户端不在需要这个请求的结果时会取消请求,这时服务端继续往下执行也没有了意义。如果介意这个错误,可以参考上面的中间件增加 `NeverDoneCtx` 的处理逻辑,这个时候服务端会忽略客户端的取消请求并继续往下执行。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.6 2023-12-19.md b/versioned_docs/version-2.8.x/release/v2.6 2023-12-19.md new file mode 100644 index 00000000000..573f33885d0 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.6 2023-12-19.md @@ -0,0 +1,118 @@ +--- +slug: '/release/v2.6.0' +title: 'v2.6 2023-12-19' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,v2.6.0,代码改进,Bug修复,数据库实现,服务注册发现,微服务组件,开发工具,异步goroutine,日志打印] +description: 'GoFrame框架发布v2.6.0版本,升级Golang版本,改善异步goroutine、新增日志打印结构化功能,扩展数据库实现和服务注册组件,优化开发工具及错误处理模块。此外,改进gmutex互斥操作和gcfg配置管理,提升框架的稳定性和功能丰富性。' +--- + +大家好啊, `GoFrame` 框架今天发布了 `v2.6.0` 正式版本啦!👏👏👏👏 + +本次版本主要是大量的代码改进和 `BugFix` 工作。由于本次版本变更内容较多,以下中文介绍一些较为重要的改进点,详细的 `ChangeLog` 请参考(特别是 `BugFix`): [https://github.com/gogf/gf/releases/tag/v2.6.0](https://github.com/gogf/gf/releases/tag/v2.6.0) + +完整代码变更:[https://github.com/gogf/gf/compare/v2.5.0...v2.6.0](https://github.com/gogf/gf/compare/v2.5.0...v2.6.0) + +感谢本次所有的贡献开发者: + +![](/markdown/8cbb41ea81e456eb8a5a145520c9462a.png) + +## 功能改进 + +框架最低依赖的 `Golang` 版本从 `v1.15` 升级到 `v1.18`。 + +1. `g` + 1. 新增 `g.Go方` 法,用于便捷创建带有 `ctx` 和 `recover` 参数的异步 `goroutine`。 +2. `glog` + 1. 改进 `Handler` 回调处理函数的 `HandlerInput` 输入参数,增加 `Values` 参数,该参数为日志打印时的输入参数列表: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 2. 新增 `HandlerStructure` 通用组件方法,将日志打印内容按照结构化参数打印: [日志组件-Handler](../docs/核心组件/日志组件/日志组件-Handler.md) + 3. 改进日志文件 `rotate` 逻辑,解决在个别场景下文件无法 `rotate` 的问题。 +3. `gerror` + 1. 增加错误堆栈模式( `brief/detail`):在 `brief` 模式下,错误堆栈仅会打印非框架组件堆栈。在 `detail` 模式下,错误堆栈会打印完整的框架代码调用链路。框架默认使用 `brief` 模式: [错误处理-其他特性](../docs/核心组件/错误处理/错误处理-其他特性.md) +4. `gcode` + 1. 新增 `gcode.CodeInternalPanic` 错误码,框架组件捕获的所有 `panic` 错误将会以此错误码返回。 +5. `gmap` + 1. 新增 `Diff` 方法,用于对比并返回两个 `Map` 的差异。 +6. `gaes` + 1. 新增 `PKCS7Padding/PKCS7UnPadding` 方法。 +7. `gdb` + 1. 删除 `ConvertDataForRecord` 转换方法,新增 `ConvertValueForField` 转换方法。 + 2. 修改 `CheckLocalTypeForField` 方法,返回参数类型 `string` 修改为 `LocalType` 类型。 + 3. 这两个主要是数据库实现会用到,通常在社区组件中使用。如果使用者本地有 `gdb.DB` 接口的实现需要注意这个改动。 + 4. 新增 `Model.Partition` 方法,允许数据库操作时用户显式指定分区参数。 + 5. 新增 `Model.LeftJoinOnFields/RightJoinOnFields/InnerJoinOnFields` 方法,用于更便捷实现 `Join` 关联操作。 + 6. 修复 `Model.WherePrefixNotIn` 方法实现问题。 +8. `gredis` + 1. 新增 `Cluster` 配置项,用于指定是否使用集群模式: [Redis-配置管理](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) + 2. 新增 `Protocol` 配置项,用于指定 `RESP` 版本: [Redis-配置管理](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) +9. `gi18n` + 1. 改进转译文件读取逻辑,支持从资源管理器中自动读取文件: [I18N国际化-配置管理](../docs/核心组件/I18N国际化/I18N国际化-配置管理.md) +10. `gclient` + 1. 新增 `NoUrlEncode` 特性,设置 `GET` 请求不自动对参数做 `UrlEncode` 编码。 +11. `ghttp` + 1. 改进退出信号处理,支持 `windows` 平台下的退出信号捕获后优雅退出。 +12. `goai` + 1. 支持自动识别 `ghttp.UploadFile` 类型为 `OpenAPIv3` 的 `File` 类型。 + 2. 去掉 `Path` 对象上与 `Method` 对象上重复的描述信息。 + 3. 接口示例的字段类型按照参数数据类型自动转换为对应的数据类型。 +13. `gcfg` + 1. 新增 `AdapterContent` 配置接口实现,使用具体的配置内容来实现配置管理对象: [配置管理-AdapterContent](../docs/核心组件/配置管理/配置管理-接口化设计/配置管理-AdapterContent.md) +14. `gctx` + 1. 新增 `NeverDone` 方法,用于包裹给定的 `ctx` 对象并返回一个永不会过期和 `Cancel` 的 `ctx` 对象。 +15. `gfile` + 1. 默认创建的文件模式从 `0777` 改为了 `0755`。 + 2. 改进 `Copy/CopyFile/CopyDir` 方法,增加 `CopyOption` 可选参数,用于控制复制逻辑的可选项。 +16. `gmutex` + 1. 使用 `Golang` 新版本的 `mutex` 改进 `gmutex.Mutex` 对象,直接使用新版本标准库的 `TryLock/TryRLock`,不再自行实现这些重复的方法。保留 `LockFunc/TryLockFunc` 方法。 + 2. 新增 `gmutex.RWMutex` 对象,扩展自标准库的 `sync.RWMutex` 对象,扩展新增了 `LockFunc/TryLockFunc、RLockFunc/TryRLockFunc` 方法。 +17. `gstr` + 1. 新增 `List2/ListAndTrim2/List3/ListAndTrim3` 方法,实现类似于 `PHP list` 方法特性,将字符串拆分后作为多个结果值返回。 + 2. 新增 `CaseConvert` 方法,用于按照给定的 `CaseType` 类型参数执行字符串命名格式转换。 +18. `gconv` + 1. 新增 `ConvertWithRefer` 方法,用于给定参数作为类型参考,并转换给定参数为指定参数的类型。 +19. `gutil` + 1. 新增 `FillStructWithDefault` 方法,用于自动通过读取 `struct tag` 读取默认值并填充给定的 `struct` 对象/指针。 +20. `gvalid` + 1. 修复 `enums` 校验规则不支持 `map` 参数类型的问题。 + +## 社区组件 + +### 配置管理 + +1. 新增 `contrib/config/consul` 组件,用于配置管理组件接口的 `consul` 服务实现: [https://github.com/gogf/gf/tree/master/contrib/config/consul](https://github.com/gogf/gf/tree/master/contrib/config/consul) + +### 数据库实现 + +1. 改进 `contrib/drivers/dm` 组件: +1. 支持 `schema` 参数配置。 +2. 支持 `time.Time/*time.Time` 时间类型参数操作。 +2. 改进 `contrib/drivers/sqlite` 组件,支持 `Insert Ignore` 及 `Save` 操作。 +3. 新增 `contrib/drivers/sqlitecgo` 组件,通过 `cgo` 方式支持 `i386` 系统架构。 +4. 改进 `contrib/nosql/redis` 组件: +1. 增加 `TLSConfig` 配置,以支持 `TLS` 链接 `Redis Server`。 +2. 增加 `Protocol` 配置项,以支持最新版本的 `Redis Server`。 + +### 服务注册发现 + +1. 新增 `contrib/registry/nacos` 组件,使用 `nacos` 实现微服务的注册发现: [https://github.com/gogf/gf/tree/master/contrib/registry/nacos](https://github.com/gogf/gf/tree/master/contrib/registry/nacos) +2. 改进 `contrib/registry/file` 组件,自动删除过期的注册项,避免客户端发现并连接过期的服务端地址。 +3. 修复 `contrib/registry/polaris` 组件部分实现问题。 + +### 微服务组件 + +1. 改进 `contrib/rpc/grpcx` 组件: +1. 客户端支持直接给定链接地址访问服务端。 +2. 完善单测,提高代码质量。 + +## 开发工具 + +1. 改进 `cli` 工具安装方式,额外支持 `go install` 安装方式: `go install github.com/gogf/gf/cmd/gf/v2@latest` +2. 改进 `gf run` 命令,新增 `WatchPaths/-w` 配置,支持指定监听的路径列表,避免默认监听本地项目所有项目文件引发 `too many opened files` 问题: [自动编译-run](../docs/开发工具/自动编译-run.md) +3. 改进 `gf gen ctrl` 命令,新增 `Merge/-m` 选项,用以控制生成的控制器代码文件按照 `api` 层的文件生成,而不是默认按照 `api` 接口拆分为不同的接口实现文件: [接口规范-gen ctrl](../docs/开发工具/代码生成-gen/接口规范-gen%20ctrl.md) +4. 改进 `gf gen dao` 命令,新增 `RemoveFieldPrefix/-rf` 选项,用于自动去掉生成表字段的名称前缀: [数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) +5. 改进 `gf gen pbentity` 命令,新增 `RemoveFieldPrefix/-rf` 选项,用于自动去掉生成表字段的名称前缀: [数据表PB-gen pbentity](../docs/开发工具/代码生成-gen/数据表PB-gen%20pbentity.md) +6. 改进 `gf gen service` 命令,支持自动识别 `logic` 模块对象的方法注释生成到 `service` 接口文件中。 +7. 改进 `gf version/gf -v` 命令,更详细的工具版本、运行环境、框架版本信息。 +8. 改进开发工具的初始化效率,去掉影响初始化效率的 `init` 包方法逻辑。 +9. 修复 `gf gen dao` 命令中指定 `Link` 数据库配置失效的问题。 +10. 其他一些细节修复。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.7 2024-04-09.md b/versioned_docs/version-2.8.x/release/v2.7 2024-04-09.md new file mode 100644 index 00000000000..ff8042cf404 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.7 2024-04-09.md @@ -0,0 +1,103 @@ +--- +slug: '/release/v2.7.0' +title: 'v2.7 2024-04-09' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,metric监控,OpenTelemetry,数据库支持,定时任务,gcron,HTTP监控,中间件,代码生成,gmetric组件] +description: 'GoFrame框架发布v2.7.0版本,新增metric监控组件并提供HTTP Client和Server的监控指标实现,支持多个数据库的Save操作,改进gcron定时任务组件,增强gdb数据库组件功能。同时,修复若干系统问题,提升框架性能和稳定性。' +--- + +大家好, `GoFrame` 框架今天发布了 `v2.7.0` 正式版本啦! 👏👏👏👏👏👏👏👏👏 + +本次版本最大的看点是提供了 `metric` 监控组件,主库提供了接口化的 `metric` 设计,社区组件提供了基于 `OpenTelemetry` 的 `metric` 接口实现。该特性在默认情况下是关闭的,只有在引入具体的接口实现或者社区实现时才会默认启用。当前版本同时提供了 `HTTP Client&Server` 的监控指标实现,其他组件的监控指标将在后续版本中陆续提供。详情请参考文档: [服务监控告警](../docs/服务可观测性/服务监控告警/服务监控告警.md) + +同时,在本次版本中实现了对 `dm/mssql/oracle/pgsql/sqlite` 数据库的 `Save` 操作支持,感谢社区小伙伴 [https://github.com/oldme-git](https://github.com/oldme-git) 💖。 + +此外,值得一提的是,在本次版本中,我们对 `gcron` 定时任务组件对秒级字段增加了忽略符号 `#` 的特性,用于将 `6` 段式的 `cron pattern` 转换为类似于 `5` 段式的 `linux crontab pattern` 功能,用于解决秒级粒度下由于延迟引起的任务执行不准确问题: [定时任务-表达式](../docs/组件列表/系统相关/定时任务-gcron/定时任务-表达式.md) + +由于本次版本变更内容较多,以下中文介绍一些较为重要的改进点,详细的 `ChangeLog` 请参考: [https://github.com/gogf/gf/releases/tag/v2.7.0](https://github.com/gogf/gf/releases/tag/v2.7.0) + +完整代码变更请参考:[https://github.com/gogf/gf/compare/v2.6.0...v2.7.0](https://github.com/gogf/gf/compare/v2.6.0...v2.7.0) + +感谢所有参与本次版本的贡献开发者们💖! + +![](/markdown/950e1af6550f59942ab68e09ffb63c72.png) + +## 组件改进 + +1. `gdb` + 1. 新增 `Stats` 接口定义及实现,用于获取当前数据库 `orm` 对象维护的连接池信息: [ORM高级特性-连接池状态](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-连接池状态.md) + 2. 新增 `FormatUpsert` 接口定义及实现,用于不同数据库类型实现写入/更新操作,即 `Save` 操作。 + 3. 新增 `SqlType` 类型,并将已有的 `sql type` 类型定义从 `string` 类型改为了 `SqlType` 类型。 + 4. 新增 `Model.OnConflict` 方法,用于实现部分数据库类型当字段唯一键冲突时的更新策略,特别是实现 `Save` 操作。 + 5. 修复 `ClearTableFieldsAll` 方法失效的问题。 +2. `ghttp` + 1. 新增 `MiddlewareNeverDoneCtx` 中间件,开发者可以选择使用,避免客户端取消请求时服务端收到的 `context cancel` 问题: [常见问题](../docs/WEB服务开发/常见问题.md) + 2. 新增 `http server` 的监控指标实现,默认关闭不会影响性能,只有在开启 `metrics` 特性时才会自动开启: [HTTPServer-监控指标](../docs/WEB服务开发/高级特性/HTTPServer-监控指标.md) + 3. 改进 `tracing` 记录,将 `span` 名称从 `query uri` 改为了 `route uri`,便于查看时更容易聚合。 + 4. 改进 `Request` 对象中的 `EnterTime` 及 `LeaveTime` 属性类型,从 `int64` 改为了 `*gtime.Time` 类型。 + 5. 将 `WebSocket` 方法标记废弃,将会在未来的大版本中去掉,也会去掉 `http server` 对 `websocket` 的内嵌耦合支持。未来建议通过其他的 `websocket` 开源组件结合 `http server` 一起使用,更加解耦灵活。 + 6. 修复由于 `Request Body` 默认可重复读取的问题引发的大文件上传占用内存问题。 + 7. 修复 `StartPProfServer` 方法的 `pattern` 参数失效问题。 + 8. 文件下载方法 `Request.ServeFileDownload` 方法新增 `Access-Control-Expose-Headers Header` 返回,以支持 `ajax` 文件请求。 + 9. 服务配置新增 `SwaggerUITemplate` 配置项,用于快捷配置 `SwaggerUI` 页面的 `HTML` 内容。 + 10. `http server` 启动路由打印时不再打印内置的中间件。 +3. `gclient` + 1. 修复在开启服务发现的情况下,所有的 `http` 请求均会强制走服务发现的域名解析问题。 + 2. 新增 `http client` 的监控指标实现,默认关闭不会影响性能,只有在开启 `metrics` 特性时才会自动开启: [HTTPClient-监控指标](../docs/WEB服务开发/HTTPClient/HTTPClient-监控指标.md) +4. `gcron` + 1. 新增忽略符号 `#`,对 `cron pattern` 中的 **秒字段** 进行占位,表示忽略秒字段,用于将 `6` 段式的 `cron pattern` 转换为 `5` 段式的 `linux crontab pattern`: [定时任务-表达式](../docs/组件列表/系统相关/定时任务-gcron/定时任务-表达式.md) + 2. 修复在确定的秒级任务场景中(例如 `2 * * * * *`),当底层时间出现不准确时可能引发两次任务执行。 +5. `gerror` + 1. 修复 `gerror.HasCode` 方法递归逻辑失效的问题。 +6. `g` + 1. 改进 `DumpJson` 方法,用于实现对任意变量按照 `JSON` 格式进行打印。 +7. `gcache` + 1. 修复大量创建 `Cache` 对象引发的 `goroutine` 过多问题。 +8. `gcmd` + 1. 新增 `RunWithSpecificArgs` 方法,用于使用自定义的 `arguments` 运行命令对象。 + 2. 修复部分场景下由于参数结构体名称和结构体标签名称冲突引发的参数丢失问题。 +9. `gfsnotify` + 1. 修复 `gfsnotify` 对象关闭时可能引发的 `panic` 问题。 +10. `glog` + 1. 修复 `rotate` 特性对 `gz` 后缀日志压缩文件的重复 `rotate` 问题。 +11. `gmetric` + 1. 新增 `gmetric` 组件,采用了解耦设计,只有接口定义和 `Noop` 的实现,真实的实现在社区组件中。只有引入具体的实现才会真实开启 `metric` 特性: [服务监控告警](../docs/服务可观测性/服务监控告警/服务监控告警.md) +12. `gproc` + 1. 修复进程参数解析在 `windows` 下可能失败的问题。 + 2. 改进 `Signal` 信号监听实现,允许在运行时增加信号监听处理方法。 +13. `gview` + 1. 修复由于 `os.Getwd` 方法执行失败引起的健壮性问题。 +14. `gconv` + 1. `json.RawMessage` 支持接受 `slice` 类型的参数转换。 + 2. 修复 `MapDeep` 内部转换缺失 `Deep` 参数引发的递归转换失败问题。 + 3. 使用 `MapRange` 改进内部 `Map` 遍历逻辑,提高执行性能。 + +## 社区组件 + +1. 社区 `contrib/drivers` 数据库组件的改进: +1. `contrib/drivers/dm` 组件新增对 `Save` 操作的支持。 +2. `contrib/drivers/mssql` 组件新增对 `Save` 操作的支持。 +3. `contrib/drivers/oracle` 组件新增对 `Save` 操作的支持,并修复写入参数不支持 `gdb.Raw` 类型的问题。 +4. `contrib/drivers/pgsql` 组件新增对 `Save` 操作的支持。 +5. `contrib/drivers/sqlite` 组件新增对 `Save` 操作的支持。 +6. `contrib/drivers/sqlitecgo` 组件新增对 `Save` 操作的支持。 +2. 新增 `contrib/metric/otelmetric` 组件,实现了对 `OpenTelemetry Metric` 的支持: [服务监控告警](../docs/服务可观测性/服务监控告警/服务监控告警.md) +3. 改进 `contrib/nosql/redis` 组件: +1. 新增 `SentinelUsername` 及 `SentinelPassword` 参数配置,以扩展对 `Redis Sentinel` 模式的支持: [Redis-配置管理](../docs/组件列表/NoSQL%20Redis/Redis-配置管理.md) +2. 改进 `Redis` 接口实现,开发者可以灵活自定义扩展、覆盖社区组件对象 `redis.Redis` 类型的实现: [Redis-接口化设计](../docs/组件列表/NoSQL%20Redis/Redis-接口化设计.md) +4. 改进 `contrib/registry/etcd` 组件,允许开发者配置 `etcd` 链接的校验信息。 +5. 改进 `contrib/rpc/grpcx` 组件: +1. 对启用 `tracing` 特性时,安全截断请求内容。 +2. 新增对 `logger` 配置项的支持,允许在配置文件中通过 `logger` 配置项配置 `grpc server` 的日志对象: [服务端配置](../docs/微服务开发/服务端配置.md) +6. 改进 `contrib/trace/otlphttp` 及 `contrib/trace/otlpgrpc` 组件,修复在短进程场景下正常 `ShutDown` 仍可能会出现的 `trace` 数据丢失问题。 + +## 开发工具 + +1. 改进 `gen dao` 生成的 `entity` 源文件,增加 `orm` 标签,以提高数据库查询结果转换到 `entity` 对象的效率。 +2. 改进 `gen service` 命令,修复生成的源码文件中,方法顺序不一致问题。 +3. 改进 `build` 命令,将生成的二进制文件存放目录 `path` 参数的默认值从 `./temp` 改为了 `.` 即当前目录,以解决该参数自定义失效的问题。 +4. 改进 `init` 命令,新增 `-module/g` 参数,用于在初始化项目时显式指定 `go module` 名称。 +5. 修复 `gen dao` 在多个数据库生成配置下,使用 `clear` 参数时删除已生成的 `dao` 源文件的问题。 +6. 修复 `gen pbentity` 命令,使用自定义的 `jsonCase` 参数无效的问题。 +7. 修复 `run` 命令的 `-w` 指定监听目录参数失效的问题。 \ No newline at end of file diff --git a/versioned_docs/version-2.8.x/release/v2.8 2024-11-18.md b/versioned_docs/version-2.8.x/release/v2.8 2024-11-18.md new file mode 100644 index 00000000000..8a9e495c243 --- /dev/null +++ b/versioned_docs/version-2.8.x/release/v2.8 2024-11-18.md @@ -0,0 +1,123 @@ +--- +slug: '/release/v2.8' +title: 'v2.8 2024-11-18' +sidebar_position: -1 +hide_title: true +keywords: [GoFrame,Golang,v2.8,组件改进,兼容提示,数据库,网络服务,文件监听,数据校验,开发工具] +description: '此版本更新包含多个重要改进,包括最低Golang版本要求从1.18调整为1.20,废弃gring组件,删除jaeger组件,改进gjson参数形式,以及提高ghttp和gudp的灵活性。新特性包括数据库连接支持unix socket,支持time字段类型,gdb增加Exist方法,提升gconv转换性能,改进gvalid校验规则与gtest断言方法,增强gcron定时任务管理,并显著提升GoFrame开发工具的功能和扩展性。' +--- + + +我们很高兴宣布`GoFrame`迎来了`v2.8.0`正式版本的发布! +本次更新带来了重要的改进和新特性,使`GoFrame`在开发效率、执行性能、稳定性和扩展性方面取得了显著提升。 + +## 版本亮点 + +1. **兼容性更新** + - `GoFrame v2.8.0`要求最低 `Golang` 版本为 `1.20`,提供更高效的性能和稳定性。 + - `container/gring` 组件已被废弃,同时 `contrib/trace/jaeger` 组件从源码仓库移除,简化了依赖管理。 + +2. **组件改进** + - `database/gdb`模块现在支持`created_at/updated_at/deleted_at`整型时间戳字段、`unix socket`连接、`time/year` 字段类型,并增加了 `Model.Exist` 方法,显著提升了数据库操作的灵活性和效率。 + - `util/gconv`组件使用类型缓存提升转换性能,针对复杂数据类型的转换性能提升约`300%`。 + - `net/ghttp` 和 `net/gudp` 网络服务组件优化了参数配置和请求处理逻辑,为用户提供更简洁的开发体验。 + +3. **开发工具升级** + - `gf init` 命令支持生成单仓多应用项目结构。 + - `gf gen ctrl`、`gf gen dao` 和 `gf run` 等命令的优化,进一步增强了代码生成的灵活性与执行效率,简化了项目搭建流程。 + +4. **社区组件支持** + - 为 `contrib/drivers/mssql` 和 `contrib/registry/etcd` 等多个社区驱动增加了对最新功能的支持,提升了与外部服务集成的便捷性。 + +## 未来展望 + +`GoFrame`团队感谢每一位社区用户的支持。 +我们将继续倾听社区反馈,为用户提供更丰富的功能和更高效的开发支持。 + + +## 特别致谢 + +💖💖💖 感谢所有参与本次版本的贡献开发者们 💖💖💖 + +![alt text](QQ_1731813654454.png) + + +# 主要内容 + +本次版本变更内容较多,以下为本次版本的主要内容,详细的`Change Log`请参考: +[https://github.com/gogf/gf/releases/tag/v2.8.0](https://github.com/gogf/gf/releases/tag/v2.8.0) + +完整的代码变更请参考:[https://github.com/gogf/gf/compare/v2.7.0...v2.8.0](https://github.com/gogf/gf/compare/v2.7.0...v2.8.0) + + +## 兼容提示 +1. 最低`Golang`版本要求从`1.18`调整为了`1.20`。 +2. 组件`container/gring`标记废弃,未来不再继续维护。 +3. 组件`contrib/trace/jaeger`已从源码仓库删除。 +4. 组件`encoding/gjson`的`Load*`方法参数,从`interface{}`调整为了`[]byte`参数,以提高性能。 +5. 组件`net/ghttp`的`StartPProfServer`方法端口参数从`port int`调整为了`address string`以提高灵活性。 +6. 组件`net/gudp`重构,个别方法参数有调整:[UDP组件](../docs/组件列表/网络组件/UDP组件/UDP组件.md) + +## 组件改进 +1. `database/gdb` + - 时间维护特性增加对整型字段的支持,当`created_at/updated_at/deleted_at`为整型字段时,将使用时间戳更新该字段,详见:[ORM链式操作-时间维护](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-时间维护/ORM链式操作-时间维护.md) + - 新增`Model.Exist`方法,用于判断满足所给条件的数据是否存在:[ORM查询-Exist](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-Exist.md) + - 新增对数据库`time/year`字段类型的支持:[ORM高级特性-类型识别](../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性-类型识别.md) + - 新增`OrderRandomFunction`接口方法,并为常用数据库实现并支持了`OrderRandom`排序方法。 + - 改进`Model.Fields`方法,增加对`gdb.Raw`类型参数的支持:[ORM链式操作-字段获取](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段获取.md) + - 改进`With`特性中的`orm`标签,增加对`unscoped`的支持:[模型关联-静态关联-With特性](../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) + - 配置文件增加对`unix socket`本地数据库服务连接的支持。 + - 去掉链路跟踪`trace`信息中的`sql`语句,以减少`trace`包大小。 + - 改进查询结果到`struct`的转换性能。 +2. `net/ghttp` + - 取消了规范路由注册中对输入输出对象的`XxxReq/XxxRes`命名限制。 + - `StartPProfServer`的端口参数从`port int`调整为了`address string`以提高灵活性。 + - 去掉了链路跟踪`trace`信息中的请求/返回数据内容,以减少`trace`包大小。 + - 修复`/debug/admin/shutdown`接口关闭`Server`时的优雅关闭问题。 +3. `net/goai` + - 允许在请求/返回结构体属性中通过`type`标签指定参数的类型。 +4. `container/gtree` + - 使用第三方数据结构组件`github.com/emirpasic/gods`重构`gtree`实现,以提高可扩展性,利于维护。 +5. `encoding/gjson` + - `Load*`方法参数,从`interface{}`调整为了`[]byte`参数,以提高性能。 +6. `os/gcron` + - 新增`StopGracefully`方法,用于等待当前正在执行的定时任务完成后再停止定时任务:[定时任务-基本使用](../docs/组件列表/系统相关/定时任务-gcron/定时任务-基本使用.md) +7. `os/gfsnotify` + - 改进文件递归监听实现,当监听目录时,如果后续在目录中创建子级目录,或者子级目录的子级目录,以此类推,也将会被递归监听:[文件监控-gfsnotify](../docs/组件列表/系统相关/文件监控-gfsnotify/文件监控-gfsnotify.md) +8. `test/gtest` + - 改进`AssertIN/AssertNI`断言方法,增加对字符串子串的包含断言支持。 +9. `util/gvalid` + - 新增`required-if-all`校验规则,所有指定的参数和对应的数值相等时,该参数为必须参数:[数据校验-校验规则](../docs/核心组件/数据校验/数据校验-校验规则.md) + - 改进`phone`校验规则,增加对`171`系列手机号码的验证支持。 +10. `util/gconv` + - 使用类型缓存提升转换性能,针对复杂数据类型的转换性能提升约`300%`。 + +## 社区组件 +1. `drivers/mssql` + - 基础`driver`从`github.com/denisenkom/go-mssqldb`改为了官方组件`github.com/microsoft/go-mssqldb`。 +2. `contrib/drivers/pgsql` + - 增加`InsertIgnore`操作支持。 + - 增加对`Golang`数组类型到数据库数组字段类型操作的支持。 +3. `contrib/registry/etcd` + - 增加`DialTimeout`及`AutoSyncInterval`配置项。 +4. `contrib/registry/nacos` + - 使用官方组件依赖`github.com/nacos-group/nacos-sdk-go/v2`替换掉`github.com/joy999/nacos-sdk-go` +5. `contrib/rpc/grpcx` + - 去掉了链路跟踪`trace`信息中的请求/返回数据内容,以减少`trace`包大小。 + - 由于`grpc`组件已启用`grpc.Dial`方法,这里使用`grpc.NewClient`替换掉了`grpc.Dial`。 +6. `contrib/sdk/httpclient` + - 新增`Handler`接口,允许使用者自定义处理`HTTP Client`返回数据。 + +## 开发工具 +1. 改进`gf init`命令,新增`-a/monoApp`选项,用于在大仓下新创建应用项目模板:[项目创建-init](../docs/开发工具/项目创建-init.md) +2. 改进`gf pack`命令,新增对命令参数选项的配置文件支持,配置项路径为`gfcli.pack`。 +3. 改进`gf tpl`命令,新增对命令参数选项的配置文件支持,配置项路径为`gfcli.tpl.parse`。 +4. 改进`gf gen ctrl`命令,使用`AST`重新实现解析逻辑,提高生成`Go`代码文件内容的准确性,并提升该功能的可扩展性。 +5. 改进`gf run`命令: + - 新增对命令参数选项的配置文件支持,配置项路径为`gfcli.run`。 + - 在临时的子进程结束时,如新进程替换老进程时,自动删除临时的老进程二进制文件。 +6. 改进`gf gen dao`命令: + - 增加`field mapping`特性,支持对指定字段配置生成的`Golang`数据类型:[数据规范-gen dao](../docs/开发工具/代码生成-gen/数据规范-gen%20dao.md) + - 自动读取生成代码文件的目录名称,作为生成的`dao/do/entity`文件的包名称。 + - 由于`dm`数据库限制了`cli`支持的平台,因此`cli`去掉了对`dm`数据库的默认支持,若有需要请手动修改源码安装`cli`。 + - 修复传递`link`参数和配置文件同时存在的场景下,`link`参数失效问题。 diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" new file mode 100644 index 00000000000..89b3b936a23 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.1 2018-04-23.md" @@ -0,0 +1,26 @@ +--- +slug: '/release/v0.1.0' +title: 'v0.1 2018-04-23' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,GoFrame框架,gfsnotify,配置管理,模板引擎,gconv,gpage分页,ghttp,gredis包,gdb数据库ORM] +description: 'v0.1版本更新发布,新增多个模块并改进现有功能,包括GoFrame框架的gfsnotify文件监控、配置和模板引擎更新机制、gconv类型转换、gpage分页管理、多样化日志处理、gredis封装Redis操作,增强ghttp和gdb功能,提供详细开发文档,并修复多项问题。' +--- + +1. 增加gfsnotify文件监控模块; +2. 配置管理模块增加配置文件自动检测更新机制; +3. 模板引擎增加对模板文件的自动检测更新机制; +4. 改进gconv包基本类型转换功能,提高转换性能; +5. 增加gpage分页管理包,支持动态分页、静态分页以及自定义分页样式特性; +6. ghttp.Request增加Exit方法,用以标记服务退出,当在服务执行前调用后,服务将不再执行; +7. ghttp.Response去掉WriteString方法,统一使用Write方法返回数据流,是使用灵活的参数形式; +8. 模板引擎增加模板变量暴露接口LockFunc/RLockFunc,以便支持开发者灵活处理模板变量; +9. ghttp.Server增加access & error log功能,并支持开发者自定义日志处理回调函数注册; +10. 增加gredis包,支持对redis的客户端操作封装,并将gredis.Redis对象加入到gins单例管理器中进行统一配置管理维护; +11. gins单例管理器增加对单例对象配置文件的自动检测更新机制,当配置文件在外部发生变更时,自动刷新单例管理器中的单例对象; +12. gdb数据库ORM包增加And/Or条件链式方法,并改进Where/Data方法参数灵活性; +13. 对于新增加的模块,同时也增加了对应的开发文档,并梳理完善了现有的其他模块开发文档; +14. 修复ISSUE: + - #IISWI [github.com/gogf/gf/issues/IISWI](http://github.com/gogf/gf/issues/IISWI) + - #IISMY [github.com/gogf/gf/issues/IISMY](http://github.com/gogf/gf/issues/IISMY) + - 反馈并跟踪完成第三方依赖mxj包的ISSUE修复( [github.com/clbanning/mxj/issues/48](http://github.com/clbanning/mxj/issues/48)); \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" new file mode 100644 index 00000000000..561542cd5f5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.2 2018-05-21.md" @@ -0,0 +1,68 @@ +--- +slug: '/release/v0.2.0' +title: 'v0.2 2018-05-21' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,GoFrame框架,v0.2,平滑重启,gflock,gproc,gpage,ghttp,gdb,功能改进] +description: '此版本v0.2包括多个新特性和功能更新,如GoFrame框架的平滑重启特性、gflock文件锁模块、gproc进程管理模块以及强大的gpage分页管理模块。此外,还新增了ghttp.Server多端口监听特性和gspath目录检索工具。功能改进涵盖增强的gredis客户端、gdb方法的性能优化以及ghttp包中请求记录的增强。多个问题修复确保了GoFrame框架的稳定性与可靠性。' +--- + +### 新特性 + +1. 平滑重启特性( [http://gf.johng.cn/625833](http://gf.johng.cn/625833) ); +2. gflock文件锁模块( [http://gf.johng.cn/626062](http://gf.johng.cn/626062) ); +3. gproc进程管理及通信模块( [http://gf.johng.cn/626063](http://gf.johng.cn/626063) ); +4. gpage分页管理模块,强大的动态分页及静态分页功能,并为开发者自定义分页样式提供了极高的灵活度( [http://gf.johng.cn/597431](http://gf.johng.cn/597431) ); +5. ghttp.Server增加多端口监听特性,并支持HTTP/HTTPS( [http://gf.johng.cn/494366](http://gf.johng.cn/494366) , [http://gf.johng.cn/598802](http://gf.johng.cn/598802) ); +6. 增加gspath目录检索包管理工具,支持对多目录下的文件检索特性; +7. ghttp包控制器及执行对象注册增加更灵活的动态路由特性,路由规则增加`{method}`变量支持; + +### 新功能 + +1. gutil包增加MapToStruct方法,支持将map数据类型映射为struct对象; +2. gconv + - gconv包增加按照类型名称字符串进行类型转换; + - gconv包新增Time/TimeDuration类型转换方法; +3. ghttp + - 增加WebServer目录安全访问控制机制; + - ghttp.Server增加自定义状态码回调函数注册处理; +4. gdb + - gdb包增加gdb.GetStruct/gdb.Model.Struct方法,获取查询结果记录自动转换为指定对象; + - gdb增加Value/Record/Result类型,增加对Value类型的系列类型转换方法; + - gdb包增加db.GetCount,tx.GetCount,model.Count数量查询方法; + +### 功能改进 + +1. 改进gredis客户端功能封装; +2. 改进grand包随机数生成性能; +3. grand/gdb/gredis包增加benchmark性能测试脚本; +4. 改进gjson/gparser包的ToStruct方法实现; +5. gdb :改进gdb.New获取ORM操作对象性能; +6. gcfg :改进配置文件检索功能; +7. gview:模板引擎增加多目录检索功能; +8. gfile:增加源码main包目录获取方法MainPkgPath; +9. ghttp + - ghttp.Request增加请求进入和完成时间记录,并增加到默认日志内容中; + - ghttp.Server事件回调之间支持通过ghttp.Request.Param自定义参数进行流程传参; +10. gdb + - 改进gdb.Result与gdb.List, gdb.Record与gdb.Map之间的类型转换,便于业务层数据编码处理(如json/xml); + - 改进gdb.Tx.GetValue返回值类型; + - gdb.Model.Data参数支持更加灵活的map参数; + +### 问题修复 + +1. ghttp + - 修复ghttp包路由缓存问题; + - 修复服务注册时的控制器及执行对象方法丢失问题; +2. gconv + - 修正gconv.Float64方法位大小设置问题; + - 修复gconv.Int64(float64(xxx))问题; +3. gdb + - 修复gdb.GetAll针对返回数据列表的for..range…的返回结果slice相同指针问题; + - 修复gdb.Delete方法错误; + - 修复gdb.Model.And/Or方法; + - 修复gdb.Model.Where方法参数处理问题; +4. garray:修复garray包Remove方法锁机制问题; +5. gtype :修复gtype.Float32/gtype.Float64对象类型的方法逻辑错误; +6. gfsnotify:修复在windows下文件参数中不同文件分隔符引起的热更新机制失效问题; +7. 修复gvalid包验证问题:如果值为nil,并且不需要require\*验证时,其他验证失效。并增加单元测试项,测试通过。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" new file mode 100644 index 00000000000..4d65435682f --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/v0.3 2018-08-07.md" @@ -0,0 +1,66 @@ +--- +slug: '/release/v0.3.0' +title: 'v0.3 2018-08-07' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,DES加密,Kafka客户端,对象复用池,网络通信,并发安全,数据库调试,动态路由,分页URL] +description: 'GoFrame框架的v0.3版本的新增特性、功能改进和问题修复。新特性包括增加DES加密/解密算法包、Kafka的Gololang客户端、以及对象复用池等。功能改进涵盖了网络通信包的重构、数据库查询缓存特性、HTPP客户端账号密码设置,以及服务器路由匹配规则增强等。另外,还对多个包和函数进行了优化,提高了系统性能和稳定性。' +--- + +### 新特性 + +1. 新增gdes包,用于DES加密/加密算法处理; +2. 新增gkafka包,kafka的golang客户端; +3. 新增gpool对象复用池,比较于标准库的sync.Pool更加灵活强大,可自定义对象的缓存时间、创建方法、销毁方法( [http://gf.johng.cn/686654)](http://gf.johng.cn/686654)) +4. 完成网络通信gtcp/gudp包的重构,并进行了大量的改进工作,新增了详尽的开发文档及示例代码( [http://gf.johng.cn/494382)](http://gf.johng.cn/494382)) +5. 增加gring并发安全环,标准库container/ring包的并发安全版本,并做了易用性的封装( [http://gf.johng.cn/686655)](http://gf.johng.cn/686655)) +6. gtime包新增了自定义日期格式话的支持,格式化语法类似PHP的date语法( [http://gf.johng.cn/494387)](http://gf.johng.cn/494387)) +7. gdb增加调试模式特性,使用SetDebug方法实现,在调试模式下可以获取详细的SQL执行记录,增加了详细的开发文档及示例代码( [http://gf.johng.cn/702801)](http://gf.johng.cn/702801)) +8. gdb增加查询缓存特性,使用Cache方法实现,增加了详细的开发文档及示例代码( [http://gf.johng.cn/702801)](http://gf.johng.cn/702801)) +9. ghttp.Server路由功能增加字段匹配规则特性,支持如:`/order/list/{page}.html` 动态路由规则特性( [http://gf.johng.cn/702766)](http://gf.johng.cn/702766)) +10. gpage分页包增加分页URL规则生成模板特性,内部可使用`{.page}`变量指定页码位置( [http://gf.johng.cn/716438)](http://gf.johng.cn/716438)) +11. 增加gmap.Map对象,这是gmap.InterfaceInterfaceMap的别名; + +### 新功能 + +1. gdb增加MaxIdleConnCount/MaxOpenConnCount/MaxConnLifetime三项配置,并增加SetMaxConnLifetime方法; +2. ghttp.Client增加HTTP账号密码设置功能(SetBasicAuth); +3. glog新增对系统换行符号的自适配调整(\\n\|\\r\\n); +4. 增加glog控制台调试模式打印开关(SetDebug); +5. gcfg增加SetFileName方法设置默认读取的配置文件名称; +6. gcfg/gjson/gparser包新增Int8/16/32/64,Uint8/16/32/64方法; +7. 增加gzip方法的封装(Zip/Unzip); +8. gview增加模板变量分隔符设置方法SetDelimiters; +9. ghttp.Response增加Writef、Writefln方法; + +### 功能改进 + +1. 改进gfilepool文件指针池设计;改进gfile文本内容写入,增加指针池使用 +2. gdb包增加调试模式特性,并支持在调试模式下获得已执行的SQL列表结果 +3. 改进gproc进程间通信机制,增加进程消息分组特性,并限定队列大小 +4. gdb结果方法处理增加ToXml/ToJson方法 +5. gregx包名修改为gregex +6. 改进gtime.StrToTime方法,新增对常见标准时间日期的自动转换,以及对时区的自动识别支持,并调整gconv,gvalid对该包的引用 +7. 增加对字符集转换的封装,gxml包中使用新增的字符集转换包来做处理 +8. ghttp.Server.EnableAdmin页面Restart接口支持GET参数newExeFilePath支持 +9. ghttp.Server平滑重启机制增加可自定义重启可执行文件路径,特别是针对windows系统特别有用(因为windows下不支持可执行文件覆盖更新) +10. 改进ghttp.Server静态文件检索设计,增加开发环境时的main包源码目录查找机制;改进gcfg/gview的main包源码目录查找机制 +11. 优化gcache设计,LRU特性非默认开启;优化gtype/gcache基准测试脚本;新增gregx基准测试脚本,改进设计,提升性能 +12. gfile包增加GoRootOfBuild方法,用于获取编译时的GOROOT数值;并改进glog包中backtrace的GOROOT路径过滤处理; +13. 改进grpool代码质量,并改进对池化goroutine数量的限制设计 +14. 改进gdb.Map/List及g.Map/List的类型定义,改用别名特性以便支持原生类型输入(map/slice),并修复gdb.Model.Update方法参数处理问题 +15. 调整ghttp包示例代码目录结构,增加ghttp.Client自定义Header方法,ghttp.Cookie增加Map方法用于获得客户端提交的所有cookie值,构造成map返回 +16. 删除gcharset中的getcharset方法 +17. 去掉gmap中常用的基本数据类型转换获取方法 +18. 改进gconv.String方法,当无法使用基本类型进行字符串转换时,使用json.Marshal进行转换 +19. gvalid.CheckObject方法名称修改为gvalid.CheckStruct + +### 问题修复 + +1. 修正gstr.IsNumeric错误 +2. 修复当xml中encoding字符集为非UTF-8字符集时报错的问题 +3. 修正gconv包float32->float64精度问题 +4. 修复gpage包分页计数问题 +5. 修复gdb批量数据Save错误 +6. 去掉gpool中math.MAXINT64常量的使用,以修复int64到int类型的转换错误,兼容32位系统 +7. 修正ghttp包没有使用Server仍然初始化相关异步goroutine的问题 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" new file mode 100644 index 00000000000..f5a87a5c88a --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v0.x.md" @@ -0,0 +1,13 @@ +--- +slug: '/release/v0.x' +title: '历史版本记录 v0.x' +sidebar_position: 999999 +hide_title: true +keywords: [历史版本记录,GoFrame,v0.x版本,更新日志,软件更新,版本发布,功能改进,性能优化,问题修复,框架] +description: '本节详细记录了GoFrame框架v0.x版本的所有历史更新,包括各个版本的功能改进、性能优化和问题修复等内容,以便开发者快速了解版本变化和新特性。' +--- + + +import DocCardList from '@theme/DocCardList'; + + \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" new file mode 100644 index 00000000000..ced13d72575 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.0 2018-10-24.md" @@ -0,0 +1,105 @@ +--- +slug: '/release/v1.0.0' +title: 'v1.0 2018-10-24' +sidebar_position: 16 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf-orm,gkafka,gcron,gredis,gmlock,gaes,gproc,gfcache] +description: 'GoFrame v1.0发布,包括新特性如gf-orm对SQLite的支持,gkafka模块的增加,gcron定时任务模块以及gredis的单例操作改进等。同时新增模块如gmlock内存锁和gaes算法模块,并完善日志管理、模板引擎和数据库操作等功能,提升开发者的使用体验和性能。' +--- + +### 新特性 + +1. `gf-orm` 增加 `sqlite` 数据库类型支持( [http://gf.johng.cn/database/orm/database](http://gf.johng.cn/database/orm/database)); +2. 增加 `gkafka` 模块,对kafka的客户端程序封装,支持分组消费及指定起始位置等特性,并提供简便易用的API接口( [http://gf.johng.cn/database/gkafka/index](http://gf.johng.cn/database/gkafka/index)); +3. 增加go语言最新版本的 `go modules` 特性支持; +4. 增加 `gcron` 定时任务模块( [http://gf.johng.cn/os/gcron/index](http://gf.johng.cn/os/gcron/index)); +5. `WebServer` 增加路由注册项获取/打印特性,所有的路由注册/回调注册一览无余; +6. 模板引擎增加全局变量管理,并增加多个常用的内置函数及内置变量( [http://gf.johng.cn/os/gview/funcs](http://gf.johng.cn/os/gview/funcs)); +7. `gredis` 改进为单例操作方式(基于基层连接池特性),每次操作 `redis` 服务器时开发者无需显示调用 `Close` 方法执行关闭( [http://gf.johng.cn/database/gredis/index](http://gf.johng.cn/database/gredis/index)); +8. `gf-orm` 增加数据库操作自动 `Close` 特性(基于底层链接池特性),开发者无需再 `defer db.Close()`,并增加 `g.DB` 数据库对象单例别名( [http://gf.johng.cn/database/orm/linkop](http://gf.johng.cn/database/orm/linkop)); +9. 增加 `gvar` 通用动态变量模块( [http://gf.johng.cn/container/gvar/index](http://gf.johng.cn/container/gvar/index)); +10. 数据结构容器增加 `并发安全特性开启/关闭功能`,当关闭后和普通的数据结构无异,且在非并发安全模式下性能会得到提高; +11. 新增 `gmlock` 内存锁模块( [http://gf.johng.cn/os/gmlock/index](http://gf.johng.cn/os/gmlock/index)); +12. 增加 `gaes` 算法模块( [http://gf.johng.cn/crypto/gaes/index](http://gf.johng.cn/crypto/gaes/index)); +13. `gproc` 模块增加执行 `shell` 命令方法( [http://gf.johng.cn/os/gproc/index](http://gf.johng.cn/os/gproc/index)); +14. 新增 `gfcache` 模块,用于带自动缓存更新的文件内容操作(文档待完善); + +### 新功能 + +1. `glog` 增加链式操作方法,增加日志级别管理控制、分类管理、调试管理功能; +2. `g.View` 增加分组名称设置,支持通过 `g.*` 对象管理器获取多个命名的单例模板引擎对象; +3. `glog` 增加对文件名称格式的自定义设置,支持 `gtime日期格式`; +4. `gconv` 增加 `Ints/Uints/Floats/Interfaces` 转换方法; +5. `gjson` 增加 `Append` 方法; +6. `gparser` 增加 `NewUnsafe/Append` 方法; +7. `gcache` 增加 `GetOrSet/GetOrSetFunc/GetOrSetFuncLock` 方法; +8. `gset` 增加 `LockFunc/RLockFunc` 方法; +9. `ghttp.Response` 方法完善,增加 `ParseTpl/ParseTplContent/TplContent` 方法, `Template` 修改为 `Tpl` 方法; +10. `ghttp.Request` 增加获取用户真实IP判断; +11. `Session` 增加 `Contains` 方法; +12. 完善 `ghtml` 模块,增加多个方法; +13. `gcache` 新增 `Contains/SetIfNotExist` 方法; +14. `gvalid` 增加 `Error` 对象,用以管理校验错误信息; +15. `gvalid` 模块增加 `struct tag` 的校验规则、自定义错误提示信息绑定的支持特性( [http://gf.johng.cn/util/gvalid/index](http://gf.johng.cn/util/gvalid/index)); +16. `ghttp` 增加输入参数与 `struct` 的 `绑定机制`,并增加对应 `params` 标签支持( [http://gf.johng.cn/net/ghttp/service/handler](http://gf.johng.cn/net/ghttp/service/handler)); +17. `ghttp.Request` 增加服务端 `BasicAuth` 功能(文档待完善); +18. `gvalid` 增加字段校验别名,用于自定义返回结果字段,并更新WebServer中相关使用的模块; +19. `gf-orm` 链式操作增加 `ForPage` 方法,调整 `Chunks` 方法; +20. `ghttp` 对象路由注册增加 `Init&Shut` 自动回调方法,增加重复路由注册检测功能; +21. `gfsnotify` 增加默认递归 `Add/Remove` 特性; +22. `ghttp.Response` 增加 `ServiceFile` 方法; +23. 其他一些新功能; + +### 功能改进 + +1. 改进 `ghttp.Server` 配置管理; +2. 改进 `gcache` 底层对象继承关系,改进部分设计细节,提高性能; +3. 改进 `gfpool` 文件指针池,修复部分错误,提升性能,并增加基准测试代码; +4. 改进 `gmap` 系列并发安全map数据结构,增加多个易用性的方法; +5. 改进 `gconv.Struct` 对象转换功能( [http://gf.johng.cn/util/gconv/index](http://gf.johng.cn/util/gconv/index)); +6. 改进 `grand` 随机数生成规则,提供了极高的随机数生成性能,并保证每一次调用随机方法时生成的都是不同的随机数值( [http://gf.johng.cn/util/grand/index](http://gf.johng.cn/util/grand/index)); +7. 改进 `gfile` 文件内容操作方法,增加若干常用的文件内容读取方法; +8. 改进 `gtime` 模块,并增加时区转换方法; +9. 改进 `COOKIE`,去掉锁机制; +10. 改进 `SESSION` 获取方法,新增多个类型获取方法; +11. 改进 `g.DB/g.Config` 单例缓存键名; +12. 改进 `gtcp/gudp` 超时错误判断机制; +13. 改进 `gtype` 底层统一修改为原子操作; +14. 改进 `gvalid` 对 `struct` 的 `string` 属性的默认值非必需校验; +15. 改进 `gvalid` 在关联规则下的非必需校验; +16. 改进 `gf-orm` 在调试模式下日志自动输出功能; +17. `ghttp.Server/gspath` 模块静态文件检索改进; +18. 优化 `ghttp.ServerConfig` 配置,增加 `struct/method` `名称到uri` 的转换规则,通过 `SetNameToUri` 方法进行灵活配置( [http://gf.johng.cn/net/ghttp/service/object](http://gf.johng.cn/net/ghttp/service/object)); +19. 改进 `*any/:name` 路由匹配规则,支持不带名字的 `*/:` 路由规则; +20. 修改默认配置文件名称 `config.yml` -\> `config.toml`( [http://gf.johng.cn/os/gcfg/index](http://gf.johng.cn/os/gcfg/index)); +21. 调整服务注册的 `BindControllerMethod` 及 `BindObjectMethod` 逻辑为绑定路由到指定的方法执行; +22. 改进 `garray` 二分查找方法,增加安全操作处理; +23. 改进 `gdb.Result/Recorde` `ToXml` 方法,增加可选的 `rootTag` 参数; +24. 其他一些改进; + +### 问题修复 + +1. 修复 `ghttp.Server` 在 `windows` 下的重启失效问题; +2. 修复 `ghttp.Server` 服务注册与回调注册路由重复判断问题; +3. 修复 `garray` 排序数组 `Add` 变参时的死锁问题; +4. 修复 `gfsnotify` 默认递归监控整个 `gspath.Add` 添加的目录的问题; +5. 修复 `ghttp.BindParams` 对 `@file` 文件上传标识符的转义问题; +6. 修复 `ghttp.Server` 日志路径丢失问题; +7. 修复 `多WebServer` 下的状态检测问题; +8. 修复 `gvalid` 模块 `min/max` 校验问题; +9. 修复控制器和执行对象服务注册时绑定’/‘路由的问题; +10. 修复 `gvalid.CheckStruct` 自定义错误提示失效问题; +11. `ghttp.Server` 修复 `hook` 与 `serve` 方法的路由影响,并新增跳转方法; +12. 其他一些修复; + +### 其他改动 + +1. 去掉 `gfile.IsExecutable` 方法; +2. 目录调整,将 `加密/解密` 相关的包从 `encoding` 目录迁移到 `crypto` 目录下; +3. 增加 `gfsnotify/gfcache` 调试信息; +4. `gf-orm` 允许写入的键值为 `nil` 时往数据库中写入 `null`; +5. 统一使用 `gview.Params` 类型作为模板变量类型; +6. `gconv.MapToStruct` 方法名称修改为 `gconv.Struct`; +7. `ghttp.Server` 完善重启及停止的终端提示信息; +8. 完善 `gring` 模块,增加 `约瑟夫问题` 代码作为 `gring` 示例程序; +9. 其他一些改动; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" new file mode 100644 index 00000000000..5855d9f5e06 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.1 2018-11-26.md" @@ -0,0 +1,10 @@ +--- +slug: '/release/v1.1.0' +title: 'v1.1 2018-11-26' +sidebar_position: 15 +hide_title: true +keywords: [GoFrame,GoFrame框架,v1.1,版本更新,功能合并,v1.2,更新日志,软件发布,技术文档,开发框架] +description: '该文档介绍了GoFrame框架的v1.1版本,该版本的功能合并到v1.2一起发布。通过这个更新日志,用户可以了解版本的改进和功能调整细节。' +--- + +该版本功能合并到 `v1.2` 一起发 [v1.2 2018-11-26](./v1.2%202018-11-26.md) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" new file mode 100644 index 00000000000..feda38d35a5 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.10 2019-12-05.md" @@ -0,0 +1,186 @@ +--- +slug: '/release/v1.10.0' +title: 'v1.10 2019-12-05' +sidebar_position: 6 +hide_title: true +keywords: [GoFrame,WebServer,Session Storage,日志组件,JSON 数据格式,guuid 模块,数据库改进,ghttp 改进,container 改进,gconv 性能优化] +description: 'GoFrame 框架推出 v1.10 版本,为开发者带来了多个新特性和改进。这次更新包括 WebServer 中间件和路由实现优化、增加文件配置管理、Session 多种存储实现、日志组件单例对象、JSON 数据格式支持、guuid 模块用于 UUID 生成,数据库、网络、容器等多个模块的性能改进,进一步提高了操作效率和开发体验。' +--- + +各位 `gfer` 久等了,较上一次发布时间过去已有两个多月了,这段时间 `GoFrame` 也在不断地迭代改进,细节比较多,拟了个大概,以下是 `release log`。 + +另外, `GoFrame` 也参加了2019最受欢迎中国开源软件评选投票,明天就结束了,欢迎为 `GoFrame` 投票啊: [https://www.oschina.net/project/top\_cn\_2019](https://www.oschina.net/project/top_cn_2019) 网页可以投一票,微信也可以投一票。 + +### 新特性 + +1. `WebServer` 新特性: + - 改进中间件及分组路由实现: [https://goframe.org/net/ghttp/router/middleware](https://wiki.goframe.org/net/ghttp/router/middleware) + - 增加文件配置管理特性: [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - 改进参数获取: [https://goframe.org/net/ghttp/request/index](https://wiki.goframe.org/net/ghttp/request/index) + - 改进文件上传: [https://goframe.org/net/ghttp/client/demo/upload](https://wiki.goframe.org/net/ghttp/client/demo/upload) +2. `Session` 增加内置的多种 `Storage` 实现: + - 基本介绍: [https://goframe.org/os/gsession/index](https://wiki.goframe.org/os/gsession/index) + - 文件存储: [https://goframe.org/os/gsession/file](https://wiki.goframe.org/os/gsession/file) + - 内存存储: [https://goframe.org/os/gsession/memory](https://wiki.goframe.org/os/gsession/memory) + - `Redis` 存储: [https://goframe.org/os/gsession/redis](https://wiki.goframe.org/os/gsession/redis) +3. 增加日志组件单例对象,并优化配置管理: + - [https://goframe.org/frame/g/index](https://wiki.goframe.org/frame/g/index) + - [https://goframe.org/os/glog/config](https://wiki.goframe.org/os/glog/config) +4. 常用的 `container` 容器增加 `JSON` 数据格式的 `Marshal`/ `UnMarshal` 接口实现: + - [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) + - [https://goframe.org/container/garray/index](https://wiki.goframe.org/container/garray/index) + - [https://goframe.org/container/gset/index](https://wiki.goframe.org/container/gset/index) + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) + - [https://goframe.org/container/gtype/index](https://wiki.goframe.org/container/gtype/index) + - [https://goframe.org/container/glist/index](https://wiki.goframe.org/container/glist/index) + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) +5. 新增 `guuid` 模块,用于通用的 `UUID` 生成: [https://goframe.org/util/guuid/index](https://wiki.goframe.org/util/guuid/index) + +### 功能改进 + +#### `net` + +1. `ghttp` + - 改进请求流程处理性能; + - `Server` 增加对 `Logger` 日志对象的配置; + - `Server` 开放了 `GetRouterMap` 方法,用于获得当前服务的路由列表信息,使得开发者可以更方便地实现自定义权限管理; + - `Server` 配置管理优化; + - `Client` 客户端对象进行了大量的改进工作; + - `Client` 客户端对象增加多文件上传功能; + - `Request` 对象增加 `GetError` 方法,用于获取当前处理错误; + - `Request` 对象增加独立的视图对象及视图变量绑定功能,使得每个请求可以独立视图管理,也可以通过中间件切换请求对象的视图对象。默认情况下该功能关闭,视图解析时使用的是 `Server` 对象的视图对象; + - 改建 `Response` 对象的 `CORS` 功能; + - 增加 `Response.WriteTplDefault` 方法,用于解析并返回默认的模板内容; + - 增加更多的单元测试用例; + - 其他改进; +2. `gipv4`/ `gipv6` + - 一些改进工作; +3. `gtcp`/ `gudp` + - 一些改进工作; + +#### `database` + +1. `gdb` + - 大量细节改进工作; + - 去掉查询数据为空时的 `sql.ErrNoRows` 错误返回,保留 `Struct`/ `Structs`/ `Scan` 方法在操作数据为空的该错误返回; + - 调试模式开启时,输出的SQL语句改进为完整的带参数的SQL,仅作参考; + - `Where` 方法增加对 `gmap` 数据类型支持,包括顺序性的 `ListMap`/ `TreeMap` 等等; + - 查询缓存方法 `Cache` 的缓存时间参数类型修改为 `time.Duration`; + - 修改 `Record`/ `Result` 的数据类型转换方法名称,原有的转换方法标记为 `deprecated`; + - `Record`/ `Result` 查询结果类型增加 `IsEmpty` 方法,用于判断结果集是否为空; + - `Record` 类型增加 `GMap` 方法,用于将查询记录转换为 `gmap` 类型; + - 增加 `Option`/ `OptionOmitEmpty` 方法,用于输入参数过滤,包括 `Data` 参数及 `Where` 参数: [https://goframe.org/database/gdb/empty](https://wiki.goframe.org/database/gdb/empty) + - 增加字段排除方法 `FieldsEx`: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - 增加日志功能特性: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - 改进数据库配置管理: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - 增加大量单元测试; +2. `gredis` + - 返回数据类型转换改进: [https://github.com/gogf/gf/issues/415](https://github.com/gogf/gf/issues/415) + - 完善单元测试; + - 其他改进; + +#### `os` + +1. `gcache` + - 需要注意了:缓存的有效时间参数从 `interface{}` 类型调整为了 `time.Duration` 类型,因此不再兼容之前的 `int` 参数类型,以保证更好的性能; +2. `gfcache` + - 由于 `gcache` 组件的缓存时间参数类型的变更,因此该组件的时间参数也变更为了 `time.Duration` 类型; +3. `gcfg` + - 增加 `Available` 方法,用以判断配置是否有效; +4. `gfile` + - 增加 `Chdir` 方法,用于工作目录切换; +5. `gtime` + - 增加 `JSON` 数据格式的 `Marshal`/ `UnMarshal` 接口实现; + +#### `container` + +1. `gmap` + - 增加 `MapStrAny` 方法,用于常见 `map` 类型的转换; + - 增加 `MapCopy` 方法,用于底层 `map` 数据复制; + - 增加 `FilterEmpty` 方法,用于 `map` 空值过滤; + - 增加 `Pop`/ `Pops` 方法,用于随机返回 `map` 中的数据项(并删除); + - 增加 `Replace` 方法,用于给定的 `map` 数据覆盖底层 `map` 数据项; + - 完善单元测试; + - 其他改进; +2. `garray` + - 增加 `Interfaces` 转换方法,返回 `[]interface{}` 类型; + - 对排序数组增加 `SetComparator` 方法用户自定义修改比较器; + - 完善单元测试; + - 其他改进; +3. `glist` + - 增加 `NewFrom` 方法,基于给定的 `[]interface{}` 变量创建链表; + - 增加 `Join` 方法,用于将链表项使用给定字符串连接为字符串返回; + - 完善单元测试; + - 其他改进; +4. `gset` + - 增加 `AddIfNotExistFunc`/ `AddIfNotExistFuncLock` 方法; + - 完善单元测试; + - 其他改进; +5. `gtree` + - 增加 `Replace` 方法,用于更新现有树的数据项; + - 其他改进; +6. `gtype` + - 一些细节改进工作,不一一列出; + - 完善基准测试、单元测试; +7. `gvar` + - 增加 `Ints`/ `Uints` 类型转换方法; + - 其他改进; + +#### `crypto` + +1. `gmd5` + - 小细节改进; +2. `gsha1` + - 小细节改进; + +#### `text` + +1. `gstr` + - 改进 `SplitAndTrim` 方法,将 `SplitAndTrimSpace` 标记为 `deprecated`; + - 增加 `TrimStr` 方法; + - 完善单元测试; + - 其他改进; + +#### `debug` + +1. `gdebug` + - 增加 `CallerFileLineShort`/ `FuncPath`/ `FuncName` 方法; + - 其他改进; + +#### `encoding` + +1. `gbase64` + - 增加 `EncodeToString`/ `EncodeFile`/ `EncodeFileToString`/ `DecodeToString` 方法; + - 完善单元测试; +2. `gjson` + - 完善单元测试; + +#### `frame` + +1. `g`/ `gins` + - [https://goframe.org/frame/g/index](https://wiki.goframe.org/frame/g/index) + - 增加 `CreateVar` 方法; + - 完善单元测试; + - 其他改进; + +#### `util` + +1. `gconv` + - 改进优化部分类型转换方法性能; + - 增加 `Uints`/ `SliceUint` 类型转换方法; + - 增加 `UnsafeStrToBytes`/ `UnsafeBytesToStr` 高性能的类型转换方法; + - 增加对 `MapStrAny` 接口方法的支持,用于常见 `map` 类型的转换; + - 其他改进; +2. `gvalid` + - 改进对中国身份证号的识别校验功能; + - 增加 `luhn` 银行卡号的校验功能; +3. `grand` + - 一些性能改进工作; + +### Bug Fix + +1. 解决 `WebSocket` 关闭时的 `hijacked` 报错问题: [https://github.com/gogf/gf/issues/381](https://github.com/gogf/gf/issues/381) +2. 解决静态文件服务时大文件的内存占用问题; +3. 修复前置 `Nginx` 后默认情况下的 `Cookie` 域名设置问题; +4. 修复 `gconv.Struct` 在属性为 `[]struct` 并且输入属性参数为空时的转换失败问题: [https://github.com/gogf/gf/issues/405](https://github.com/gogf/gf/issues/405) +5. 其他一些修复; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" new file mode 100644 index 00000000000..6affa6c9126 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.11 2020-01-14.md" @@ -0,0 +1,149 @@ +--- +slug: '/release/v1.11.0' +title: 'v1.11 2020-01-14' +sidebar_position: 5 +hide_title: true +keywords: [GoFrame,GoFrame框架,模块化,高性能,生产级,微服务,物联网,区块链,电商系统,企业级项目] +description: 'GoFrame是一款模块化、高性能、生产级的Go基础开发框架,提供缓存、日志、文件、时间、命令行等丰富核心组件,支持Web服务开发并具备热重启、多域名等特性。被广泛应用于微服务、物联网、区块链等领域,适合电商系统和银行项目。' +--- + +`GF(Go Frame)` [https://goframe.org](https://wiki.goframe.org) 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设,包括常用的核心开发组件, 如:缓存、日志、文件、时间、队列、数组、集合、字符串、定时器、命令行、文件锁、内存锁、对象池、连接池、资源管理、数据校验、数据编码、文件监控、 定时任务、数据库ORM、TCP/UDP组件、进程管理/通信、并发安全容器等等。 并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、配置管理、模板引擎等等, 支持热重启、热更新、多域名、多端口、多服务、HTTPS、Rewrite等特性。 + +`GoFrame` 有着丰富的基础模块、完善的工具链、详尽的开发文档。开源近两年以来, `GoFrame` 得到越来越多小伙伴的肯定和支持,从寂寂无名到现在被广泛应用于微服务、物联网、区块链、电商系统、银行系统等企业级的生产项目中,经历了百万级、千万级项目的考验,2019年度被码云 `gitee` 评选为 `GVP` 最有价值开源项目。 `GoFrame` 正在快速地成长中,目前保持着1-2个月迭代版本的发布规律,社区活跃,欢迎加入 `GoFrame` 大家庭。 + +最后,祝大家2020新年快乐,鼠年大吉! + +### 新特性 + +1. 新年新气象,官网文档大量更新: [https://goframe.org/index](https://wiki.goframe.org/index) +2. `GoFrame` 工具链更新: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) + - 新增 `gf run` 热编译运行命令; + - 新增 `gf docker` Docker镜像编译命令; + - 新增 `gf gen model` 强大的模型自动生成命令; + - `gf build` 命令增加对配置文件配置支持; + - 大量命令行工具改进工作; + - 新增自动代理设置特性; +3. 数据库 `ORM` 新特性: + - 增加 `prefix` 数据表前缀支持: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - 新增 `Schema` 数据库对象并改进数据库切换特性: [https://goframe.org/database/gdb/model/schema](https://wiki.goframe.org/database/gdb/model/schema) + - 新增 `WherePri` 方法,用于自动识别主键的条件方法: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - 文档及示例大量更新,覆盖95%以上的功能特性; + +### 功能改进 + +#### `container` + +1. `garray` + - 新增 `New*ArrayRange` 方法,用于初始化创建指定数值范围的数组。 + - 新增 `Iterator*` 方法,用于数组项元素回调遍历。 + - 完善单元测试。 +2. `gvar` + - 改进 `MapStrStr`、 `MapStrStrDeep` 方法实现。 + +#### `net` + +1. `ghttp` + - 改进HTTP客户端,增加对提交参数的自动 `Content-Type` 识别功能。 + - `Request` 对象增加 `Parse` 方法,用于快捷的对象转换即参数校验。 + - `Request.GetPost*` 方法全部标记为 `deprecated`,统一客户端参数提交方式为 `QueryString`, `Form`, `Body`。 + - 去掉 `Response` 模板解析时的 `Get`/ `Post` 内置变量,新增 `Query`, `Form`, `Request` 内置变量: [https://goframe.org/net/ghttp/response/template](https://wiki.goframe.org/net/ghttp/response/template) + - 改进 `Response.WriteJson*` 及 `Response.WriteXml*` 方法,增加对 `string`, `[]byte` 类型参数的支持。 + - `Server` 新增 `GetRouterArray` 方法,用于向应用层暴露并获取 `Server` 的路由列表。 + - `Server` 新增 `Use` 方法,该方法为 `BindMiddlewareDefault` 的别名,用以全局中间件的注册。 + - `Server` 新增 `RouteOverWrite` 配置项,用于控制是否在注册路由冲突时自动覆盖,默认关闭并提示。 + - `Server` 新增 `Graceful` 配置项,用于在单服务场景下控制平滑重启特性的开启/关闭,默认开启。 + - 完善单元测试。 +2. `gtcp` + - 改进简单协议下的数据包发送接收功能。 + - 将连接池默认的缓存过期时间 `30` 秒修改为 `10` 秒。 + - 完善单元测试。 + +#### `database` + +1. `gdb` + + - 新增 `As` 数据表别名方法。 + + - 改进数据表、字段的安全字符自动识别添加功能。 + + - 新增 `DB` 数据库对象切换方法。 + + - 新增 `TX` 链式操作事务支持方法。 + + - 完善单元测试。 + + + #### `os` +2. `gcfg` + + - 新增 `GetMapStrStr` 方法。 +3. `gcmd` + + - 增加参数解析的 `strict` 严格参数,默认严格解析,不存在指定参数/选项名称时则报错返回。 +4. `genv` + + - 改进 `Remove` 方法支持多个环境变量的删除。 +5. `gfile` + + - 改进 `TempDir` 临时目录获取方法,在 `*nix` 系统下默认为 `/tmp` 目录。 + - 新增 `ReadLines`, `ReadByteLines` 方法,用以按行回调读取文件内容。 + - 新增 `Copy*` 方法,用以文件/目录的拷贝,支持递归。 + - 新增 `Replace*` 方法,用以目录下的文件内容替换,支持递归。 + - 改进 `Scan*` 方法,用以检索并返回指定目录下的所有文件/目录,支持文件模式指定,支持递归。 + - 完善单元测试。 +6. `gproc` + + - 改进命令行运行方法。 + - 改进 `Shell` 命令文件检索逻辑。 + - 改进实验性的进程间通信设计。 +7. `gtime` + + - 将包方法以及 `Time` 对象的时间戳方法 `Second`, `Millisecond`, `Microsecond`, `Nanosecond` 标记为废除, 并新增 `Timestamp`, `TimestampMilli`, `TimestampMicro`, `TimestampNano` 替换。 + - 需要注意的是以上修改可能和老版本存在兼容性问题。 +8. `gview` + + - 解析功能、缓存设计改进。 + - 新增 `encode`, `decode` HTML编码/解码模板函数。 + - 新增 `concat` 字符串拼接模板函数。 + - 新增 `dump` 模板函数,功能类似于 `g.Dump` 方法。 + - 新增 `AutoEncode` 配置项,用于自动转码输出的 `HTML` 内容,常用于防止 `XSS`,默认关闭。需要注意的是该特性并不会影响 `include` 内置函数: [https://goframe.org/os/gview/xss](https://wiki.goframe.org/os/gview/xss) + - 单元测试完善。 + +#### `crypto` + +1. `gmd5` + - 增加 `MustEncrypt`, `MustEncryptBytes`, `MustEncryptString`, `MustEncryptFile` 方法。 +2. `gsha1` + - 增加 `MustEncryptFile` 方法 + +#### `encoding` + +1. `gbase64` + - 新增 `MustEncodeFile`, `MustEncodeFileToString`, `MustDecode`, `MustDecodeToString` 方法。 +2. `gjson`/ `gparser` + - 新增 `GetMapStrStr` 方法。 + - 新增 `Must*` 方法,用于指定数据格式的转换失败时产生 `panic` 错误,而不会返回 `error` 参数。 + +#### `util` + +1. `gconv` + - 改进 `Convert` 方法增加对 `[]int32`, `[]int64`, `[]uint`, `[]uint32`, `[]uint64`, `[]float32`, `[]float64` 数据类型的转换支持。 + - 改进 `String` 字符串转换方法对指针参数的支持。 + - 改进 `Map*` Map转换方法的代码结构及性能。 + - 新增 `Floats`, `Float32s`, `Float64s` 对 `[]float32`, `[]float64` 类型转换方法。 + - 新增 `Ints`, `Int32s`, `Int64s` 对 `[]int`, `[]int32`, `[]int64` 类型转换方法。 + - 新增 `Uints`, `Uint32s`, `Uint64s` 对 `[]uint`, `[]uint32`, `[]uint64` 类型转换方法。 + - 完善单元测试。 + +#### `frame` + +1. `gins` + - 所有的单例对象在获取失败时产生 `panic` 错误。 + +### Bug Fix + +1. 增加对常见错误路由格式例如 `/user//index` 的兼容支持。 +2. 修复 `gtcp`/ `gudp` 在数据接收时的间隔时间单位问题。 +3. 修复 `gfile`/ `gspath`/ `gfsnotify` 包对文件的存在性判断不严谨问题。 +4. 修复 `gproc.Kill` 方法在 `windows` 系统下的运行阻塞问题。 +5. 修复 `gstr.TrimLeftStr`/ `gstr.TrimRightStr` 在被替换字符串长度小于替换字符串长度时的数组溢出问题。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" new file mode 100644 index 00000000000..45c3a73de6e --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.12 2020-03-31.md" @@ -0,0 +1,237 @@ +--- +slug: '/release/v1.12.0' +title: 'v1.12 2020-03-31' +sidebar_position: 4 +hide_title: true +keywords: [GoFrame,高性能,模块化,基础开发框架,单元测试,缓存,日志,数据校验,数据库ORM,Web服务] +description: 'GoFrame 是一款模块化、高性能、生产级的Go基础开发框架,提供了缓存、日志、队列、容器等基础模块,并支持Web服务开发的核心组件,适合企业及团队使用。本版本更新了许多功能,包括对数据库操作、日志管理、文件操作的改进,以及新增了Swagger工具支持。' +--- + +大家好啊!久等啦! + +由于自从上次版本的发布以来,越来越多小伙伴加入了 `GoFrame` 的大家庭,并提供了许多不错的建议和反馈,这次版本对其中大部分反馈进行了处理,包括大部分的改进建议和部分新特性,因此这次的版本发布时隔了两个多月。 `GoFrame` 非常注重代码质量以及可持续维护性,这次版本也进一步对框架大部分模块的示例、注释和单元测试用例进行了完善,目前单元测试用例数量约为 `1991` 例,代码覆盖率为 `71%`,覆盖了所有模块的绝大部分主要功能。 + +`GoFrame` 框架提供了比较常用、高质量的基础开发模块,轻量级、模块化、高性能,推荐大家阅读框架源码了解更多细节。本次发布有个别的不兼容升级,往往批量替换即可,以下 `change log` 比较完善,建议升级前仔细阅读。 + +本次发布即意味下一版本开发计划的开启,欢迎更多小伙伴参与开源贡献: [https://github.com/gogf/gf/projects/8](https://github.com/gogf/gf/projects/8) + +感谢大家支持!Enjoy your `GoFrame`! + +## GoFrame + +`GoFrame(Go Frame)` 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 + +### 特点 + +- 模块化、松耦合设计; +- 模块丰富,开箱即用; +- 简便易用,易于维护; +- 社区活跃,大牛谦逊低调脾气好; +- 高代码质量、高单元测试覆盖率; +- 详尽的开发文档及示例; +- 完善的本地中文化支持; +- 更适合企业及团队使用; + +### 地址 + +- 官网: [https://goframe.org](https://wiki.goframe.org) +- 主库: [https://github.com/gogf/gf](https://github.com/gogf/gf) +- 码云: [https://gitee.com/johng/gf](https://gitee.com/johng/gf) + +## Change Log + +从 `GoFrame v1.12` 版本开始,框架要求的最低 `Golang` 运行版本为 `v1.13`,由于 `Golang` 新版本都是向后兼容的,因此推荐大家更新使用 `Golang` 新版本: [https://golang.google.cn/dl/](https://golang.google.cn/dl/) + +> 本次版本也新增了 `Swagger` 的工具及插件支持,另行独立发布。 + +### `tool chain` + +1. `gen model` 命令新增对 `pgsql/mssql/sqlite/oracle` 的模型生成支持。 +2. `gen model` 命令生成模型新增公开包变量 `Columns` 用于获得表的字段名称。 + +### `net` + +1. `ghttp` + - 注意:从该版本开始, `Server` 默认关闭了平滑重启特性。开发者可以通过相应的配置选项打开。 + - 改进 `Client.Get` 方法,增加可选的请求参数。 + - 新增 `Client` 链式操作方法: `Header`, `HeaderRaw`, `Cookie`, `ContentType`, `ContentJson`, `ContentXml`, `Timeout`, `BasicAuth`, `Ctx`: [https://goframe.org/net/ghttp/client/chain](https://wiki.goframe.org/net/ghttp/client/chain) + - 新增 `Request.GetCtx/GetCtxVar/SetCtxVar/Context` 上下文变量管理方法,用于请求内部的上下文变量特性: + - 自定义变量: [https://goframe.org/net/ghttp/request/custom](https://wiki.goframe.org/net/ghttp/request/custom) + - 上下文变量: [https://goframe.org/net/ghttp/request/context](https://wiki.goframe.org/net/ghttp/request/context) + - 新增 `Request.GetUploadFile/GetUploadFiles` 方法,以及 `UploadFile` 类型,极大简化文件上传处理逻辑: [https://goframe.org/net/ghttp/client/demo/upload](https://wiki.goframe.org/net/ghttp/client/demo/upload) + - 新增 `Request.GetPage` 方法,用于便捷地获得分页对象: + - 基本介绍: [https://goframe.org/util/gpage/index](https://wiki.goframe.org/util/gpage/index) + - 动态分页: [https://goframe.org/util/gpage/dynamic](https://wiki.goframe.org/util/gpage/dynamic) + - 静态分页: [https://goframe.org/util/gpage/static](https://wiki.goframe.org/util/gpage/static) + - Ajax分页: [https://goframe.org/util/gpage/ajax](https://wiki.goframe.org/util/gpage/ajax) + - URL模板: [https://goframe.org/util/gpage/template](https://wiki.goframe.org/util/gpage/template) + - 自定义分页: [https://goframe.org/util/gpage/custom](https://wiki.goframe.org/util/gpage/custom) + - 改进 `Response.Redirect*` 方法,增加自定义的跳转HTTP状态码参数。 + - 改进 `CORS` 特性,完善跨域功能处理,并完全遵守 `W3C` 关于 `OPTIONS` 请求方法的规范约定: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - 模板视图对象增加 `Request` 内置变量,用于模板获得请求参数。由于 `GoFrame` 框架的模板引擎采用两级缓存设计,减少 `IO` 开销的同时提升了执行效率,即使增加了大量的内置变量以及内置方法,经过大规模的性能测试,性能比其他 `WebServer` 库相同逻辑下高出 `50% - 200%` 的效率。 + - 新增 `Server` 实验性的 `Plugin` 特性。 + - 改进 `Server` 底层路由存储、检索逻辑,优化代码,提升效率。 + - 改进分组路由注册的源码位置记录功能,当路由注册冲突时能够精准定位及提示重复路由源码位置。 + - 改进 `Server` 日志处理。 + - 完善单元测试。 + +### `database` + +1. `gdb` + + - 代码重构改进,增加 `Driver` 驱动接口设计,方便开发者自定义驱动实现。新增 `Register` 包方法,用于开发者注册自定义的数据库类型驱动: [https://goframe.org/database/gdb/driver](https://wiki.goframe.org/database/gdb/driver) + - 新增 `GetArray` 接口及实现,用于获取指定字段列的数据,构造成数组返回: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - 新增 `InsertIgnore` 接口及实现,用于写入时忽略写入冲突,仅对 `mysql` 数据库类型有效: [https://goframe.org/database/gdb/model/insert-save](https://wiki.goframe.org/database/gdb/model/insert-save) + - 新增 `Schema` 接口及实现,用于动态切换并获取指定名称的数据库对象: [https://goframe.org/database/gdb/model/schema](https://wiki.goframe.org/database/gdb/model/schema) + - 新增 `FieldsStr/FieldsExStr` 模型方法,用于获取表字段,并构造成字符串返回: [https://goframe.org/database/gdb/model/fields-retrieve](https://wiki.goframe.org/database/gdb/model/fields-retrieve) + - 新增 `LockUpdate/LockShared` 模型链式操作方法,用于实现悲观锁操作: [https://goframe.org/database/gdb/model/lock](https://wiki.goframe.org/database/gdb/model/lock) + - 改进 `Where/Data` 方法对更新参数输入方式的支持,提高灵活性: + - [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - [https://goframe.org/database/gdb/model/update-delete](https://wiki.goframe.org/database/gdb/model/update-delete) + - 查询结果对象 `Result` 新增 `Array` 方法,用于获得指定字段的数值,构造成数组返回: [https://goframe.org/database/gdb/result](https://wiki.goframe.org/database/gdb/result) + - 改进 `OmitEmpty` 方法对 `Data` 输入参数的过滤,当给定的 `Data` 参数为空时间对象时,将会被过滤。 + - 增加默认的数据库连接池参数: `MaxIdleConns=10`。 + - 其他一些改进。 + - 完善单元测试。 +2. `gredis` + + - 增加/修改默认的数据库连接池参数: `MaxIdle=10`, `IdleTimeout=10s`, `MaxConnLifetime=30s`, `Wait=true`。 + - 完善单元测试。 + +### `container` + +1. 所有容器对象新增 `UnmarshalValue(interface{}) error` 接口方法实现,用于 `gconv` 转换时根据任意类型变量创建/设置对象内容, `GoFrame` 框架的所有容器对象均实现了该接口。 + +2. `garray` + + - 新增 `RemoveValue` 方法,用于根据数值检索并删除元素项。 + - 新增 `FilterNil` 方法,用于遍历并删除数组中的 `nil` 元素项。 + - 新增 `FilterEmpty` 方法,用于遍历并删除数组中的空值元素项,空值包括: `0, nil, false, "", len(slice/map/chan) == 0`。 + - 改进 `Set/Fill/InsertBefore/InsertAfter` 方法严谨性,将原有的链式操作返回值对象修改为 `error` 返回值。 + - 改进 `Get/Remove/PopRand/PopLeft/PopRight/Rand` 方法严谨性,增加 `found` 的 `bool` 返回值,标识当前方法是否有数据返回,当空数组或者操作越界时 `found` 返回值为 `false`。 + - 改变 `Rands` 方法原有逻辑,保证按照给定大小返回随机数组。 + - 完善单元测试。 +3. `gpool` + + - 调整缓存池过期时间参数类型,旧版本为 `int` 类型表示毫秒,新版本为 `time.Duration` 类型使得时间控制更灵活。 + - 内部代码优化,性能改进。 + - 完善单元测试。 + - 完善注释。 + +### `os` + +1. `glog` + + - 增加 `Rotation` 日志滚动切分特性,新增按照文件大小或过期时间进行日志切分,并支持切分文件数量限制、对日志文件进行自动压缩、可自定义压缩级别( `1-9`)、支持对切分文件过期时间清理: [https://goframe.org/os/glog/rotate](https://wiki.goframe.org/os/glog/rotate) + - 新增 `LevelPrefixes` 特性,支持对日志级别的前缀名称进行自定义: [https://goframe.org/os/glog/level](https://wiki.goframe.org/os/glog/level) + - 新增 `SetLevelStr` 方法,并增加按照字符串进行日志级别配置的特性: + - [https://goframe.org/os/glog/level](https://wiki.goframe.org/os/glog/level) + - [https://goframe.org/os/glog/config](https://wiki.goframe.org/os/glog/config) + - 新增 `SetDefaultLogger` 包方法,用于设置全局默认的 `Logger` 对象。 +2. `gres` + + 1. 改进资源内容编码设计,采用新的压缩算法,减少资源文件大小,例如原本 `15MB` 的网站静态资源文件( `css/js/html` 等),资源文件打包后约为 `4MB` 左右: [https://goframe.org/os/gres/index](https://wiki.goframe.org/os/gres/index) + 2. 注意:该改进与旧版本无法兼容,需要重新打包原有的资源文件。 + 3. 完善单元测试。 +3. `gcfg` + + - 去掉配置对象属性的原子并发安全控制,改为普通变量类型。由于配置管理是最常用模块之一,因此确保高效的设计及方法实现。 + - 单例对象做较大调整:为方便多文件场景下的配置文件调用,简便使用并提高开发效率,因此当给定的单例名称对应的 `toml` 配置文件在配置目录中存在时,将自动设置该单例对象的默认配置文件为该文件。例如: `g.Cfg("redis")` 获取到的单例对象将会默认去检索并设置默认的配置文件为 `redis.toml`,当该文件不存在时,则使用默认的配置文件( `config.toml`): [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - 完善单元测试。 +4. `gview` + + - 新增 `concat` 内置字符串拼接方法: [https://goframe.org/os/gview/function/buildin](https://wiki.goframe.org/os/gview/function/buildin) + - 完善单元测试。 +5. `gfile` + + - 改进 `SelfPath` 获取当前执行文件路径方法,提高执行效率。 + - 改进 `Join` 文件路径连接方法,防止多余的路径连接符号。 + - 改建 `GetContents` 文件内容获取方法执行效率,降低内存占用。 + - 新增 `StrToSize` 方法,用于将大小字符串转换为字节数字,大小字符串例如 `10m`, `5KB`, `1.25Gib` 等。 + - 新增 `ReadLines/ReadByteLines` 方法,用于按行读取指定文件内容,并给定读取回调函数。 + - 完善单元测试。 +6. `gtime` + + - 改进 `gtime.Time` 对象实现,统一字符串打印时间格式为 `Y-m-d H:i:s`,如: `2020-01-01 12:00:00`。 +7. `gcmd` + + - 命令行解析方法增加 `strict` 参数,用于设置当前解析是否严格解析,严格解析下如果给定了非法的选项,将会停止解析并返回错误。默认情况下为非严格解析。 +8. `genv` + + - 改进 `Remove` 删除环境变量键值对方法,增加对多个键值对环境变量的删除。 +9. `gfpool` + + - 改进代码实现,提高效率。 + - 完善单元测试。 +10. `gfsnotify` + + - 改进包初始化方法,当系统原因引起默认 `Watcher` 对象创建失败时,直接 `panic`。 +11. `gproc` + + - 改进 `SearchBinaryPath` 方法。 + - 改进 `Process.Kill` 方法在 `windows` 及 `*niux` 平台下不同表现的处理。 + +### `encoding` + +1. `gjson` + - 代码改进、完善注释、新增大量代码示例。 + - 文档更新: + - 基本介绍: [https://goframe.org/encoding/gjson/index](https://wiki.goframe.org/encoding/gjson/index) + - 对象创建: [https://goframe.org/encoding/gjson/object](https://wiki.goframe.org/encoding/gjson/object) + - 层级访问: [https://goframe.org/encoding/gjson/pattern](https://wiki.goframe.org/encoding/gjson/pattern) + - Struct转换: [https://goframe.org/encoding/gjson/conversion-struct](https://wiki.goframe.org/encoding/gjson/conversion-struct) + - 动态创建修改: [https://goframe.org/encoding/gjson/dataset](https://wiki.goframe.org/encoding/gjson/dataset) + - 数据格式转换: [https://goframe.org/encoding/gjson/conversion-format](https://wiki.goframe.org/encoding/gjson/conversion-format) + +### `frame` + +1. `g` + - 新增 `IsNil` 方法,用于判断当前给定的变量是否为 `nil`,该方法有可能会使用到反射来实现判断。 + - 新增 `IsEmpty` 方法,用于判断当前给定的变量是否为 `空`,该方法优先使用断言判断但有可能会使用到反射来实现判断。空值包括: `0, nil, false, "", len(slice/map/chan) == 0`。 + - 标记废弃 `SetServerGraceful` 方法,转而统一交给 `Server` 的配置来管理。 +2. `gins` + - 改进代码结构,方便维护。 + - 改进、完善单元测试。 +3. `gmvc` + - 新增 `M` 类型,为 `*gdb.Model` 的别名简称,用于工具链自动生成模型中的 `M` 属性。 + +### `text` + +1. `gstr` + - 新增 `HasPrefix/HasSuffix` 方法。 + - 新增 `OctStr` 方法,用于将八进制字符串转换为其对应的 `unicode` 字符串,例如转换为中文。常用于 `gRPC` 底层通信编码中。 + - 完善单元测试。 + +### `debug` + +1. `gdebug` + - 改进代码结构,方便维护。 + - 新增 `TestDataPath` 方法,用于当前包单元测试中获得当前包中 `testdata` 目录的绝对路径。 + +### `util` + +1. `gconv` + + - 改进 `String` 字符串转换方法,增加对 `time.Time/*time.Time/gtime.Time` 类型的内置支持。 + - 改进 `Map/Struct` 转换方法,增加对一些特殊场景的细节处理。经过大规模的使用,大量的反馈改进,不断完善了细节。 + - 改进 `Struct` 转换方法,增加对 `UnmarshalValue(interface{}) error` 接口的支持。 + - 完善单元测试。 +2. `grand` + + - 注意:不兼容调整,原有的 `Str` 方法更名为 `S` 方法,用以获取指定长度的随机字符串、数字,并增加 `symbol` 参数,指定是否可以随机返回特殊可见字符。 + - 新增 `Str` 方法,用于从指定的字符串字符中随机获取指定长度的字符串。该方法同时支持 `unicode` 字符串,例如:中文: [https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index) + - 新增 `Symbols` 方法,用于随机返回指定场孤独的特殊可见字符: [https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index) + - 完善单元测试。 +3. `gvalid` + + - 长度校验规则增加对 `unicode` 字符串的支持,例如:中文。 + +## Bug Fix + +1. 修复 `Server` 的视图对象配置失效问题。 +2. 修复 `Server` 中间件在中间件 `panic` 情况下,忽略 `Middleware.Next` 方法控制,导致鉴权中间件失效的问题。 +3. 修复 `gudp.Server` 在请求包大小超过 `64bytes` 时的断包问题,并调整默认缓冲区大小为 `1024byte`,开发者可自定义缓冲区大小。 +4. 修复 `gfile.MTimeMillisecond` 方法返回错误的文件修改毫秒时间戳。 +5. 修复 `gconv.Int64` 对负数转换的支持。 +6. 其他一些修复。 +7. 详见: [https://github.com/gogf/gf/issues?q=label%3Abug](https://github.com/gogf/gf/issues?q=label%3Abug) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" new file mode 100644 index 00000000000..40ef51cb851 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.13 2020-06-10.md" @@ -0,0 +1,226 @@ +--- +slug: '/release/v1.13.0' +title: 'v1.13 2020-06-10' +sidebar_position: 3 +hide_title: true +keywords: [GoFrame,Go开发框架,模块化设计,Web服务组件,缓存管理,数据库ORM,TCP/UDP组件,中间件支持,配置管理,数据校验] +description: 'GoFrame是一个模块化、高性能的Go语言开发框架,提供丰富的基础和Web服务开发组件,如缓存、日志、数据库ORM、TCP/UDP组件等。框架设计松耦合,易于维护并具备高代码质量,适合团队和企业应用的开发需求。' +--- + +## GoFrame + +`GF(Go Frame)` 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如:Router、Cookie、Session、Middleware、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、TLS/HTTPS、Rewrite等特性。 + +### 特点 + +- 模块化、松耦合设计; +- 模块丰富、开箱即用; +- 简便易用、易于维护; +- 高代码质量、高单元测试覆盖率; +- 社区活跃,大牛谦逊低调脾气好; +- 详尽的开发文档及示例; +- 完善的本地中文化支持; +- 设计为团队及企业使用; + +### 发展 + +`GoFrame` 开始得比较早, `2011` 年始于北京一个智能物联网平台项目,那时还没有这么多物联网的现行标准, `Go` 的标准库以及生态也未如此丰富。 `2017` 年的时候 `GoFrame` 才开始发布测试版, `2018` 年 `1024` 程序员节日的时候才发布 `v1.0` 正式版,为 `Go` 生态发展添砖加瓦。开源以来快速迭代、发展成长,广受开发者和企业的青睐,也有许多的开发者加入了贡献行列。 `GoFrame` 原本是为开发团队设计的,因此她的开发效率和可维护性做得非常好,有着很高的代码质量以及丰富的单元测试和示例,并且 `GoFrame` 是目前中文化文档做的最好的 `Golang` 开发框架。 + +## Change Log + +1. 应多数开发者的要求,框架要求的最低 `Golang` 运行版本降级为了 `v1.11`。 +2. 新增 `GoFrame` 视频教程地址: + - bilibili: [https://www.bilibili.com/video/av94410029](https://www.bilibili.com/video/av94410029) + - 西瓜视频: [https://www.ixigua.com/pseries/6809291194665796100/](https://www.ixigua.com/pseries/6809291194665796100/) +3. 将不常用的 `guuid` 模块迁移到 github.com/gogf/guuid 作为社区模块维护,保持 `gf` 主仓库的轻量级。 +4. 新增 `guid` 模块,用于高效轻量级的唯一字符串生成: [https://goframe.org/util/guid/index](https://wiki.goframe.org/util/guid/index) + +### `tool chain` + +1. 工具链更新: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) +2. 新增 `gf env` 命令,更优雅地查看当前 `Golang` 环境变量信息。 +3. 新增 `gf mod path` 命令,用于将当前 `go modules` 包拷贝到 `GOPATH` 中,以便使用原始的 `GOPATH` 方式开发项目。 +4. 对现有 `cli` 命令进行了一些改进,提高使用体验;预编译二进制版本在部分平台下提供了 `upx` 压缩,使得下载的文件更小。 + +### `container` + +1. `garray` + - [https://goframe.org/container/garray/index](https://wiki.goframe.org/container/garray/index) + - 简化数组使用方式,支持类似于 `var garray.Array` 的变量定义使用方式; + - 增加 `Walk` 方法,用于自定义的数组元素处理方法; + - 增加 `ContainsI` 方法,用于大小写忽略匹配的数组元素项存在性查找; + - 完善单元测试,代码覆盖率 `94%`; + - 代码改进,提高性能; + - 修复一些问题; +2. `gchan` + - 由于该封装包实际意义不是很大,因此从主框架中删除; +3. `glist` + - [https://goframe.org/container/glist/index](https://wiki.goframe.org/container/glist/index) + - 简化链表使用方式,支持类似于 `var glist.List` 的变量定义使用方式; + - 完善单元测试,代码覆盖率 `99%`; +4. `gmap` + - [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) + - 简化 `Map` 使用方式,支持类似于 `var gmap.Map` 的变量定义使用方式; + - 完善单元测试,代码覆盖率 `81%`; + - 代码改进,提高性能; +5. `gset` + - [https://goframe.org/container/gset/index](https://wiki.goframe.org/container/gset/index) + - 简化集合使用方式,支持类似于 `var gset.Set` 的变量定义使用方式; + - 增加 `Walk` 方法,用于自定义的集合元素处理方法; + - 完善单元测试,代码覆盖率 `90%`; + - 代码改进,提高性能; +6. `gtree` + - [https://goframe.org/container/gtree/index](https://wiki.goframe.org/container/gtree/index) + - 简化树型使用方式,支持类似于 `var gtree.BTree` 的变量定义使用方式; + - 完善单元测试,代码覆盖率 `90%`; +7. `gvar` + - [https://goframe.org/container/gvar/index](https://wiki.goframe.org/container/gvar/index) + - 完善单元测试,代码覆盖率 `69%`; + - 代码组织结构调整,提高维护性; + - 代码改进,提高性能; + +### `database` + +1. `gdb` + - 增加 `Transaction(f func(tx *TX) error) (err error)` 接口方法,用于通过闭包实现事务封装处理: [https://goframe.org/database/gdb/transaction](https://wiki.goframe.org/database/gdb/transaction) + - 去掉不常用的 `From` 接口方法,改进 `Table` 及 `Model` 方法的参数为不定参数,并支持通过不定参数传递表别名: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - 增加 `DryRun` 特性,支持空跑时只执行查询不执行写入/更新/删除操作: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - 增加 `create_at`, `update_at` 写入时间、更新时间字段自动填充特性: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - 增加 `delete_at` 软删除特性: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - 增加 `Having` 链式操作方法,用于 `having` 条件查询: [https://goframe.org/database/gdb/model/select](https://wiki.goframe.org/database/gdb/model/select) + - `Result` 结果对象增加 `Chunk` 方法,用于自定义的数据分批处理: [https://goframe.org/database/gdb/result](https://wiki.goframe.org/database/gdb/result) + - 改进 `Schema` 数据库运行时切换特性; + - 改进对 `pgsql`, `mssql`, `sqlite`, `oracle` 数据库字段类型的支持; + - 进一步完善单元测试; + - 代码组织结构调整,提高维护性; + - 代码改进,提高性能; +2. `gredis` + - 增加 `MaxActive` 连接池参数默认配置为 `100`,限制默认的连接数量; + - 改进 `Conn` 连接对象的 `Do` 方法,支持对 `map/slice/struct` 类型进行自动的 `json.Marshal` 处理,注意获取数据时使用 `DoVar` 方法获取: [https://goframe.org/database/gredis/usage](https://wiki.goframe.org/database/gredis/usage) + - 完善单元测试,代码覆盖率 `72%`; + +### `net` + +1. `ghttp` + + - 增加 `Prefix` 及 `Retry` 客户端链式操作方法; + - 增加客户端原始请求打印特性: [https://goframe.org/net/ghttp/client/demo/dump](https://wiki.goframe.org/net/ghttp/client/demo/dump) + - 增加 `ClientMaxBodySize` 的服务端配置,用于限制客户端提交的 `Body` 大小,默认为 `8MB`;在涉及到上传的Server中需要增加该配置的大小,在配置文件中指定对应的大小即可,如 `ClientMaxBodySize="100MB"`: [https://goframe.org/net/ghttp/config](https://wiki.goframe.org/net/ghttp/config) + - 改进 `SessionId` 生成的随机性,提高 `Session` 安全性: [https://goframe.org/os/gsession/index](https://wiki.goframe.org/os/gsession/index) + - 改进 `ghttp.Server` 实现了标准库的 `http.Handler` 接口,便于与其他第三方的服务如 `Prometheus` 进行代码集成; + - 其他大量的代码细节改进工作,提高性能及持久维护性; + - 完善单元测试,代码覆盖率 `61%`; +2. `gipv4` + + - 增加 `GetIpArray` 方法,用于获取当前主机的所有IPv4地址; + - 增加 `GetMacArray` 及 `GetMac` 方法,用于获取当前主机的 `MAC` 地址信息; + - 修改 `IntranetIP` 方法名称为 `GetIntranetIp`,修改 `IntranetIPArray` 方法名称为 `GetIntranetIpArray`; + +### `encoding` + +1. `gjson` + - 新增 `GetMaps` 获取 `JSON` 内部节点变量方法; + - 改进 `NewWithTag` 方法对 `map/struct` 的处理; + - 完善单元测试,代码覆盖率 `77%`; +2. `gyaml` + - 升级依赖的第三方 `yaml` 解析包,解决了 `map[interface{}]interface{}` 转换问题; + +### `error` + +1. `gerror` + - 新增 `NewfSkip` 方法,用于创建 `skip` 指定堆栈的错误对象; + - 放开框架所有的堆栈链路打印,展示错误时真实的链路调用详情; + +### `os` + +1. `gcache` + + - 增加 `GetVar` 方法,用于获得可以便捷转换为其他数据类型的”泛型”变量; + - 标记 `Removes` 方法废弃,改进 `Remove` 方法参数为不定参数,统一使用 `Remove` 方法删除单个/多个键值对; + - 完善单元测试,代码覆盖率 `96%`; +2. `genv` + + - 增加 `GetVar` 方法,用于获得可以便捷转换为其他数据类型的”泛型”变量; +3. `gfile` + + - 改进 `CopyDir/CopyFile` 复制目录/文件方法; + - 新增 `ScanDirFunc` 方法,用于支持自定义处理回调的目录检索; + - 完善单元测试,代码覆盖率 `64%`; +4. `glog` + + - 增加支持 `Context` 上下文变量的日志打印特性: [https://goframe.org/os/glog/context](https://wiki.goframe.org/os/glog/context) +5. `gres` + + - 改进打包特性,增强生成二进制文件及Go文件的压缩比,比旧版本增加 `20%` 压缩率,使得编译生成的二进制文件体积更小; + - 代码结构改进,提高执行效率及可持久维护性; +6. `gsession` + + - 改进 `SessionId` 默认生成方法,采用 `guid.S` 方法生成; + - 增加 `SetId` 及 `SetIdFunc` 方法,用于自定义 `SessionId` 及自定义的 `SessionId` 生成方法; + +### `frame` + +1. `g` + - 新增 `g.Table` 方法,用于快速创建数据库模型操作对象; + +### `i18n` + +1. `gi18n` + - 新增 `GetContent` 方法,用于获取指定 `i18n` 关键字为转译内容; + - 改进代码细节,提高性能和持久可维护性; + - 完善单元测试,代码覆盖率 `74%`; + +### `test` + +1. `gtest` + - 增加 `AssertNQ` 断言方法,用于强类型的不相等判断; + +### `text` + +1. `gstr` + - 增加 `SubStrRune` 方法,用于支持 `unicode` 的字符串截取; + - 增加 `StrLimitRune` 方法,用于支持 `unicode` 的字符串截断隐藏; + - 增加 `LenRune` 方法,用于替换 `RuneLen` 方法,统一方法命名风格; + - 增加 `PosRune/PosIRune/PosRRune/PosRIRune` 方法,用于支持 `unicode` 的字符串左右位置查找; + - 增加 `CompareVersionGo` 方法,用于 `Golang` 风格的版本号大小比较; + - 完善单元测试,代码覆盖率 `75%`; + +### `util` + +1. `gconv` + + - 改进 `Convert` 转换方法,支持常见 `map` 类型的转换; + - 改进类型转换过程中异常错误的捕获,通过 `error` 返回; + - 其他一些细节改进; + - 完善单元测试,代码覆盖率 `63%`; +2. `grand` + + - 增加 `B` 方法,用于获得随机的二进制数据; + - 改进代码底层实现,部分接口性能提高 `50%`; + - 完善单元测试,代码覆盖率 `74%`; +3. `guid` + + - 新增 `guid` 模块,用于高效轻量级的唯一字符串生成: [https://goframe.org/util/guid/index](https://wiki.goframe.org/util/guid/index) +4. `gutil` + + - 增加 `MapContains` 方法,用于判断map中是否包含指定键名; + - 增加 `MapDelete` 方法,用于删除map中指定的键名,可以为多个键名; + - 增加 `MapMerge` 方法,用于合并两个map; + - 增加 `MapMergeCopy` 方法,用于拷贝多个map; + - 增加 `MapContainsPossibleKey` 方法,用于查找指定键名,忽略大小写及字符 `'-'/'_'/'.'/' '`; +5. `gvalid` + + - 所有默认的错误提示改为了英文; + - 错误提示的配置改为了通过 `i18n` 来配置实现,以便支持国际化: [https://goframe.org/util/gvalid/message](https://wiki.goframe.org/util/gvalid/message) + - 身份证号规则名称从 `id-number` 改为了 `resident-id`; + - 银行卡号规则名称从 `luhn` 改为了 `bank-card`; + - 完善单元测试,代码覆盖率 `96%`; + +### Bug Fix + +1. 修复 `gcompress` 的多文件 `zip` 压缩问题; +2. 修复 `ghttp.Client` 获取返回的过期 `Cookie` 的问题; +3. 修复 `gres.File` 对于 `http.File` 接口的实现细节; +4. 修复 `garray.Pop*` 方法的边界问题; +5. 修复 `gres` 中 `Readdir` 方法参数为 `0` 时报错的问题; +6. 其他一些修复: [https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug](https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" new file mode 100644 index 00000000000..3921678b91a --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.14 2020-10-27.md" @@ -0,0 +1,193 @@ +--- +slug: '/release/v1.14.0' +title: 'v1.14 2020-10-27' +sidebar_position: 2 +hide_title: true +keywords: [GoFrame,Go框架,高性能,Web服务,模块化,缓存,ORM,开发工具,社区支持,本地化] +description: 'GoFrame是一款模块化、高性能的Go开发框架,提供丰富的基础开发模块和Web服务核心组件,包括缓存、日志、数据校验、数据库ORM等。框架支持热重启、TLS/HTTPS以及Redis适配器,具备高代码质量和完善的中文支持,适合团队和企业使用。' +--- + +## GoFrame + +`GoFrame` 是一款模块化、高性能、生产级的Go基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、对象池、配置管理、资源管理、数据校验、数据编码、定时任务、数据库ORM、TCP/UDP组件、进程管理/通信等等。并提供了Web服务开发的系列核心组件,如: `Router`、 `Cookie`、 `Session`、 `Middleware`、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、 `TLS/HTTPS`、 `Rewrite` 等特性。 + +### 特点 + +- 模块化、松耦合设计; +- 模块丰富、开箱即用; +- 简便易用、易于维护; +- 高代码质量、高单元测试覆盖率; +- 社区活跃,大牛谦逊低调脾气好; +- 详尽的开发文档及示例; +- 完善的本地中文化支持; +- 设计为团队及企业使用; + +### 支持我们 + +OSC最佳开源项目评选开始了,如果您喜欢 `GoFrame`,欢迎为 `GoFrame` 投上您宝贵的一票🙏 [https://www.oschina.net/p/goframe](https://www.oschina.net/p/goframe) + +## Change Log + +由于 `GoFrame` 是模块化设计,因此每个版本的更新记录都会以模块的形式进行介绍。 + +重要更新: + +1. 将框架内所有的 `json` 操作从标准库替换为 `json-iterator/go`,提高操作效率。 +2. 缓存模块重构底层设计,增加适配器设计模式,并增加内存及 `Redis` 适配器支持。其中内存适配器默认核心模块提供, `Redis` 适配器由社区模块提供: [https://goframe.org/os/gcache/adapter](https://wiki.goframe.org/os/gcache/adapter) +3. 增加可自定义的校验规则注册特性: [https://goframe.org/util/gvalid/customrule](https://wiki.goframe.org/util/gvalid/customrule) +4. `Web Server` 增加所有配置项示例: [https://goframe.org/net/ghttp/config/example](https://wiki.goframe.org/net/ghttp/config/example) +5. `ORM` 新增基于 `Redis` 的 `SQL` 缓存适配器: [https://goframe.org/database/gdb/model/cache](https://wiki.goframe.org/database/gdb/model/cache) +6. `ORM` 新增模型关联实验特性: [https://goframe.org/database/gdb/model/association](https://wiki.goframe.org/database/gdb/model/association) +7. `ORM` 改进时间自动更新特性增加自定义时间字段: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) +8. 错误处理模块新增 `Current` 及 `Next` 方法: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) + +### `net` + +1. `ghttp` + - `Client` + - 增加 `GetVar/PutVar/PostVar` 等 `*Var` 请求方法,用于发起 `HTTP` 请求获取内容之后直接返回泛型对象,方便类型转换,特别是针对于返回 `XML/JSON` 的结果处理将会更加简便: [https://goframe.org/net/ghttp/client/demo/index](https://wiki.goframe.org/net/ghttp/client/demo/index) + - 增加 `SetProxy/Proxy` 方法,用于设置客户端代理,支持 `HTTP/Socket5` 代理类型: [https://goframe.org/net/ghttp/client/demo/proxy](https://wiki.goframe.org/net/ghttp/client/demo/proxy) + - 增加 `SetRedirectLimit/RedirectLimit` 方法,用于设置页面跳转数量限制。 + - `Request` + - 增加 `ParseQuery`, `ParseForm` 方法,用于解析指定类型的参数,并绑定到给定的对象。 + - 增加 `GetHeader` 方法,用于获取指定 `Header` 参数。 + - 增加 `GetRemoteIp` 方法,用于获取请求客户端IP。在IP白名单限制时应当使用 `GetRemoteIp` 而不是 `GetClientIp` 进行判断,后者可以通过 `Header` 伪造。 + - 增加 `ReloadParam` 方法,往往用在中间件处理中,当中间件修改了请求参数,需要通过调用该方法重新解析一下请求参数。 + - 增加 `GetRouterMap` 方法,用于获得所有的路由参数返回为 `map`。 + - `Response` + - 将 `Output` 方法名称改为 `Flush`,用于将缓冲区的数据写入到客户端数据流中。 + - `Server` + - `Server` 增加所有配置项示例: [https://goframe.org/net/ghttp/config/example](https://wiki.goframe.org/net/ghttp/config/example) + - 增加 `SessionCookieOutput` 配置,用于控制是否输出 `SessionId` 到 `Cookie` 中,默认为开启。 + - 改进路由解析,增加对 `URI` 带有重复的 `/` 符号的支持。 + - `Pprof` 功能路由支持 `Domain` 绑定。 + - 其他一些细节改进。 + - `Cookie` + - 增加 `SetHttpCookie` 方法,用于根据标准库 `http.Cookie` 对象设置 `Cookie`。 + - 其他一些功能改进 + +### `database` + +1. `gdb` + + - 新增模型关联实验特性: [https://goframe.org/database/gdb/model/association](https://wiki.goframe.org/database/gdb/model/association) + - 改进时间自动更新特性增加自定义时间字段: [https://goframe.org/database/gdb/model/auto-time](https://wiki.goframe.org/database/gdb/model/auto-time) + - 新增基于 `Redis` 的 `SQL` 缓存适配器: [https://goframe.org/database/gdb/model/cache](https://wiki.goframe.org/database/gdb/model/cache) + - 新增对输入参数的键名-字段名自动识别映射特性: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - 新增 `DB.HasTable` 方法,用于判断是否当前数据库存在指定数据表。 + - 新增 `Model.HasField` 方法,用于判断是否当前数据表存在指定字段。 + - 新增 `Model.ScanList` 方法,用于智能地将当前 `struct`/ `slice` 绑定到指定的 `list` 对应属性上。 + - 新增 `Result.MapKeyValue` 方法,用于将当前 `Result` 转换为 `map[string]Value` 类型。 + - 新增 `Result.IsEmpty/Len/Size/ScanList` 方法。 + - 增加 `ListItemValues` 及 `ListItemValuesUnique` 方法,用于自动获取 `list` 中指定名称的键值或属性值,构成 `slice` 返回。 + - `SQL` 日志内容增加分组名称打印。 + - 改进 `DataToMapDeep` 方法。 + - 其他一些细节改进工作。 +2. `gredis` + + - 新增 `TLS` 特性支持,并支持配置文件配置, [https://goframe.org/database/gredis/config](https://wiki.goframe.org/database/gredis/config) + +### `container` + +1. `gvar` + - 增加 `Scan` 及 `ScanDeep` 方法,用于 `struct`/ `slice` 自动识别转换。 + - 增加 `ListItemValues` 及 `ListItemValuesUnique` 方法,用于自动获取 `list` 中指定名称的键值或属性值,构成 `slice` 返回。 + - 增加 `MapStrAny` 接口实现方法。 + +### `os` + +1. `gcache` + + - 增加 `GetVar` 方法,用于获取缓存数据并返回为泛型对象。 + - 增加 `Update` 方法,用于仅修改缓存数值,不修改缓存过期时间。 + - 增加 `UpdateExpire` 方法,用于仅修改缓存过期时间,不修改缓存数值。 + - 重构底层设计,增加适配器设计模式,并增加内存及 `Redis` 适配器支持。其中内存适配器默认核心模块提供, `Redis` 适配器由社区模块提供: [https://goframe.org/os/gcache/adapter](https://wiki.goframe.org/os/gcache/adapter) + - 注意,本次模块的修改会有部分方法不兼容,部分方法增加了 `error` 参数返回,升级时请注意查看。编译时将不会通过。 + - 其他一些功能改进。 +2. `gfile` + + - 增加 `ScanDirFileFunc` 方法,用于自定义函数处理的递归目录文件遍历。 + - 改进 `Scan*` 方法,增加递归层级限制,默认层级限制为 `100000`. +3. `gfsnotify` + + - 去掉模块初始化时的 `Watcher` 对象创建,调整为运行时按需创建,并且增加了并发安全控制。 +4. `grpool` + + - 增加 `AddWithRecover` 方法,用于添加异步任务时给定一个 `recover` 处理方法,当任务 `panic` 时交由该 `recover` 方法处理,防止异步任务 `panic` 引起整个进程崩溃。 \> 这里解决的痛点是 `recover` 只能捕获到当前 `goroutine` 的 `panic`,因此只能在创建异步任务的时候指定 `recover` 处理方法。 +5. `gtime` + + - 增加 `ParseDuration` 方法,增加了对时间单位 `d` 的支持,表示天。 + - 改进 `New` 方法,支持通过字符串、时间戳、 `time.Time` 对象创建 `gtime.Time` 对象, [https://goframe.org/os/gtime/time](https://wiki.goframe.org/os/gtime/time) + - 改进 `Add/AddStr/ToLocation/ToZone/UTCLocal/AddDate/Truncate/Round` 方法,这些方法调用时,不再修改当前对象本身,而是创建并返回一个新的 `gtine.Time` 对象,以便保证和标准库 `time.Time` 的逻辑一致,防止混淆。 + - 其他一些细节改进。 +6. `gtimer` + + - 增加 `Reset` 方法,用于重置定时任务的计时。 +7. `gfcache` + + - 去掉了该模块,该模块的功能作用不是特别大。 + +### `debug` + +1. `gdebug` + - 新增 `GoroutineId` 方法,用于获取当前执行的 `goroutine id`,仅作调试使用。 + +### `encoding` + +1. `gjson` + + - 新增 `GetScan/GetScanDeep` 方法。 + - 新增 `ToScan/ToScanDeep` 方法。 + - 新增 `LoadContentType` 方法,用于根据指定类型的内容创建 `Json` 操作对象。 + - 新增 `IsValidDataType` 方法,用于判断给定的数据类型是否支持解析。 + - 其他一些改进。 + - 单元测试完善。 +2. `gcompress` + + - 新增 `GzipFile/UnGzipFile` 基于 `gzip` 压缩算法的文件压缩/解压。 + +### `i18n` + +1. `gi18n` + - 新增 `TranslateFormat/TranslateFormatLang/Tf/Tfl` 方法: [https://goframe.org/i18n/gi18n/index](https://wiki.goframe.org/i18n/gi18n/index) + +### `text` + +1. `gstr` + - 增加 `SnakeFirstUpperCase` 方法,用于在字母大写前增加连接符,并不会处理数字,例如: `SnakeFirstUpperCase("RGBCodeMd5")` 将会返回 `rgb_code_md5`。 + +### `util` + +1. `gconv` + + - 增加对指针基本类型的转换支持。 + - 增加 `Scan/ScanDeep` 方法,用于自动识别转换 `Struct/[]Struct`。 + - 改进 `MapDeep` 方法的层级递归处理。 + - 其他一些细节改进,性能改进。 +2. `gutil` + + - 增加 `ListItemValues` 及 `ListItemValuesUnique` 方法,用于自动获取 `list` 中指定名称的键值或属性值,构成 `slice` 返回。 + - 增加 `ItemValue` 方法,用于获取指定 `map/*map/struct/*struct` 类型的键值/属性值。 + - 增加 `MapOmitEmpty` 方法,用于过滤 `map` 中的空值。 + - 增加 `SliceDelete` 方法,用于数组项删除。 + - 增加 `Try` 方法,通过闭包执行给定的方法,如果方法产生 `panic` 则该方法返回 `error`,否则返回 `nil`。 + - 改进 `TryCatch(try func(), catch ...func(exception interface{}))` 为 `TryCatch(try func(), catch ...func(exception error))` +3. `gvalid` + + - 增加自定义规则特性,开发者可注册自定义的校验规则: [https://goframe.org/util/gvalid/customrule](https://wiki.goframe.org/util/gvalid/customrule) + - 其他一些功能改进。 + +### `error` + +1. `gerror` + - 新增 `Current` 方法,用于获取当前错误层级的 `error` 接口对象。 + - 新增 `Next` 方法,用于获取层级错误的下一级错误 `error` 接口对象。当下一层级不存在时,返回 `nil`。 + - 文档更新: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) + +### Bug Fix + +1. 修复 `garray` 模块的 `Unique` 方法问题。 +2. 修复 `glog` 中定时器懒初始化时的 `goroutine` 泄露问题。 +3. 修复 `gstr` 中名称 `Case` 转换相关方法在名称中带有数字+特殊字符时的名称转换问题。 +4. 修复 `ghttp` 模块中的 `CORS` 跨域设置 `Header` 细节问题。 +5. 其他BUG修复: [https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed](https://github.com/gogf/gf/issues?q=is%3Aissue+label%3Abug+is%3Aclosed) \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" new file mode 100644 index 00000000000..f5e9bea16b1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.15 2020-12-31.md" @@ -0,0 +1,128 @@ +--- +slug: '/release/v1.15.0' +title: 'v1.15 2020-12-31' +sidebar_position: 1 +hide_title: true +keywords: [GoFrame,Golang,企业级开发框架,网页开发,高性能框架,模块化设计,社区活跃,开源项目,单元测试,数据库组件] +description: 'GoFrame v1.15 版本提供了一系列的新功能和改进,为企业级 Golang 开发提供了高效、模块化的框架支持。此次更新涵盖了 HTTP 客户端改进、数据库 ORM 特性增强、错误码支持等诸多方面。GoFrame 通过高代码质量、丰富的开发文档以及活跃的社区支持,为开发者提供便捷的项目开发环境,广泛应用于各类互联网公司。' +--- + +大家好呀!自上次发布到现在,从初秋到深冬,我们也完全冇闲着哟,我们这次带来了爱心满满的 `GoFrame v1.15` 版本。此外,还有两件事: + +- `GoFrame` 被 `OSC` 开源中国评选为了 `2020` 年度 `TOP30` 的开源项目: [https://www.oschina.net/question/2918182\_2320114](https://www.oschina.net/question/2918182_2320114) ,感谢大家的认可与支持!同时 `GoFrame` 也是 `Gitee GVP` [最有价值项目](https://gitee.com/johng/gf)。 +- `GoFrame` 官网船新改版,里挤需体验三番钟,里造会干我一样,爱象介款框架: [https://goframe.org](https://wiki.goframe.org) 。感谢 [Atlassian](https://www.atlassian.com/) 的赞助,提供的全产品线正版授权码! + +经过多年的潜心修炼和稳定发展, `GoFrame` 已经逐步成长为了一款企业级的 `Golang` 基础开发框架,她提供了项目开发规范、开发工具链、完善的基础模块、丰富的开发文档、高代码质量以及活跃的社区。为保证框架质量,我们为各个组件进行了大量的单元测试以保证逻辑的正确( `2534` 例测试单元, `9097` 项测试断言),同时维护了高质量的文档,至今,已有很多的大型/中小型互联网公司在生产环境使用 `GoFrame`。 + +开源不易,有你们的理解和支持,幸福满满!感谢所有参与项目开发的小伙伴们,爱你们! `GoFrame, YES!` + +## `GoFrame` + +`GF(Go Frame)` 是一款模块化、高性能、企业级的 `Go` 基础开发框架。实现了比较完善的基础设施建设以及开发工具链,提供了常用的基础开发模块,如:缓存、日志、队列、数组、集合、容器、定时器、命令行、内存锁、配置管理、资源管理、数据校验、定时任务、数据库 `ORM`、 `TCP/UDP` 组件、进程管理/通信等等。并提供了 `Web` 服务开发的系列核心组件,如: `Router`、 `Cookie`、 `Session`、 `Middleware`、服务注册、模板引擎等等,支持热重启、热更新、域名绑定、 `TLS/HTTPS`、 `Rewrite` 等特性。 + +> 如果您初识 `Go` 语言,您可以将 `GoFrame` 类似于 `PHP` 中的 `Laravel`, `Java` 中的 `SpringBoot` 或者 `Python` 中的 `Django`。 + +![](https://wiki.goframe.org/download/attachments/1114119/arch.png?version=1&modificationDate=1608537397031&api=v2) + +## 特点 + +- 模块化、松耦合设计; +- 模块丰富、开箱即用; +- 简便易用、易于维护; +- 高代码质量、高单元测试覆盖率; +- 社区活跃,大牛谦逊低调脾气好; +- 详尽的开发文档及示例; +- 完善的本地中文化支持; +- 设计为团队及企业使用; + +## Change Logs + +1. `ghttp` + - 改进 `HTTPClient` 的 `GET` 请求相关方法,当传递参数时不再作为 `Body` 参数提交,而是自动构造为 `QueryString` 提交。以保证与其他服务端的兼容能力。 + - 请求对象 `Request` 增加默认值设置特性: [请求输入-默认值绑定](../../docs/WEB服务开发/请求输入/请求输入-默认值绑定.md) + - 增加 `Request.SetCtx` 方法,用于自定义上下文变量,常用于中间件/拦截器中: [请求输入-Context](../../docs/WEB服务开发/请求输入/请求输入-Context.md) + - 模板解析中增加 `Request` 变量,用于获得客户端提交的请求参数,无论是 `QueryString/Form` 类型参数: [数据返回-模板解析](../../docs/WEB服务开发/数据返回/数据返回-模板解析.md) + - `Cookie` 功能改进,如何设置与 `Session` 有效期保持一致的 `Cookie`,请参考: [Cookie#Cookie会话过期](https://wiki.goframe.org/display/gf/Cookie#Cookie-Cookie%E4%BC%9A%E8%AF%9D%E8%BF%87%E6%9C%9F) + - 分组路由注册增加 `ALLMap` 方法,用于批量注册路由: [分组路由#批量注册](https://wiki.goframe.org/pages/viewpage.action?pageId=1114517#id-%E5%88%86%E7%BB%84%E8%B7%AF%E7%94%B1-%E6%89%B9%E9%87%8F%E6%B3%A8%E5%86%8C) + - 新增 `CRSF` 插件介绍文档: [CSRF防御设置](../../docs/WEB服务开发/高级特性/CSRF防御设置.md) + - 其他一些功能及细节改进。 +2. `gdb` + - 增加 `Ctx` 方法,用于异步 `IO` 控制或传递自定义的上下文信息,特别是链路跟踪信息: [ORM上下文变量](../../docs/核心组件/数据库ORM/ORM上下文变量.md) + - 增加 `Raw` 类型,用于原始 `SQL` 语句嵌入,该语句将会直接提交到底层数据库驱动,不会做任何处理: [写入保存#RawSQL语句嵌入](https://wiki.goframe.org/pages/viewpage.action?pageId=1114344#id-%E5%86%99%E5%85%A5%E4%BF%9D%E5%AD%98-RawSQL%E8%AF%AD%E5%8F%A5%E5%B5%8C%E5%85%A5)、 [更新删除#RawSQL语句嵌入](https://wiki.goframe.org/pages/viewpage.action?pageId=1114238#id-%E6%9B%B4%E6%96%B0%E5%88%A0%E9%99%A4-RawSQL%E8%AF%AD%E5%8F%A5%E5%B5%8C%E5%85%A5) + - 改进 `Fields/Fields/Data` 方法,增加对输入 `map/struct` 参数与数据表字段的自动映射检测、过滤: [ORM高级特性#字段映射](../../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性.md) + - 增加 `InsertedAt/UpdatedAt/DeletedAt` 字段名称的配置,增加 `TimeMaintainDisabled` 配置可关闭时间填充及软删除特性: [ORM链式操作-时间维护](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-时间维护/ORM链式操作-时间维护.md) + - 增加 `Counter` 更新特性,用于字段的增加/减少操作: [更新删除#Counter更新特性](https://wiki.goframe.org/pages/viewpage.action?pageId=1114238#id-%E6%9B%B4%E6%96%B0%E5%88%A0%E9%99%A4-Counter%E6%9B%B4%E6%96%B0%E7%89%B9%E6%80%A7) + - 改进 `ORM` 时区处理,详情请参考章节: [ORM时区处理](../../docs/核心组件/数据库ORM/ORM时区处理.md) + - 其他关于性能和易用性的细节改进。 + - 一些细节问题改进。 + - 完善单元测试。 +3. `gerror` + 1. 增加 `Newf/NewSkipf` 方法创建错误对象: [错误处理](../../docs/核心组件/错误处理/错误处理.md) + 2. 增加对错误码特性的支持: [错误处理-错误码使用](../../docs/核心组件/错误处理/错误处理-错误码特性/错误处理-错误码方法.md) + 3. 完善单元测试。 +4. `gvalid` + - 增加 `phone-loose` 宽松手机号校验规则,只要满足 `13/14/15/16/17/18/19` 开头的 `11` 位数字都可以通过验证。 + - 返回校验错误实现了 `gerror` 的 `Current() error` 接口,因此可以使用 `gerror.Current` 方法来获取当前第一条校验错误: [数据校验-校验结果](../../docs/核心组件/数据校验/数据校验-校验结果.md) + - 其他细节改进。 + - 单元测试完善。 +5. `gvar` + - 增加 `IsNil/IsEmpty` 方法,判断数据是否为 `nil/空`。 + - 增加 `IsInt/IsUint/IsFloat/IsSlice/IsMap/IsStruct` 常用类型判断方法。 + - 标注废弃 `StructDeep/StructDeep` 方法,直接使用 `Struct/Structs` 即可。 + - 完善单元测试。 +6. `ghtml` + - 增加 `SpecialCharsMapOrStruct` 方法,用于自动转换 `map/struct` 键值/属性中的 `HTML` 代码,防止 `XSS`。 +7. `gjson` + - 标记废弃 `To*` 转换方法,例如: `ToStruct` 替换为 `Struct` 方法。 + - 一些细节问题改进。 + - 单元测试完善。 +8. `internal` + - 改进并完善 `internal/empty` 包的空值判断。 + - 由于性能问题 [https://github.com/gogf/gf/issues/1004](https://github.com/gogf/gf/issues/1004) ,临时删除了 `internal/json` 包中对第三方包 [github.com/json-iterator/go](http://github.com/json-iterator/go) 的依赖,还原为标准库 `encoding/json`。 + - 改进 `internal/structs` 包,由于该包在 `struct` 转换特性中使用比较频繁,因此去掉第三方包 [github.com/gqcn/structs](http://github.com/gqcn/structs) 的依赖,简化了反射处理逻辑,提高了性能以及易用性,提高了长期维护性。 + - `internal/utils` 包增加 `RemoveSymbols` 方法,用于删除字符串中的特殊字母。改进 `EqualFoldWithoutChars` 方法,去掉对字符串中字符串的正则过滤功能,极大提高了方法性能。不要小看这两个小函数,性能的点滴改进能提高框架中其他涉及到复杂类型转换模块的性能。 `internal` 包虽然不直接对外暴露方法,但是却影响着框架中的一些核心组件性能。 +9. `gcfg` + - 改进单例名称的配置对象获取,增加自动检测文件类型功能: [配置管理-单例对象#自动检索特性](https://wiki.goframe.org/pages/viewpage.action?pageId=1114194#id-%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86%E5%8D%95%E4%BE%8B%E5%AF%B9%E8%B1%A1-%E8%87%AA%E5%8A%A8%E6%A3%80%E7%B4%A2%E7%89%B9%E6%80%A7) + - 其他一些细节改进。 +10. `gcmd` + - 改进默认参数解析获取方式: [命令管理-gcmd](../../docs/组件列表/系统相关/命令管理-gcmd.md) + - 增加 `GetWithEnv` 方法,当命令行中不存在指定参数时,同时从环境变量中获取: [命令管理-gcmd](../../docs/组件列表/系统相关/命令管理-gcmd.md) +11. `genv` + - 增加 `SetMap` 方法,用于批量设置环境变量。 + - 增加 `GetWithCmd` 方法,当环境变量中不存在指定参数时,同时从命令行参数中获取: [环境变量-genv](../../docs/组件列表/系统相关/环境变量-genv.md) +12. `gfile` + - 标记废弃 `ReadByteLines` 方法,新增 `ReadLinesBytes` 方法。 + - 调整 `ReadLines/ReadLinesBytes` 方法回调函数定义,增加 `error` 返回。 +13. `glog` + - 改进滚动更新功能。 + - 其他一些细节改进。 +14. `gsession` + - 增加 `SetMap` 方法,用于批量设置键值对数据。 +15. `gtimer` + - 常量名称改进,统一采用大驼峰方式。 +16. `gview` + - 增加内置模板函数 `map`,用于将参数转换为 `map[string]interface{}` 类型。 + - 增加内置模板函数 `maps`,用于将参数转换为 `[]map[string]interface{}` 类型。 + - 增加内置模板函数 `json`,用于将参数转换为 `JSON` 字符串类型。 + - 文档更新: [模板函数-内置函数](../../docs/核心组件/模板引擎/模板引擎-模板函数/模板函数-内置函数.md) + - 其他一些细节改进。 +17. `gconv` + - 性能改进。 + - 功能改进(细节改进有点多,实在不想写得太详细)。 + - 代码更加健壮。 + - 单元测试完善。 +18. `gutil` + - 增加 `Keys` 方法,用于获取 `map/struct` 的键名/属性名称,构造成数组返回。 + - 增加 `Values` 方法,用于获取 `map/struct` 的键值/属性值,构造成数组返回。 + - 增加 `MapToSlice` 方法,例如: `{"K1": "v1", "K2": "v2"} => ["K1", "v1", "K2", "v2"]` + - 增加 `StructToSlice` 方法,例如: `{"K1": "v1", "K2": "v2"} => ["K1", "v1", "K2", "v2"]` + - 增加 `SliceToMap` 方法,例如: ` ["K1", "v1", "K2", "v2"] => {"K1": "v1", "K2": "v2"}` + - 单元测试完善。 + - 其他细节改进。 + +## Bug Fix + +1. 修复 `garray/gmap/gtree` 的 `Clone` 方法并发安全判断问题。 +2. 修复当设置了过期方法,但 `gpool` 在元素项过期时没有自动调用过期方法处理的问题。 +3. 修复 `gfile.ReadLInes/ReadLineBytes` 在数据量大时的读取重复问题。 +4. 其他一些错误修复。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" new file mode 100644 index 00000000000..a1b211b3422 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.16 2021-06-01.md" @@ -0,0 +1,105 @@ +--- +slug: '/release/v1.16.0' +title: 'v1.16 2021-06-01' +sidebar_position: 0 +hide_title: true +keywords: [GoFrame,GoFrame框架,Golang,全链路跟踪,数据库ORM,HTTP客户端,数据校验,定时器组件,错误堆栈,开发工具链] +description: 'GoFrame v1.16版本全新发布,框架新增全链路跟踪特性,带来数据库ORM模型关联、嵌套事务、子查询等多种改进功能。HTTP客户端增加中间件拦截器,数据校验组件支持Context及I18N增强,定时器组件全新重构,此外还有大量代码优化。' +--- + +`Hello`,小伙伴久等啦!距离上一次发布,时隔刚好半年,这半年发生了很多很多的事情。薛老的猫以不同的观测方式决定了小猫不同的结果,同样看待世界的不同方式决定了世界在我们眼中的样子。这次给大家带来了最新 `GoFrame v1.16` 版本! `GoFrame` 是一款模块化、高性能、企业级的 `Go` 基础开发框架: [https://goframe.org](https://wiki.goframe.org) ,一款低调务实、真正意义的企业级 `Golang` 开发框架!本次更新包含了大量的新特性和功能改进,特别是全链路跟踪、 `ORM` 模型关联/嵌套事务/子查询/数十项新增方法、 `HTTP` 客户端拦截器、数据校验及 `I18N` 组件改进、重构版的定时器等等。本次更新内容较多,以下为主要更新介绍,希望大家喜欢! `Enjoy!` + +![](/markdown/86fedaae17d9c3ed7be8d93a1f31d5bd.png) + +本次文档有大量更新,目前开发文档总约有20+万字,建议按照官方目录结构进行阅读 + +## 重要特性 + +1. 框架新增 **全链路跟踪** 特性,采用 `OpenTelementry` 标准,目前已打通 `HTTP Client&Server/GRPC Client&Server/ORM/Redis/Logging` 组件,详细介绍请参考章节: +1. [链路跟踪-基本示例](../../docs/服务可观测性/服务链路跟踪/链路跟踪-基本示例.md) +2. [链路跟踪-HTTP示例](../../docs/服务可观测性/服务链路跟踪/链路跟踪-HTTP示例/链路跟踪-HTTP示例.md) +3. [链路跟踪-GRPC示例](../../docs/服务可观测性/服务链路跟踪/链路跟踪-GRPC示例.md) +2. 数据库核心组件新增以下特性: +1. **模型关联**: + 1. [模型关联-动态关联-ScanList](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-动态关联-ScanList.md) + 2. [模型关联-静态关联-With特性](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-模型关联/模型关联-静态关联-With特性.md) +2. **嵌套事务**: [ORM事务处理](../../docs/核心组件/数据库ORM/ORM事务处理/ORM事务处理.md) +3. **子查询** 特性: [ORM查询-子查询特性](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM查询-子查询特性.md) +4. 新增 **数十项** 个ORM模型操作方法(参考 `PHP Laravel`),正在使用 `goframe` 的小伙伴们建议务必看一下: + 1. [ORM链式操作(🔥重点🔥)](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作.md) + 2. [ORM链式操作-写入保存](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) + 3. [ORM链式操作-更新删除](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-更新删除.md) + 4. [ORM链式操作-数据查询](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM链式操作-数据查询.md) +3. HTTP **客户端增加中间件拦截器** 功能特性,详情请参考章节: [HTTPClient-拦截器/中间件](../../docs/WEB服务开发/HTTPClient/HTTPClient-拦截器中间件.md) +4. 数据校验组件大量改进: +1. 增加链式操作校验对象: [数据校验-校验对象](../../docs/核心组件/数据校验/数据校验-校验对象.md) +2. 增加对 `Context` 的支持,并改进支持了强大的 `I18N` 国际化错误信息管理能力,详情请参考章节: + 1. [I18N国际化](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. [数据校验-自定义错误](../../docs/核心组件/数据校验/数据校验-自定义错误.md) +3. 自定义校验规则改进,增加局部校验规则注册特性,增加完整数据校验: [数据校验-自定义规则](../../docs/核心组件/数据校验/数据校验-自定义规则/数据校验-自定义规则.md) +5. 定时器组件 `gtimer` 的全新重构,去掉 `TimingWheel` 的实现,改为了更加稳健的 `PriorityQueue` 的改进实现,详情请参考章节: [定时器-gtimer](../../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +6. 框架核心基础组件已增加全错误堆栈特性。当错误产生时可以完整获取链路相关组件的错误堆栈信息。该特性只有一款基础组件相对完善并且统一设计的开发框架才能具备。 + +## 功能改进 + +1. `ORM` + 1. 增加全链路的链路跟踪上下文 `Context` 参数传递: [ORM上下文变量](../../docs/核心组件/数据库ORM/ORM上下文变量.md) 链路跟踪中默认会记录 `SQL` 以及数据库连接信息(不包含敏感配置),组件链路跟踪信息可配置关闭。 + 2. 进一步完善 `ORM` 组件日志记录,日志仅在调试模式下有效,详细介绍请参考章节: [ORM高级特性](../../docs/核心组件/数据库ORM/ORM高级特性/ORM高级特性.md) + 3. 新增数十项 `ORM` 模型操作方法(参考 `PHP Laravel`),例如: `InsertAndGetId`、 `Min/Max/Avg/Sum`、 `Increment/Decrement`、 `WhereBetween/WhereLike/WhereIn/WhereNull`、 `OrderAsc/OrderDesc/OrderRandom` 等等。正在使用 `goframe` 的小伙伴们建议务必看一下,详情请参考章节: + 1. [ORM链式操作(🔥重点🔥)](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作.md) + 2. [ORM链式操作-写入保存](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-写入保存.md) + 3. [ORM链式操作-更新删除](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-更新删除.md) + 4. [ORM链式操作-数据查询](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-数据查询/ORM链式操作-数据查询.md) + 4. 新版本开始,数据库 `ORM` 链式操作默认启用了字段过滤特性,当给定的参数无法与数据表字段智能匹配时将会被自动过滤,详情请参考章节: [ORM链式操作-字段过滤](../../docs/核心组件/数据库ORM/ORM链式操作/ORM链式操作-字段过滤.md) + 5. 改进 `pgsql` 数据库类型 `int8` 到 `Golang` 类型的转换,从 `int` 类型调整为了 `int64`。 + 6. 大量重构、改进工作,细节比较多,这里不再一一赘述。关键结果是组件功能更加丰富、代码更加严谨、设计更加简洁、使用更加便捷。 +2. `HTTP` + 1. `HTTP Client` 增加中间件拦截器功能特性: [HTTPClient-拦截器/中间件](../../docs/WEB服务开发/HTTPClient/HTTPClient-拦截器中间件.md) + 2. `HTTP Client&Server` 增加链路跟踪支持: [链路跟踪-HTTP示例](../../docs/服务可观测性/服务链路跟踪/链路跟踪-HTTP示例/链路跟踪-HTTP示例.md) + 3. `ghttp` 包中的客户端请求方法标记废除,后续统一采用 `HTTP Client` 对象方式使用。 + 4. 改进 `Request.Parse` 方法的数据校验,直接校验提交参数,而不是校验数据转换后的 `struct` 对象: [请求输入-请求校验](../../docs/WEB服务开发/请求输入/请求输入-请求校验.md) + 5. 增加 `WrapF/WrapH` 方法,用于将标准库的 `http.HandlerFunc/http.Handler` 转换为 `ghttp.Server` 支持的服务方法类型。 + 6. 其他大量改进工作,细节也很多。关键结果就是组件功能更加丰富、代码更加严谨、使用更加便捷。 +3. `gvalid` + 1. `Check` 方法名称修改为了 `CheckValue`,详情请参考章节: [数据校验-单数据校验](../../docs/核心组件/数据校验/数据校验-参数类型/数据校验-单数据校验.md) + 2. 新增 `CheckStructWithData` 方法,用于校验指定参数的 `struct` 对象: [数据校验-Struct校验](../../docs/核心组件/数据校验/数据校验-参数类型/数据校验-Struct校验/Struct校验-基本使用.md) + 3. 新增 `Validator` 校验对象,用于便捷的链式操作,并可在后期进行进一步扩展: [数据校验-校验对象](../../docs/核心组件/数据校验/数据校验-校验对象.md) + 4. 自定义的规则方法定义增加了 `Context` 上下文变量输入,并增加 `RuleFunc` 及 `RuleFuncMap` 局部校验规则注册方法: [数据校验-自定义规则](../../docs/核心组件/数据校验/数据校验-自定义规则/数据校验-自定义规则.md) + 5. 左右校验方法增加了 `Context` 参数支持,并改进支持了强大的 `I18N` 国际化错误信息管理能力: + 1. [I18N国际化](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. [数据校验-自定义错误](../../docs/核心组件/数据校验/数据校验-自定义错误.md) + 6. `Error` 错误对象修改为了接口实现,需要注意之前使用 `*Error` 指针方式的地方会报错。 + 7. 其他一些细节改进。 +4. `gcache` + 1. 增加 `Ctx` 链式操作方法,用于上下文参数输入,详情请参考章节: [缓存管理](../../docs/核心组件/缓存管理/缓存管理.md) + 2. `Adapter` 接口增加 `Context` 参数输入,便于上下文参数传递,并改进相关内置实现的适配器支持上下文参数传递: [缓存管理-接口设计](../../docs/核心组件/缓存管理/缓存管理-接口设计.md) +5. `gredis` + 1. 增加了对链路跟踪的支持,详情请参考章节:Redis-Context +6. `gjson` + 1. `Option` 类型名称修改为了 `Options`,这是一项非兼容性更新。 + 2. `NewWithOption` 方法名称修改为了 `NewWithOptions`。 +7. `gcmd` + 1. 新增 `GetOptWithEnv` 方法,并将 `GetWithEnv` 方法标记废弃。 +8. `glog` + 1. 增加基于 `OpenTelemetry` 标准的链路跟踪支持: [日志组件-Context](../../docs/核心组件/日志组件/日志组件-Context.md) +9. `gproc` + 1. 增加统一的信号注册监听回调特性: [进程管理-信号监听](../../docs/组件列表/系统相关/进程管理-gproc/进程管理-信号监听.md) +10. `gres` + 1. 资源管理的最佳实践参考: [资源管理-最佳实践](../../docs/核心组件/资源管理/资源管理-最佳实践.md) +11. `gtimer` + 1. 定时器组件 `gtimer` 的全新重构,去掉 `TimingWheel` 的实现,改为了更加稳健的 `PriorityQueue` 的改进实现,详情请参考章节: [定时器-gtimer](../../docs/组件列表/系统相关/定时器-gtimer/定时器-gtimer.md) +12. `gview` + 1. 所有模板解析方法增加了 `Context` 参数输入: [模板引擎](../../docs/核心组件/模板引擎/模板引擎.md) +13. `gconv` + 1. 改进 `Scan` 方法,增加对 `Map/Maps` 参数类型的自动转换支持: [类型转换-Scan转换](../../docs/核心组件/类型转换/类型转换-Scan转换.md) +14. `gi18n` + 1. `I18N` 国际化组件增加对 `Context` 的支持,详情请参考章节: [I18N国际化](../../docs/核心组件/I18N国际化/I18N国际化.md) + 2. 注意,所有方法都增加了 `ctx` 参数的输入,并去掉了部分方法的 `language` 参数,转而由 `ctx` 参数来控制 `language` 输入,并提高可扩展性: [I18N国际化-使用介绍](../../docs/核心组件/I18N国际化/I18N国际化-使用介绍.md) + 3. 去掉了 `TranslateFormatLang`、 `Tfl` 方法。 +15. `gmeta` + 1. 新增 `gmeta` 元数据包,详情请参考章节: [元数据-gmeta](../../docs/组件列表/实用工具/元数据-gmeta.md) +16. 各个组件的其他一些细节改进,不用特意在发布文档中阐述。 + +## 开发工具链 + +`CLI` 工具有更新,主要是简化了 `dao` 模型代码生成,去掉了重复方法生成,去掉了直接返回模型对象的相关方法, `dao` 对象修改为了直接继承 `GoFrame ORM` 组件中的 `Model` 对象,通过给定模型接受查询数据,因此部分方法使用的方式会需要调整。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" new file mode 100644 index 00000000000..555fbb329ca --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.2 2018-11-26.md" @@ -0,0 +1,50 @@ +--- +slug: '/release/v1.2.0' +title: 'v1.2 2018-11-26' +sidebar_position: 14 +hide_title: true +keywords: [GoFrame框架,SQLServer支持,Oracle支持,gvalid校验,ghttp改进,模板引擎函数,gconv改进,gform配置,gfsnotify,WebSocket跨域] +description: 'GoFrame框架v1.2版本的新特性、新功能以及功能改进。此版本增加了对SQLServer和Oracle数据库的支持,完善了gvalid模块的顺序特性,改进了ghttp.Request的处理机制。模板引擎新增多种内置函数,并在gvar、gform、gfsnotify等模块中进行了多项升级和优化。' +--- + +### 新特性 + +1. `ORM` 新增对 `SQLServer` 及 `Oracle` 的支持( [https://goframe.org/database/orm/database](https://wiki.goframe.org/database/orm/database)); +2. 完成 `gvalid` 模块校验结果的顺序特性( [https://goframe.org/util/gvalid/checkmap](https://wiki.goframe.org/util/gvalid/checkmap)); +3. 改进 `ghttp.Request.Exit`,使得调用该方法时立即退出业务执行,开发者无需调用 `Exit` 方法时再使用 `return` 返回( [https://goframe.org/net/ghttp/service/object](https://wiki.goframe.org/net/ghttp/service/object)); +4. 模板引擎新增若干内置函数: `text/html/htmldecode/url/urldecode/date/compare/substr/strlimit/hidestr/highlight/toupper/tolower/nl2br` ( [https://goframe.org/os/gview/funcs](https://wiki.goframe.org/os/gview/funcs)); +5. 模板引擎新增内置变量 `Config` ( [https://goframe.org/os/gview/vars](https://wiki.goframe.org/os/gview/vars)); +6. 改进 `gconv.Struct` 转换默认规则,支持不区分大小写的键名与属性名称匹配; +7. `gform` 配置文件支持 `linkinfo` 自定义数据库连接字段( [https://goframe.org/database/orm/config](https://wiki.goframe.org/database/orm/config)); +8. `gfsnotify` 模块增加对特定回调的取消注册功能( [https://goframe.org/os/gfsnotify/index](https://wiki.goframe.org/os/gfsnotify/index)); + +### 新功能 + +1. 改进 `ghttp.Request`,增加 `SetParam/GetParam` 请求流程自定义变量设置/获取方法,用于在请求流程中的回调函数共享变量( [https://goframe.org/net/ghttp/request](https://wiki.goframe.org/net/ghttp/request)); +2. 改进 `ghttp.Response`,增加 `ServeFileDownload` 方法,用于WebServer引导客户端下载文件( [https://goframe.org/net/ghttp/response](https://wiki.goframe.org/net/ghttp/response)); +3. `gvar` 模块新增 `gvar.VarRead` 只读接口,用于控制对外只暴露数据读取功能; +4. 增加 `g.Throw` 抛异常方法, `g.TryCatch` 异常捕获方法封装; +5. 改进 `gcron` 模块,增加自定义的Cron管理对象,增加 `New/Start/Stop` 方法; + +### 功能改进 + +1. WebServer添加 `RouterCacheExpire` 配置参数,用于设置路由检索缓存过期时间; +2. WebServer允许同一 `HOOK` 事件被多次绑定注册,先注册的回调函数优先级更高( [https://goframe.org/net/ghttp/service/hook](https://wiki.goframe.org/net/ghttp/service/hook)); +3. 当前工作目录为系统临时目录时, `gcfg`/ `gview`/ `ghttp` 模块默认不添加工作目录到搜索路径; +4. 改进 `WebSocket` 默认支持跨域请求( [https://goframe.org/net/ghttp/websocket](https://wiki.goframe.org/net/ghttp/websocket)); +5. 改进 `gtime.Format` 支持中文; +6. 改进 `gfsnotify`,支持编辑器对文件非执行标准编辑时(RENAME+CHMOD)的热更新问题; +7. 改进 `gtype.Set` 方法,增加Set原子操作返回旧的变量值; +8. `gfile.ScanDir` 增加支持 `pattern` 多个文件模式匹配,使用’ `,`‘符号分隔多个匹配模式; +9. `gcfg` 模块增加获取配置变量为 `*gvar.Var`; +10. `gstr` 模块增加对中文截取方法; +11. 改进 `gtime.StrToTime` 对常用时间格式匹配模式,新增 `gtime.ParseTimeFromContent` 方法; +12. 修改配置管理、模板引擎、调试模式的环境变量名称为大写下划线标准格式; +13. 改进 `grand` 模块随机数生成设计,底层使用 `crypto/rand` +缓冲区实现高速的随机数生成( [https://goframe.org/util/grand/index](https://wiki.goframe.org/util/grand/index)); + +### 问题修复 + +1. 修复 `gspath` 模块在 `windows` 下搜索失效问题; +2. 修复 `gspath` 模块Search时带有indexFiles的检索问题; +3. bug fix INZS1( [https://github.com/gogf/gf/issues/INZS1](https://github.com/gogf/gf/issues/INZS1)); +4. 修复 `gproc.ShellRun` 在windows下的执行问题; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" new file mode 100644 index 00000000000..3260eb1946e --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.3 2018-12-26.md" @@ -0,0 +1,43 @@ +--- +slug: '/release/v1.3.0' +title: 'v1.3 2018-12-26' +sidebar_position: 13 +hide_title: true +keywords: [GoFrame,GoFrame框架,v1.3,gform,WebServer,gcache,gredis,Travis CI,gview,Bug Fix] +description: '本次发布的GoFrame框架v1.3版本涵盖多个新特性,包括gform的重构、WebServer分组路由及Rewrite路由重写特性,并改善框架在开发环境下的自动识别。此外,集成了Travis CI自动化构建与测试,优化了WebServer静态文件服务,提升了gcache性能,并修复多项功能Bug,增强了整体的稳定性和扩展性。' +--- + +### 新特性 + +1. 对 `gform` 完成重构,以提高扩展性,并修复部分细节问题、完善单元测试用例( [https://goframe.org/database/orm/index](https://wiki.goframe.org/database/orm/index)); +2. `WebServer` 路由注册新增分组路由特性( [https://goframe.org/net/ghttp/group](https://wiki.goframe.org/net/ghttp/group)); +3. `WebServer` 新增 `Rewrite` 路由重写特性( [https://goframe.org/net/ghttp/static](https://wiki.goframe.org/net/ghttp/static)); +4. 增加框架运行时对开发环境的自动识别; +5. 增加了 `Travis CI` 自动化构建/测试; + +### 新功能 + +1. 改进 `WebServer` 静态文件服务功能,增加 `SetStaticPath`/ `AddStaticPath` 方法( [https://goframe.org/net/ghttp/static](https://wiki.goframe.org/net/ghttp/static)); +2. `gform` 新增 `Filter` 链式操作方法,用于过滤参数中的非表字段键值对( [https://goframe.org/database/orm/linkop](https://wiki.goframe.org/database/orm/linkop)); +3. `gcache` 新增 `Data` 方法,用以获取所有的缓存数据项; +4. `gredis` 增加 `GetConn` 方法获取原生redis连接对象; + +### 功能改进 + +1. 改进 `gform` 的 `Where` 方法,支持 `slice` 类型的参数,并更方便地支持 `in` 操作查询( [https://goframe.org/database/orm/linkop](https://wiki.goframe.org/database/orm/linkop)); +2. 改进 `gproc` 进程间通信数据结构,将 `pid` 字段从 `16bit` 扩展为 `24bit`; +3. 改进 `gconv`/ `gmap`/ `garray`,增加若干操作方法; +4. 改进 `gview` 模板引擎中的 `date` 内置函数,当给定的时间戳为空时打印当前的系统时间; +5. 改进 `gview` 模板引擎中,当打印的变量不存在时,显示为空(标准库默认显示为 ``); +6. 改进 `WebServer`,去掉 `HANGUP` 的信号监听,避免程序通过 `nohup` 运行时产生异常退出问题; +7. 改进 `gcache` 性能,并完善基准测试; + +### Bug Fix + +1. 修复 `gcache` 在非LRU特性开启时的缓存关闭资源竞争问题,并修复 `doSetWithLockCheck` 内部方法的返回值问题; +2. 修复 `grand.intn` 内部方法在 `x86` 架构下的随机数位溢出问题; +3. 修复 `gbinary` 中 `Int` 方法针对 `[]byte` 参数长度自动匹配造成的字节长度溢出问题; +4. 修复 `gjson` 由于官方标准库 `json` 不支持 `map[interface{}]*` 类型造成的Go变量编码问题; +5. 修复 `garray` 中部分方法的数据竞争问题,修复二分查找排序问题; +6. 修复 `ghttp.Request.GetVar` 方法获取参数问题; +7. 修复 `gform` 的数据库连接池不起作用的问题; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" new file mode 100644 index 00000000000..1f537f942ee --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.4 2019-01-24.md" @@ -0,0 +1,39 @@ +--- +slug: '/release/v1.4.0' +title: 'v1.4 2019-01-24' +sidebar_position: 12 +hide_title: true +keywords: [GoFrame,GoFrame框架,v1.4更新,gtimer定时器,gcron重构,gconv类型转换,gform自动识别,Travis CI支持,ghttp流程控制,gvalid手机号校验] +description: '本次GoFrame框架v1.4版本更新带来了多项新特性和功能改进。新增高性能任务定时器模块gtimer,以及对gcron模块的重构优化。gconv模块支持结构体指针属性转换,gform增加数据库类型的自动识别特性。此外,还改进了多个模块的性能,提高了并发安全性,并修复了一些已知问题。' +--- + +### 新特性 + +1. 新增并发安全的高性能任务定时器模块 `gtimer`, 类似于Java的 `Timer`,但是比较于Java的 `Timer` 更加强大,内部实现采用灵活高效的 `分层时间轮` 设计,被设计为可管理维护百万级别以上数量的定时任务。 `gtimer` 为 `GoFrame` 框架的核心模块之一,单元测试覆盖率达到 `93.6%`: [https://goframe.org/os/gtimer/index](https://wiki.goframe.org/os/gtimer/index) +2. 采用任务定时器 `gtimer` 重构 `gcron` 定时任务模块,去掉第三方 `github.com/robfig/cron` 包的使用。 `gcron` 增加单例模式的定时任务: [https://goframe.org/os/gcron/index#](https://wiki.goframe.org/os/gcron/index); +3. `gconv` 类型转换模块支持对 `struct` 结构体中的 **指针属性** 转换: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct); +4. `gform` 增加对数据库类型的自动识别特性,这一特性在需要将查询结果 `json` 编码返回时非常有用: [https://goframe.org/database/orm/index](https://wiki.goframe.org/database/orm/index) +5. `Travis CI` 增加对 `386` 架构的自动化测试支持(目前已支持 `386` 和 `amd64`); + +### 新功能 + +1. `ghttp` 模块新增 `Exit`、 `ExitAll`、 `ExitHook` 方法,用于HTTP请求处理流程控制: [https://goframe.org/net/ghttp/service/object](https://wiki.goframe.org/net/ghttp/service/object); +2. `grand` 模块增加 `Meet/MeetProb` 方法,用于给定概率的随机满足判断,增加别名方法 `N/Str/Digits/Letters`; +3. `gvalid` 数据/表单校验模块增加 `16X` 及 `19X` 手机号的校验支持; + +### 功能改进 + +1. `gform` 设置默认的数据库连接池 `CONN_MAX_LIFE` 参数值为 `30` 秒; +2. 改进 `glist` 模块,提高约 `20%` 左右性能,并增加若干链表操作方法; +3. 改进 `gqueue` 模块,提高约 `50` 左右性能,并增加模块对 `select` 语法的支持(使用 `Queue.C`): [https://goframe.org/container/gqueue/index](https://wiki.goframe.org/container/gqueue/index); +4. 改进 `gmlock` 内存锁模块,并完善单元测试用例: [https://goframe.org/os/gmlock/index](https://wiki.goframe.org/os/gmlock/index); +5. 改进并发安全容器所有的模块,调整并发安全控制非必需参数 `safe...bool` 为 `unsafe...bool`; +6. 改进 `gpool` 对象复用模块,支持并发安全; +7. 更新 `gkafka` 模块的第三方依赖包; +8. 完善 `ghttp` 模块的单元测试用例; + +### Bug Fix + +1. 修复 `gmd5` 模块操作文件时的文件指针未关闭问题; +2. 修复 `gcache` 缓存项过期删除失效问题; +3. 其他修复; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" new file mode 100644 index 00000000000..85f6ca82bb1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.5 2019-02-28.md" @@ -0,0 +1,42 @@ +--- +slug: '/release/v1.5.0' +title: 'v1.5 2019-02-28' +sidebar_position: 11 +hide_title: true +keywords: [GoFrame,GoFrame框架,v1.5更新,模块改进,容器模块,新特性,bug修复,ghttp,gvalid,greuseport] +description: 'GoFrame框架v1.5版本发布,主库迁移至GitHub并做出多项模块改进,包括garray、gset、gmap、gstr等容器模块和gform的参数支持。新增greuseport模块,并在ghttp中改进多项功能。同时,修复了一些已知问题如gvalid的检查规则和gcron的定时器问题。了解更多详情请访问GoFrame官网。' +--- + +### 新特性 + +1. 主库从 `gitee` 迁移到了 `github`( [https://github.com/gogf/gf](https://github.com/gogf/gf) ), `gitee` 作为镜像站,用于国内的代码贡献及ISSUE提交,迁移说明详见: [https://goframe.org/upgradeto150](https://wiki.goframe.org/upgradeto150) +2. 对常用的 `container` 数组模块: `garray` 做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/garray](https://pkg.go.dev/github.com/gogf/gf/v2/container/garray) +3. 对常用的 `container` 集合模块: `gset` 做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gset](https://pkg.go.dev/github.com/gogf/gf/v2/container/gset) +4. 对常用的 `container` MAP模块: `gmap` 做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档: [https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap](https://pkg.go.dev/github.com/gogf/gf/v2/container/gmap) +5. 对常用的字符串模块: `gstr` 做了大量改进/完善工作,新增大量常用方法,并完善单元测试用例及方法注释,详见API文档: [https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr](https://pkg.go.dev/github.com/gogf/gf/v2/text/gstr) +6. 改进 `gform` 中对 `struct`/ `*struct` 参数的支持, `*Insert/*Save/*Replace/*Update/Where/Data` 方法的参数调整为 `interface{}` 类型,并支持任意类型的: `string/map/slice/struct/*struct` 参数传递,具体请参考: [https://goframe.org/database/orm/chaining](https://wiki.goframe.org/database/orm/chaining) +7. 新增/完善若干模块的单元测试用例, 包括: `gvalid`/ `gregex`/ `garray`/ `gset`/ `gmap`/ `gstr`/ `gconv`/ `ghttp`/ `gdb`; +8. 由于 `gkafka` 模块比较重,且不是框架核心模块,因此将该模块迁移到新的仓库中独立管理,并去掉相关依赖包: [https://github.com/gogf/gkafka](https://github.com/gogf/gkafka) +9. 新增 `greuseport` 模块,用以实现TCP的 `REUSEPORT` 特性: [https://pkg.go.dev/github.com/gogf/gf/v2/net/greuseport](https://pkg.go.dev/github.com/gogf/gf/v2/net/greuseport) + +### 新功能/改进 + +1. 去掉模板引擎内置变量中自动初始化 `session` 对象带来的内存占用问题; +2. `ghttp.Client` 改进,增加若干方法,详见: [https://goframe.org/net/ghttp/client](https://wiki.goframe.org/net/ghttp/client) +3. `ghttp` 分组路由增加 `COMMON` 方法,用以注册常用的 `HTTP METHOD`( `GET/PUT/POST/DELETE`)路由; +4. 更新框架依赖的 `golang.org/x/sys` 模块; +5. 改进 `gform` 的批量操作( `Batch*` 操作)返回结果对象,可以通过该结果对象获得批量操作准确的受影响记录行数; +6. 将 `gstr`/ `gregex` 模块从 `util` 分类迁移到了 `text` 分类目录下; +7. 将 `gtest` 模块从 `util` 分类迁移到了 `test` 分类目录下; +8. 完善 `glog` 方法注释; + +### Bug Fix + +1. 修复带点的邮件格式,用 `gvalid.Check` 的” `email`“规则不能匹配成功; +2. 修复 `gvalid.Check` 在 `regex` 规则下的检查失败问题; +3. 修复 `gcron` 模块定时规则中天和周不允许 `?` 符号的问题; +4. 修复 `ghttp.Server` 在部分异常情况下仍然返回 `200` 状态码的问题; +5. 修复 `gfpool` 模块中由于原子操作问题造成的高并发”内存泄露”问题; +6. 修复分组路由注册对象/控制时,方法 `Index` 的路由仅能通过 `/xxx/index` 访问的问题; +7. 修复模板引擎使用中,当不存在 `config.toml`(即使没使用)配置文件时的报错问题; +8. 其他一些修复; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" new file mode 100644 index 00000000000..f686482efc1 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.6 2019-04-09.md" @@ -0,0 +1,54 @@ +--- +slug: '/release/v1.6.0' +title: 'v1.6 2019-04-09' +sidebar_position: 10 +hide_title: true +keywords: [GoFrame,GoFrame框架,gcron,gredis,gcfg,gview,ghttp,gdb,gconv,gvalid] +description: 'v1.6版本带来了多项功能和改进,包括gcron定时任务模块的运行日志记录、gredis模块的全局分组配置、gcfg和gview模块的默认配置文件检索路径优化、ghttp模块的CORS特性、TLSConfig配置、以及gdb模块的新链式操作方法。修复了多个模块的资源竞争问题和转换失败问题,全面提升了系统的稳定性和安全性。' +--- + +### 新功能/改进 + +1. `gcron` 定时任务模块增加运行日志记录功能: [https://goframe.org/os/gcron/index](https://wiki.goframe.org/os/gcron/index) +2. `gredis` 增加全局分组配置功能,并增加更多的配置选项 `maxIdle/maxActive/idleTimeout/maxConnLifetime`: [https://goframe.org/database/gredis/index](https://wiki.goframe.org/database/gredis/index) +3. `gcfg` 模块增加更多的默认配置文件检索路径,并且增加全局分组配置特性,增加 `Instance` 单例方法: [https://goframe.org/os/gcfg/index](https://wiki.goframe.org/os/gcfg/index) +4. `gview` 模块增加更多的默认配置文件检索路径,并且增加 `Instance` 单例方法: [https://goframe.org/os/gview/index](https://wiki.goframe.org/os/gview/index) +5. `ghttp` 模块新功能及改进: + - 新增 `CORS` HTTP(S)跨域请求特性: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - 增加 `TLSConfig` 配置功能; + - 去掉路由注册方法的 `error` 返回值,当产生注册错误时直接终端打印错误/输出到日志文件; + - 增加在 `HTTP Code 302` 跳转时的 `Set-Cookie` 支持; + - 增加对 `SESSION ID` 的安全性检查; + - 增加对基于 `HTTPS` 的 `WebSocket` 支持( `WSS`): [https://goframe.org/net/ghttp/websocket/index](https://wiki.goframe.org/net/ghttp/websocket/index) + - `Request` 对象增加 `Error` 方法,用于输出自定义错误信息到 `WebServer` 错误日志中; + - 其他一些改进; +6. `gdb` 模块新功能及改进: + - 新增 `Instance` 单例管理方法; + - 新增 `Structs/Scan` 链式操作方法, `gdb.DB/TX` 新增 `GetStructs/GetScan` 方法,用于结果集 `struct`/ `slice` 映射转换: [https://goframe.org/database/gdb/chaining](https://wiki.goframe.org/database/gdb/chaining) + - 新增 `Safe` 链式操作方法(默认非并发安全),用于链式安全控制: [https://goframe.org/database/gdb/chaining](https://wiki.goframe.org/database/gdb/chaining) + - `Where` 链式操作方法改进: + - 方法支持任意的 `string/map/slice/struct/*struct` 类型; + - 逻辑调整,当链式操作中存在多个 `Where` 方法调用时,自动转换为 `And` 条件; + - 支持 `slice` 条件参数,常用在 `SELECT IN` 查询中,例如: `Where("uid IN(?)", g.Slice{1,2,3})`; + - 支持在 `map` 类型条件参数的 `key` 中传递条件,例如: `Where(g.Map{"uid>?", uid})`; +7. `gconv` 及 `gvalid` 模块改进并去掉对私有 `struct` 方法属性的转换/校验; +8. `gconv.Map` 转换方法新增对 `json tag`: `-`, `omitempty` 的支持: [https://goframe.org/util/gconv/map](https://wiki.goframe.org/util/gconv/map) +9. `gstr` 模块新增 `ReplaceI/ReplaceIByArray/ReplaceIByMap` 大小写非敏感替换方法; +10. `gutil` 模块增加 `IsEmpty` 方法,用于判断给定变量是否为空(整型0, 布尔false, slice/map长度为0, 其他为nil的情况,判断为空),并增加快捷方法 `g.IsEmpty`; +11. `gutil` 模块增加 `Export` 方法,用于导出返回格式化打印的变量内容字符串,并增加快捷方法 `g.Export`; +12. `gspath` 增加缓存及非缓存检索检索方法 `Search`/ `SearchWithCache`; +13. `gjson` 模块增加默认的 `UseNumber` 功能支持; +14. `gmap` 增加 `SetIfNotExistFunc/SetIfNotExistFuncLock` 方法; +15. 迁移 `greuseport` 模块到新的仓库: [https://github.com/gogf/greuseport](https://github.com/gogf/greuseport) +16. 大量的单元测试完善; + +### Bug Fix + +1. 修复 `gqueue` 模块的资源竞争问题; +2. 修复 `gconv.GTime` 转换失败问题; +3. 修复 `gconv.String` 在转换 `int` 参数时字节溢出问题; +4. 修复 `ghttp.Request` 的 `HTTP Basic Auth` 校验问题; +5. 修复 `gxml` 针对于非 `UTF-8` 编码内容转换的并发安全问题; +6. 修复 `gtime` 部分 `Format`( `G`& `j`)格式失效问题; +7. 修复 `gudp.Conn` 对象的 `RemoteAddr` 获取客户端连接地址方法问题; +8. 修复 `gmap/gcache` 模块的 `GetOrSetFuncLock` 方法,增加对回调方法返回值的 `nil` 判断,只有非nil返回值才会被保存; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" new file mode 100644 index 00000000000..3db40bd9643 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.7 2019-06-10.md" @@ -0,0 +1,69 @@ +--- +slug: '/release/v1.7.0' +title: 'v1.7 2019-06-10' +sidebar_position: 9 +hide_title: true +keywords: [GoFrame,GoFrame框架,glog,gmap,gtime,gdb,gtcp,gconv,ghttp,gtree] +description: 'GoFrame框架v1.7版本发布于2019年6月10日,包含多个模块的重构与改进,如glog日志模块的性能提升与异步输出,gmap支持多种数据结构,gtime增加更多PHP时间格式,gdb新增GetLastSql方法,gtcp支持TLS通信,gconv新增递归转换功能,同时修复了若干Bug,提升框架的稳定性和性能。' +--- + +### 新功能/改进 + +1. 重构改进 `glog` 模块: + - 去掉日志模块所有的锁机制,改为无锁设计,执行性能更加高效 + - 增加日志内容的异步输出特性: [https://goframe.org/os/glog/async](https://wiki.goframe.org/os/glog/async) + - 增加日志输出内容的 `Json` 格式支持: [https://goframe.org/os/glog/json](https://wiki.goframe.org/os/glog/json) + - 增加 `Flags` 额外特性支持,包括文件行号打印、自定义时间格式、异步输出等特性控制: [https://goframe.org/os/glog/flags](https://wiki.goframe.org/os/glog/flags) + - 增加 `Writer` 接口支持,便于开发者进行自定义的日志功能扩展,或者与第三方服务/模块对接集成: [https://goframe.org/os/glog/writer](https://wiki.goframe.org/os/glog/writer) + - 修改 `SetStdPrint` 方法名为 `SetStdoutPrint` + - 修改链式方法 `StdPrint` 方法名为 `Stdout` + - 标记淘汰 `*fln` 日志输出方法, `*f` 方法支持自动的换行输出 + - 新增更多的链式方法支持: [https://goframe.org/os/glog/chain](https://wiki.goframe.org/os/glog/chain) +2. 重构改进 `gmap` 模块: + - 增加更多数据格式支持: `HashMap`/ `ListMap`/ `TreeMap` + - 简化类型名称,如 `gmap.StringInterfaceMap` 简化为 `gmap.StrAnyMap` + - 改进 `Map/Keys/Values` 方法以提高性能 + - 修改 `BatchSet`/ `BatchRemove` 方法名为 `Sets`/ `Removes` + - 新增更多功能方法支持: [https://goframe.org/container/gmap/index](https://wiki.goframe.org/container/gmap/index) +3. 改进 `gtime` 时间模块: + - 增加并完善更多的类 `PHP` 时间格式支持 + - 新增更多功能方法,如 `FormatTo`/ `LayoutTo` 等等 + - 详见开发文档: [https://goframe.org/os/gtime/index](https://wiki.goframe.org/os/gtime/index) +4. 改进 `gdb` 数据库模块: + - 增加对继承结构体的数据转换支持: [https://goframe.org/database/gdb/senior](https://wiki.goframe.org/database/gdb/senior) + - 新增 `GetLastSql` 方法,用以在调试模式下获取最近一条执行的SQL语句 + - 其他的细节处理改进 +5. 改进 `gtcp` 通信模块: + - 完善处理细节,提高通信性能; + - 增加 `TLS` 服务端/客户端通信支持: [https://goframe.org/net/gtcp/tls](https://wiki.goframe.org/net/gtcp/tls) + - 增加简单协议支持,便于开发者封包/解包,并解决粘包/半包问题: [https://goframe.org/net/gtcp/conn/pkg](https://wiki.goframe.org/net/gtcp/conn/pkg) + - TCP服务端增加 `Close` 方法 + - 更多细节查看开发文档: [https://goframe.org/net/gtcp/index](https://wiki.goframe.org/net/gtcp/index) +6. 改进 `gconv` 类型转换模块 + - 修改 `gconv.TimeDuration` 转换方法名称为 `gconv.Duration` + - 新增 `gconv.StructDeep` 及 `gconv.MapDeep` 方法,支持递归转换 + - 详见开发文档: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct) +7. 改进 `ghttp` 模块: + - 日志输出增加 `http/https` 字段: [https://goframe.org/net/ghttp/logs](https://wiki.goframe.org/net/ghttp/logs) + - 新增 `ghttp.Server.SetKeepAlive` 设置方法,用以开启/关闭 `KeepAlive` 特性 + - 增加 `ghttp.Request.GetUrl` 方法,用以获取当前完整的URL请求地址 + - `ghttp.Client` 客户端支持开发者自定义 `Transport` 属性, `ghttp.Client.Post` 方法支持 `浏览器模式`: [https://goframe.org/net/ghttp/client](https://wiki.goframe.org/net/ghttp/client) +8. 新增 `gtree` 树形数据结构容器支持: [https://goframe.org/container/gtree/index](https://wiki.goframe.org/container/gtree/index) +9. 改进 `gudp` 通信模块,具体请参考开发文档: [https://goframe.org/net/gudp/index](https://wiki.goframe.org/net/gudp/index) +10. 改进 `gcfg` 配置管理模块,所有 `Get*` 方法增加默认值支持: [https://goframe.org/os/gcfg/index](https://wiki.goframe.org/os/gcfg/index) +11. `gredis` 模块新增 `DoVar`/ `ReceiveVar` 方法以便于开发者对执行结果进行灵活的数据格式转换: [https://goframe.org/database/gredis/index](https://wiki.goframe.org/database/gredis/index) +12. `gcache` 模块 `BatchSet`/ `BatchRemove` 方法名修改为 `Sets`/ `Removes` +13. 改进 `gjson`/ `gparser` 模块,增加更多方法: [https://goframe.org/encoding/gjson/index](https://wiki.goframe.org/encoding/gjson/index) +14. 改进 `gfile.MainPkgPath` 方法,以支持不同平台的开发环境; +15. 改进 `grpool` 协程池模块,提高执行性能: [https://goframe.org/os/grpool/index](https://wiki.goframe.org/os/grpool/index) +16. 改进 `TryCatch` 方法,当开发者不传递 `Catch` 参数时,默认抑制并忽略错误的处理 +17. 改进 `gmlock` 模块,增加 `TryLockFunc`/ `TryRLockFunc` 方法,并且为 `gmlock.Mutex` 高级互斥锁对象增加 `TryLockFunc`/ `TryRLockFunc` 方法 +18. 去除 `gvar.VarRead` 接口类型支持 + +### Bug Fix + +1. 解决 `gdb` 模块与其他第三方 `ORM` 模块同时使用的冲突; +2. 修复 `gcron.AddOnce` 方法的细节逻辑问题; +3. 修复内部 `empty` 模块的 `IsEmpty` 方法对结构体属性的空校验错误; +4. 修复 `gview` 模板引擎的并发安全问题; +5. 修复 `ghttp.Server` 的SESSION初始化过期时间问题; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" new file mode 100644 index 00000000000..190a1d78a18 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.8 2019-07-15.md" @@ -0,0 +1,78 @@ +--- +slug: '/release/v1.8.0' +title: 'v1.8 2019-07-15' +sidebar_position: 8 +hide_title: true +keywords: [GoFrame,gerror,gcharset,gmutex,glog,gdb,gconv,ghttp,gvalid,gtcp,gproc] +description: '新版GoFrame框架发布,带来多项改进与新功能。包括新增错误处理模块、字符编码转换支持、日志模块异步输出与自定义格式、数据库ORM错误处理与链式操作,以及数据转换与通信模块的增强。此更新还解决了多个模块的已知问题,提升框架整体性能与稳定性。' +--- + +### 新功能改进 + +1. 框架目前 `69` 个开发模块(不包括内部模块),原生代码 `65302` 行(不包含第三包依赖包),单元测试覆盖率达到 `77%`; +2. 新增 `gerror` 错误处理模块: [https://goframe.org/errors/gerror/index](https://wiki.goframe.org/errors/gerror/index) +3. 改进 `gcharset` 字符编码转换模块,支持更多的字符集: [https://goframe.org/encoding/gcharset/index](https://wiki.goframe.org/encoding/gcharset/index) +4. 新增 `gmutex` 模块,基于 `channel` 实现的高级互斥锁模块,支持更丰富的互斥锁特性: [https://goframe.org/os/gmutex/index](https://wiki.goframe.org/os/gmutex/index) +5. 改进 `glog` 日志模块: + - 新增日志异步输出特性: [https://goframe.org/os/glog/async](https://wiki.goframe.org/os/glog/async) + - 新增 `Flags` 额外功能特性: [https://goframe.org/os/glog/flags](https://wiki.goframe.org/os/glog/flags) + - 新增 `Json` 数据格式输出: [https://goframe.org/os/glog/json](https://wiki.goframe.org/os/glog/json) + - 新增自定义 `Writer` 接口特性: [https://goframe.org/os/glog/writer](https://wiki.goframe.org/os/glog/writer) + - **修改 `Backtrace` 名称为 `Stack`,并改进调用堆栈输出格式;** + - 新增 `Expose` 方法暴露内部默认 `Logger` 对象; +6. 改进 `gdb` 数据库ORM模块: + - **改进错误处理,当数据库操作没有查询到数据时, `error` 返回 `sql.ErrNoRows`**: [https://goframe.org/database/gdb/error](https://wiki.goframe.org/database/gdb/error) + - 改进 `Update`/ `Delete` 方法支持 `Order BY` 及 `LIMIT` 特性; + - 数据库链式操作及方法操作中,预处理变量参数支持 `slice` 参数: [https://goframe.org/database/gdb/model/model](https://wiki.goframe.org/database/gdb/model/model) + - **修改 `Priority` 权重配置名称为 `Weight`;** + - 新增 `Debug` 配置,可配置开启/关闭调试特性: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) + - 新增 `Offset` 方法,该方法为可选链式操作方法, `pgsql` 数据库可直接通过 `Limit` 方法第二个参数自动识别为 `Offset` 语法; + - 改进数据库动态切换特性,支持不同数据库类型的当前操作数据库切换; + - 改进简化配置文件结构: [https://goframe.org/database/gdb/config](https://wiki.goframe.org/database/gdb/config) +7. 改进 `gconv` 数据转换模块: + - 对结构体对象转换时支持更多的标签: `gconv/c/json`; + - 支持 `*struct/[]struct/[]*struct` 自动初始化创建对象/数组: [https://goframe.org/util/gconv/struct](https://wiki.goframe.org/util/gconv/struct) + - 新增 `Strusts/StrctsDeep` 方法,用于结构体数组的递归转换; + - 新增 `StructDeep` 方法,用于对结构体对象的递归转换; + - 新增 `MapDeep` 方法,用于对结构体属性的递归转换; +8. 改进 `ghttp` 模块: + - 改进 `ghttp` 模块的分组路由功能,完善逻辑处理细节,程序更加稳健; + - 改进 `ghttp.Request.Get*ToStruct` 方法,支持 `params/param/p` 标签,支持结构体递归转换,并且支持 `**struct` 参数的对象自动初始化; + - 改进 `ghttp.CORSDefault` 的跨域设置参数, `AllowOrigin` 参数调整为 `*`; +9. 改进 `gvalid` 数据校验模块: + - 增加对校验标签 `gvalid/valid/v` 的支持; + - 改进 `CheckStruct` 支持对结构体对象的递归校验: [https://goframe.org/util/gvalid/checkstruct](https://wiki.goframe.org/util/gvalid/checkstruct) +10. 改进 `gtcp` TCP通信模块: + - 改进通信包协议设计,更加轻量级高效: [https://goframe.org/net/gtcp/conn/pkg](https://wiki.goframe.org/net/gtcp/conn/pkg) + - 改进 `TCP Server` 增加对 `TLS` 的支持: [https://goframe.org/net/gtcp/tls](https://wiki.goframe.org/net/gtcp/tls) + - 增加 `Server.Cloce` 服务端关闭方法; +11. 改进 `gproc` 模块的通信数据结构,并使用 `gtcp` 的轻量级包协议重构消息发送逻辑; +12. 改进 `gqueue` 模块增加数据同步缓冲机制,解决大数据量下的内存占用及延迟问题; +13. 改进 `gmlock` 模块,使用 `gmutex` 模块替换内部的互斥锁,增加更多的操作方法; +14. 改进 `gaes` 加密模块,增加 `CBC` 模式的加密/解密方法: +15. 改进 `garray.Range/SubSlice` 方法,改进设计,提高性能; +16. 改进 `gjson`/ `gparser` 模块实现 `MarshalJSON` 接口以实现自定义的 `JSON` 数据格式转换; +17. **改进 `crypto` 分类下模块的方法返回值,增加 `error` 错误变量返回,以保证更严谨的接口设计风格;** +18. **改进 `gbase64` 模块,输入输出类型发生改变,并增加多个相关方法;** +19. 改进 `gflock` 修改方法名 `UnLock` 为 `Unlock`,新增 `IsRLocked` 方法; +20. 新增 `gfile.CopyFile/CopyDir` 方法,用于文件及目录的复制; +21. 改进 `gjson/gparser/gvar/gcfg` 模块增加更多的类型转换方法; +22. 改进 `gcache` 模块,过期时间参数支持 `time.Duration` 类型; +23. 新增 `internal/structs` 包,强大且便捷的结构体解析,并改进框架中所有涉及到结构体反射处理的模块; +24. 改进 `gbinary` 增加封装方法对 `BigEndian` 的支持; + +### Bug Fix + +1. 修复 `garray.Search` 返回值问题; +2. 修复 `garray.Contains`, `garray.New*ArrayFromCopy` 方法逻辑问题; +3. 修复 `gjson.Remove` 删除 `slice` 参数问题; +4. 修复 `gtree.AVLTree.Remove` 方法返回值问题; +5. 修复 `gqueue.Size` 不准确的大小问题; +6. 修复 `queue.Close` 问题; +7. 修复 `gcache.GetOrSetLockFunc` 当回调函数返回 `nil` 结果时的死锁问题; +8. 修复 `gfsnotify.Add` 方法默认递归监控添加失效问题; +9. 修复 `gdb.Model.Scan` 在某些参数类型下的失效问题; + +### 注意事项 + +请注意以上粗体文字部分,如有使用,在您升级时可能会出现不兼容性。 \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" new file mode 100644 index 00000000000..47885e7cc06 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/v1.9 2019-09-24.md" @@ -0,0 +1,95 @@ +--- +slug: '/release/v1.9.0' +title: 'v1.9 2019-09-24' +sidebar_position: 7 +hide_title: true +keywords: [GoFrame,GoFrame框架,gf命令行,gres资源管理器,gsession模块,gi18n国际化,gini模块,gcmd解析,gdebug模块,WebServer中间件] +description: 'GoFrame框架发布v1.9版本,包含重要的新特性和功能改进。新增了gf命令行工具、gres资源管理器和gsession模块等,并启用更便捷的WebServer路由注册方式。改进了ghttp和gdb等模块,调整container分类模块,同时修复了一些已知问题。' +--- + +该版本实际为 `v2.0` 的大版本发布,为避免 `go module` 机制严格要求 `v2` 版本以上需要修改 `import` 并加上 `v2` 后缀,因此使用了 `v1.9` 进行发布。 + +### 新特性 + +1. 新增 `gf` 命令行开发辅助工具: [https://goframe.org/toolchain/cli](https://wiki.goframe.org/toolchain/cli) +2. 新增 `gres` 资源管理器模块: [https://goframe.org/os/gres/index](https://wiki.goframe.org/os/gres/index) +3. 重构 `Session` 功能,新增 `gsession` 模块, `WebServer` 默认使用文件存储 `Session`: [https://goframe.org/net/ghttp/session](https://wiki.goframe.org/net/ghttp/session) +4. `WebServer` 新增中间件特性,并保留原有的HOOK设计,两者都可实现请求拦截、预处理等等特性: [https://goframe.org/net/ghttp/router/middleware](https://wiki.goframe.org/net/ghttp/router/middleware) +5. 新增 `gi18n` 国际化管理模块: [https://goframe.org/i18n/gi18n/index](https://wiki.goframe.org/i18n/gi18n/index) +6. 新增 `gini` 模块: [https://goframe.org/encoding/gini/index](https://wiki.goframe.org/encoding/gini/index) +7. `WebServer` 新增更便捷的层级路由注册方式: [https://goframe.org/net/ghttp/group/level](https://wiki.goframe.org/net/ghttp/group/level) +8. `gcmd` 命令行参数解析模块重构,增加 `Parser` 解析对象: [https://goframe.org/os/gcmd/index](https://wiki.goframe.org/os/gcmd/index) +9. 新增 `gdebug` 模块,用于堆栈信息获取/打印: [https://goframe.org/debug/gdebug/index](https://wiki.goframe.org/debug/gdebug/index) + +### 重大调整 + +1. 去掉 `1.x` 版本中已经被标记为 `deprecated` 的方法; +2. 调整 `container` 分类的容器模块,将默认并发安全参数调整为默认非并发安全; +3. 目录调整: + - 去掉 `third` 目录,统一使用 `go module` 管理包依赖; + - 将原有 `g` 目录中的模块移出到框架主目录,原有的 `g` 模块移动到 `frame/g` 目录; + - 将原有 `geg` 示例代码目录名称修改为 `.example`; + +### 功能改进 + +1. `ghttp` + - 改进 `Request` 参数解析方式: [https://goframe.org/net/ghttp/request](https://wiki.goframe.org/net/ghttp/request) + - 改进跨域请求功能,新增 `Origin` 设置及校验功能: [https://goframe.org/net/ghttp/cors](https://wiki.goframe.org/net/ghttp/cors) + - `Cookie` 及 `Session` 的 `TTL` 配置数据类型修改为 `time.Duration`; + - 新增允许同时通过 `Header/Cookie` 传递 `SessionId`; + - 新增 `ConfigFromMap/SetConfigWithMap` 方法,支持通过 `map` 参数设置WebServer; + - 改进默认的 `CORS` 配置,增加对常见 `Header` 参数的默认支持; + - 新增 `IsExitError` 方法,用于开发者自定义处理 `recover` 错误处理时,过滤框架本身自定义的非异常错误; + - 新增 `SetSessionStorage` 配置方法,用于开发者自定义 `Session` 存储; + - `ghttp.Request` 新增更多的参数获取方法; +2. `gdb` + - 增加对SQL中部分字段的自动转义( `Quote`)功能; + - 增加对方法操作以及链式操作中的 `slice` 参数的支持; + - 增加 `SetLogger` 方法用于开发者自定义数据库的日志打印; + - 增加 `Master/Slave` 方法,开发者可自主选择数据库操作执行的主从节点; + - 增加对 `mssql/pgsql/oracle` 的单元测试; + - 在 `debug` 模式支持完整带参数整合的SQL语句调试打印; + - 增加了更多的功能方法; +3. `glog` + - 新增 `Default` 方法用于获取默认的 `Logger` 对象; + - 新增 `StackWithFilter` 方法用于自定义堆栈打印过滤; + - 增加了更多的功能方法; +4. `gfile` + - 部分方法名称调整: `Get/PutBinContents` 修改为 `Get/PutBytes`; + - 增加 `ScanDirFile` 方法,用于仅检索文件目录,支持递归检索; + - 增加了更多的功能方法; +5. `gview` + - 新增 `SetI18n` 方法用于设置视图对象自定义的 `gi18n` 国际化对象; + - 新增对 `gres` 资源管理器的内置支持; +6. `gcompress` + - 增加 `zip` 算法的文件/目录的压缩/解压方法; + - 文件/目录压缩参数支持多路径; +7. `gconv` + - 改进对 `[]byte` 数据类型参数的支持; + - 新增 `Unsafe` 转换方法,开发者可在特定场景下使用,提高转换效率; + - 新增 `MapDeep/StructDeep/StructsDeep` 方法,支持递归 `struct` 转换; +8. `gjson/gparser` + - 改进类型自动识别功能; + - 新增 `LoadJson/LoadXml/LoadToml/LoadYaml/LoadIni` 方法用于自定义的数据类型内容加载; + - 增加了更多的功能方法; +9. `gerror` + - 改进错误堆栈获取逻辑; + - 增加了更多的功能方法; +10. `gmap/garray/gset/glist/gvar` + - 改进并发安全基准测试脚本; + - 修改 `garray.StringArray` 为 `garray.StrArray`; + - 增加了更多的功能方法; +11. `gdes` + - 规范化修改方法名称; +12. `gstr` + - 增加 `Camel/Snake` 相关命名转换方法; + - 增加了更多的功能方法; +13. `genv` + - 增加了更多的功能方法; + +### Bug Fix + +1. 修复 `gvalid` 校验 `struct` 时的 `tag` 自定义错误失效的问题; +2. 修复 `gcfg` 配置管理模块在特定情况下的内容类型自动识别失败问题; +3. 修复 `gqueue` 在用户主动关闭队列时的并发安全问题; +4. 修复 `session` 在开发者设置的 `TTL` 过大时的整型变量溢出问题; \ No newline at end of file diff --git "a/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" new file mode 100644 index 00000000000..ed1c4b18701 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x/\345\216\206\345\217\262\347\211\210\346\234\254\350\256\260\345\275\225 v1.x.md" @@ -0,0 +1,12 @@ +--- +slug: '/release/v1.x' +title: '历史版本记录 v1.x' +sidebar_position: 10000 +hide_title: true +keywords: [历史版本,版本记录,GoFrame框架,软件更新,功能改进,错误修复,v1.x,版本发布,升级说明,技术文档] +description: '了解GoFrame框架V1.x版本的历史变化,涵盖多个版本的功能改进和错误修复。详细记录每个版本的发布内容,帮助开发者快速了解新版本带来的变化和可能影响。' +--- + +import DocCardList from '@theme/DocCardList'; + + diff --git "a/versioned_docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" "b/versioned_docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" new file mode 100644 index 00000000000..b389a0fd980 --- /dev/null +++ "b/versioned_docs/version-2.8.x/release/\347\211\210\346\234\254\345\217\221\345\270\203\350\257\264\346\230\216.md" @@ -0,0 +1,75 @@ +--- +slug: '/release/note' +title: '版本发布说明' +sidebar_position: -10000 +hide_title: true +keywords: [GoFrame,GoFrame框架,版本命名规则,版本升级,兼容性保证,GNU风格,主版本号,子版本号,修正版本号,go modules] +description: 'GoFrame框架的版本发布说明,包括使用GNU风格的版本命名规则,如何保证版本间的兼容性,以及推荐的版本升级方式。此外,文中还介绍了master和develop分支的使用注意事项及操作方法,以确保开发者能够高效地管理和升级框架版本。' +--- + +## 版本命名 + +`GoFrame` 采用 `GNU` 风格的版本命名规则: + +`MajorVersion.MinorVersion.Revision` + +即: + +`主版本号.子版本号.修正版本号` + +如: + +`v0.0.1`, `v1.1.0`, `1.7.1` + +版本号说明: +- `MajorVersion` 主版本号: `GoFrame` 的版本号是从 `0` 开始的,从 `alpha` 到 `beta` 一直到现在的正式版本,主版本号表示一个全新的框架版本,例如:重大的重构、重大的`feature`改动、重大的不兼容性的变化。 +- `MinorVersion` 子版本号:发布较大的新 `feature` 功能,或者较大的重构或者模块变化,或者出现不兼容性改动,会增加子版本号;子版本的发布会伴随着完整的 `change log`,算是一个较大的版本发布,有仪式感。子版本通常几个月发布一次。 +- `Revision` 修正版本号:往往是 `bug fix`,或者增加较小的 `feature`,较小的功能改进或者模块变化,在保证完整向后兼容的前提下,会增加修正版本号。修正版本会不定时、较高频率进行发布。 +- 当主版本号增加时,子版本号及修正版本号置 `0`。 +- 当子版本号增加时,修正版本号置 `0`。 + +## 兼容性保证 + +`GoFrame` 承诺每一次修正版本的发布都会保证所有模块完整的向后兼容性,该版本可以随意升级。 + +由于 `GoFrame` 框架的发展非常迅速,陆续的新特性和新功能添加、更加完善细致的功能改进和锤炼, +因此子版本的发布不一定能保证所有模块完整的向后兼容性,但任何子版本的发布都会有完整的`change log`, +通知发布。如果有部分模块的兼容性调整,那么也会伴随着相关的重点标注说明,往往也有升级操作指导。 + +## 版本升级方式 + +`Golang` 项目开发不推荐直接使用 `vendor` 的方式,不推荐直接使用框架 `master` 分支的方式。推荐使用 `go modules` 的包管理方式,即使用 `go.mod` 来管理引用包的版本号。 + +每一次计划版本升级之前,在仓库查看最新的版本号: [https://github.com/gogf/gf/releases](https://github.com/gogf/gf/releases) ,选择一个需要升级的版本号,修改 `go.mod` 保存后,`Goland IDE`将会自动下载对应版本号的框架。 + +如果是升级到最新的框架版本,也可以通过`cli`工具在项目根目录下执行 `gf up -a` 进行完整的升级。 + +## 使用特定版本 + +### `master` 分支 + +`master` 分支是公测分支,所有的待发布代码先合并到 `master` 分支,进过一定时间的测试之后会打上 `tag` 正式发布。一些 `issue` 在修复完毕之后,开发者急于使用最新版本可以尝试使用 `master` 分支,更新方式如下: + +```bash +go get -u github.com/gogf/gf@master +``` + +也可以更新到特定的 `git reversion`: + +```bash +go get -u github.com/gogf/gf@4d3273379022a9518c1dc20ebada612cddeed764 +``` + +### `develop` 分支 + +`develop` 分支是开发分支,所有的开发特性 `feature` 分支会统一合并到 `develop` 分支上进行联调测试,没有问题之后再合并到 `master` 分支。注意, `develop` 分支不能用于生产环境。贡献者提交的 `PR` 也统一合并到 `develop` 分支。更新方式如下: + +```bash +go get -u github.com/gogf/gf@develop +``` + +也可以更新到特定的 `git reversion`: + +```bash +go get -u github.com/gogf/gf@0e58b6e95ba211fcde27954a68cbf4acadbb6bc9 +``` diff --git a/versioned_sidebars/version-2.8.x-sidebars.json b/versioned_sidebars/version-2.8.x-sidebars.json new file mode 100644 index 00000000000..ab657d2b12f --- /dev/null +++ b/versioned_sidebars/version-2.8.x-sidebars.json @@ -0,0 +1,50 @@ +{ + "mainSidebar": [ + { + "type": "autogenerated", + "dirName": "docs" + } + ], + "quickSidebar": [ + { + "type": "autogenerated", + "dirName": "quick" + } + ], + "courseSidebar": [ + "course/社区教程", + "course/视频入门教程", + "course/starbook/starbook", + "course/proxima-book/proxima-book" + ], + "courseStarBookSidebar": [ + { + "type": "autogenerated", + "dirName": "course/starbook" + } + ], + "courseProximaBookSidebar": [ + { + "type": "autogenerated", + "dirName": "course/proxima-book" + } + ], + "releaseSidebar": [ + { + "type": "autogenerated", + "dirName": "release" + } + ], + "examplesSidebar": [ + { + "type": "autogenerated", + "dirName": "examples" + } + ], + "communitySidebar": [ + { + "type": "autogenerated", + "dirName": "community" + } + ] +} diff --git a/versions.build.json b/versions.build.json index 0d13badf631..0a1218c5c48 100644 --- a/versions.build.json +++ b/versions.build.json @@ -1,4 +1,5 @@ [ + "2.8.x", "2.7.x", "2.6.x", "2.5.x", diff --git a/versions.json b/versions.json index bfbc014b18e..503123f381e 100644 --- a/versions.json +++ b/versions.json @@ -1,3 +1,3 @@ [ - "2.7.x" + "2.8.x" ]