0、索引

go-zero docker-compose 搭建课件服务(九):http统一返回和集成日志服务

0.1源码地址

https://github.com/liuyuede123/go-zero-courseware

1、http统一返回

一般返回中会有codemessagedata。当请求成功的时候code返回0或者200,message返回success,data为要获取的数据;当请求失败的时候code返回自定义的错误码,message返回展示给前端的错误信息,data为空。

我们将封装一个错误返回的函数,应用到api handler的返回

在user服务中创建了common文件夹,里面存一些公用的方法,创建response/response.go

package response

import (
"go-zero-courseware/user/common/xerr"
"net/http" "github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"github.com/zeromicro/go-zero/rest/httpx"
"google.golang.org/grpc/status"
) type Response struct {
Code uint32 `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
} //http返回
func HttpResult(r *http.Request, w http.ResponseWriter, resp interface{}, err error) { if err == nil {
//成功返回
r := &Response{
Code: 0,
Message: "success",
Data: resp,
}
httpx.WriteJson(w, http.StatusOK, r)
} else {
//错误返回
errcode := uint32(500)
errmsg := "服务器错误" causeErr := errors.Cause(err) // err类型
if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型
//自定义CodeError
errcode = e.GetErrCode()
errmsg = e.GetErrMsg()
} else {
if gstatus, ok := status.FromError(causeErr); ok { // grpc err错误
grpcCode := uint32(gstatus.Code())
errcode = grpcCode
errmsg = gstatus.Message()
}
} logx.WithContext(r.Context()).Errorf("【API-ERR】 : %+v ", err) httpx.WriteJson(w, http.StatusBadRequest, &Response{
Code: errcode,
Message: errmsg,
Data: nil,
})
}
}

创建xerr/errors.go文件,定义CodeError结构体

package xerr

import (
"fmt"
) /**
常用通用固定错误
*/
type CodeError struct {
errCode uint32
errMsg string
} //返回给前端的错误码
func (e *CodeError) GetErrCode() uint32 {
return e.errCode
} //返回给前端显示端错误信息
func (e *CodeError) GetErrMsg() string {
return e.errMsg
} func (e *CodeError) Error() string {
return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg)
} func NewErrCodeMsg(errCode uint32, errMsg string) *CodeError {
return &CodeError{errCode: errCode, errMsg: errMsg}
}

由于api一般调用的rpc的请求,获取到的错误无法展示给前端使用,我们会使用自定义的错误类型。当让rpc中的错误也可能是前端直接可以展示的错误,或者是数据库的某个异常抛出的错误,如果想区分这些错误,可以自己定义业务端code和message做下区分就行。这里我们统一api服务中处理。

当api或者rpc中有一些未知错误抛出的时候我们需要写入到日志中,包括具体的错误信息和堆栈信息。这些后续放到日志服务ELK中可以方便查看。

修改userinfohandler.go、userloginhandler.go、userregisterhandler.go中的返回

...

response.HttpResult(r, w, resp, err)

修改userinfologic.go

...

func (l *UserInfoLogic) UserInfo(req *types.UserInfoRequest) (resp *types.UserInfoResponse, err error) {
info, err := l.svcCtx.UserRpc.UserInfo(l.ctx, &userclient.UserInfoRequest{
Id: req.Id,
})
if err != nil {
// 自定义的错误返回
return nil, xerr.NewErrCodeMsg(500, "用户查询失败")
} return &types.UserInfoResponse{
Id: info.Id,
Username: info.Username,
LoginName: info.LoginName,
Sex: info.Sex,
}, nil
}

修改userloginlogic.go

...

func (l *UserLoginLogic) UserLogin(req *types.LoginRequest) (resp *types.LoginResponse, err error) {
login, err := l.svcCtx.UserRpc.Login(l.ctx, &userclient.LoginRequest{
LoginName: req.LoginName,
Password: req.Password,
})
if err != nil {
return nil, xerr.NewErrCodeMsg(500, "用户登录失败")
} now := time.Now().Unix()
login.Token, err = l.getJwtToken(l.svcCtx.Config.Auth.AccessSecret, now, l.svcCtx.Config.Auth.AccessExpire, int64(login.Id))
if err != nil {
// 返回错误信息,并打印堆栈信息到日志
return nil, errors.Wrapf(xerr.NewErrCodeMsg(5000, "token生成失败"), "loginName: %s,err:%v", req, err)
}
return &types.LoginResponse{
Id: login.Id,
Token: login.Token,
}, nil
} ...

修改userregisterlogic.go

...

func (l *UserRegisterLogic) UserRegister(req *types.RegisterRequest) (resp *types.RegisterResponse, err error) {
_, err = l.svcCtx.UserRpc.Register(l.ctx, &userclient.RegisterRequest{
LoginName: req.LoginName,
Username: req.Username,
Password: req.Password,
Sex: req.Sex,
})
if err != nil {
// 自定义的错误返回
return nil, xerr.NewErrCodeMsg(5000, "注册用户失败")
} return &types.RegisterResponse{}, nil
}

关于errors.Wrapf

第一个参数是错误信息,第二个是格式化之后的错误信息字符串,args是fromat中的动态参数。最终还是返回我们传入的error,但是会把堆栈信息也打印出来。这个为后面的日志服务做铺垫

func Wrapf(err error, format string, args ...interface{}) error {
if err == nil {
return nil
}
err = &withMessage{
cause: err,
msg: fmt.Sprintf(format, args...),
}
return &withStack{
err,
callers(),
}
}

关于鉴权

对于鉴权,如果鉴权失败,之前是直接返回401状态码,但是我们想同样的返回错误信息和message

此时就需要自定义一个鉴权失败的回调函数

我们在response.go中增加一个鉴权失败的回调函数

...

func JwtUnauthorizedResult(w http.ResponseWriter, r *http.Request, err error) {
httpx.WriteJson(w, http.StatusUnauthorized, &Response{401, "鉴权失败", nil})
}

然后在api入口程序user.go中修改代码如下

...

func main() {
flag.Parse() var c config.Config
conf.MustLoad(*configFile, &c) // 此处加入鉴权失败的回调
server := rest.MustNewServer(c.RestConf, rest.WithUnauthorizedCallback(response.JwtUnauthorizedResult))
defer server.Stop() ctx := svc.NewServiceContext(c)
handler.RegisterHandlers(server, ctx) fmt.Printf("Starting server at %s:%d...\n", c.Host, c.Port)
server.Start()
}

然后我们再看下user的rpc服务

这里我们会引入一个拦截器。什么是拦截器?

定义:UnaryServerInterceptor 提供了一个钩子来拦截服务器上一元 RPC 的执行。 信息包含拦截器可以操作的这个 RPC 的所有信息。 处理程序是包装器服务方法实现。 拦截器负责调用处理程序完成 RPC。

其实就是拦截handler做一些返回前和返回后的处理

我们需要在common中新增一个拦截器方法,新建文件rpcserver/rpcserver.go

package rpcserver

import (
"context"
"github.com/pkg/errors"
"github.com/zeromicro/go-zero/core/logx"
"go-zero-courseware/user/common/xerr"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) func LoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { resp, err = handler(ctx, req)
if err != nil {
causeErr := errors.Cause(err) // err类型
if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型
logx.WithContext(ctx).Errorf("【RPC-SRV-ERR】 %+v", err) //转成grpc err
err = status.Error(codes.Code(e.GetErrCode()), e.GetErrMsg())
} else {
logx.WithContext(ctx).Errorf("【RPC-SRV-ERR】 %+v", err)
} } return resp, err
}

然后在入口文件user.go中添加一个拦截器

...

s.AddUnaryInterceptors(rpcserver.LoggerInterceptor)

...

课件服务和上面类似,这里就不一一添加修改了

2、集成日志服务

我们需要搭建一个ELK体系的服务,流程图如下:

将会用到以下服务:

服务名 端口号
elasticsearch 9200
kibana 5601
go-stash
filebeat
zookeeper 2181
kafka 9092

docker-compose如下:

user服务中我们引入了日志地址,到我们的宿主机上。之所以这样做,是因为在mac系统上docker的日志文件路径和linux上的不一致。找了半天也没在mac上找到容器的日志。所以用户服务中的日志会写到文件中然后同步到宿主机的data/log目录下。

还有就是filebeat日志中,我们会从宿主机上的日志同步到filebeat指定目录。然后filebeat会同步到kafka

version: '3.5'
# 网络配置
networks:
backend:
driver: bridge # 服务容器配置
services:
etcd: # 自定义容器名称
build:
context: etcd # 指定构建使用的 Dockerfile 文件
environment:
- TZ=Asia/Shanghai
- ALLOW_NONE_AUTHENTICATION=yes
- ETCD_ADVERTISE_CLIENT_URLS=http://etcd:2379
ports: # 设置端口映射
- "2379:2379"
networks:
- backend
restart: always etcd-manage:
build:
context: etcd-manage
environment:
- TZ=Asia/Shanghai
ports:
- "7000:8080" # 设置容器8080端口映射指定宿主机端口,用于宿主机访问可视化web
depends_on: # 依赖容器
- etcd # 在 etcd 服务容器启动后启动
networks:
- backend
restart: always courseware-rpc: # 自定义容器名称
build:
context: courseware # 指定构建使用的 Dockerfile 文件
dockerfile: rpc/Dockerfile
environment: # 设置环境变量
- TZ=Asia/Shanghai
privileged: true
ports: # 设置端口映射
- "9400:9400" # 课件服务rpc端口
stdin_open: true # 打开标准输入,可以接受外部输入
tty: true
networks:
- backend
restart: always # 指定容器退出后的重启策略为始终重启 courseware-api: # 自定义容器名称
build:
context: courseware # 指定构建使用的 Dockerfile 文件
dockerfile: api/Dockerfile
environment: # 设置环境变量
- TZ=Asia/Shanghai
privileged: true
ports: # 设置端口映射
- "8400:8400" # 课件服务api端口
stdin_open: true # 打开标准输入,可以接受外部输入
tty: true
networks:
- backend
restart: always # 指定容器退出后的重启策略为始终重启 user-rpc: # 自定义容器名称
build:
context: user # 指定构建使用的 Dockerfile 文件
dockerfile: rpc/Dockerfile
environment: # 设置环境变量
- TZ=Asia/Shanghai
privileged: true
volumes:
- ./data/log/user-rpc:/var/log/go-zero/user-rpc # 日志的映射地址
ports: # 设置端口映射
- "9300:9300" # 课件服务rpc端口
stdin_open: true # 打开标准输入,可以接受外部输入
tty: true
networks:
- backend
restart: always # 指定容器退出后的重启策略为始终重启 user-api: # 自定义容器名称
build:
context: user # 指定构建使用的 Dockerfile 文件
dockerfile: api/Dockerfile
environment: # 设置环境变量
- TZ=Asia/Shanghai
privileged: true
volumes:
- ./data/log/user-api:/var/log/go-zero/user-api
ports: # 设置端口映射
- "8300:8300" # 课件服务api端口
stdin_open: true # 打开标准输入,可以接受外部输入
tty: true
networks:
- backend
restart: always # 指定容器退出后的重启策略为始终重启 elasticsearch:
build:
context: ./elasticsearch
environment:
- TZ=Asia/Shanghai
- discovery.type=single-node
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
privileged: true
ports:
- "9200:9200"
networks:
- backend
restart: always prometheus:
build:
context: ./prometheus
environment:
- TZ=Asia/Shanghai
privileged: true
volumes:
- ./prometheus/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml # 将 prometheus 配置文件挂载到容器里
- ./prometheus/target.json:/opt/bitnami/prometheus/conf/targets.json # 将 prometheus 配置文件挂载到容器里
ports:
- "9090:9090" # 设置容器9090端口映射指定宿主机端口,用于宿主机访问可视化web
networks:
- backend
restart: always grafana:
build:
context: ./grafana
environment:
- TZ=Asia/Shanghai
privileged: true
ports:
- "3000:3000"
networks:
- backend
restart: always jaeger:
build:
context: ./jaeger
environment:
- TZ=Asia/Shanghai
- SPAN_STORAGE_TYPE=elasticsearch
- ES_SERVER_URLS=http://elasticsearch:9200
- LOG_LEVEL=debug
privileged: true
ports:
- "6831:6831/udp"
- "6832:6832/udp"
- "5778:5778"
- "16686:16686"
- "4317:4317"
- "4318:4318"
- "14250:14250"
- "14268:14268"
- "14269:14269"
- "9411:9411"
networks:
- backend
restart: always kibana:
build:
context: ./kibana
environment:
- elasticsearch.hosts=http://elasticsearch:9200
- TZ=Asia/Shanghai
privileged: true
ports:
- "5601:5601"
networks:
- backend
restart: always
depends_on:
- elasticsearch go-stash:
build:
context: ./go-stash
environment:
- TZ=Asia/Shanghai
privileged: true
volumes:
- ./go-stash/go-stash.yml:/app/etc/config.yaml
networks:
- backend
restart: always
depends_on:
- elasticsearch
- kafka filebeat:
build:
context: ./filebeat
environment:
- TZ=Asia/Shanghai
entrypoint: "filebeat -e -strict.perms=false"
privileged: true
volumes:
- ./filebeat/filebeat.yml:/usr/share/filebeat/filebeat.yml
- ./data/log:/var/lib/docker/containers # 宿主机上的日志同步到filebeat指定目录
networks:
- backend
restart: always
depends_on:
- kafka zookeeper:
build:
context: ./zookeeper
environment:
- TZ=Asia/Shanghai
privileged: true
networks:
- backend
ports:
- "2181:2181"
restart: always kafka:
build:
context: ./kafka
ports:
- "9092:9092"
environment:
- KAFKA_ADVERTISED_HOST_NAME=kafka
- KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
- KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
- TZ=Asia/Shanghai
- ALLOW_PLAINTEXT_LISTENER=yes
restart: always
privileged: true
networks:
- backend
depends_on:
- zookeeper

(项目根目录下自行创建对应的Dokcerfile)

filebeat需要引入配置文件filebeat.yml如下:

其中filebeat需要从宿主机同步数据,就是上面用户服务中生成的日志文件,会同步到filebeat的对应文件中

拉取过来的文件会输出到kafka指定的topic中,我们这里定义的是courseware-log

filebeat.inputs:
- type: log
enabled: true
paths:
- /var/lib/docker/containers/*/*.log # 此为宿主机同步过来的日志文件 filebeat.config:
modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false processors:
- add_cloud_metadata: ~
- add_docker_metadata: ~ output.kafka:
enabled: true
hosts: ["kafka:9092"]
#要提前创建topic
topic: "courseware-log"
partition.hash:
reachable_only: true
compression: gzip
max_message_bytes: 1000000
required_acks: 1

用户服务中也需要修改etc下的user.yaml配置,增加日志的配置,输出到data/log目录下

Log:
Mode: file
Path: /var/log/go-zero/user-api
Level: error
Log:
Mode: file
Path: /var/log/go-zero/user-rpc
Level: error

我们启动下相关服务,请求下user-api的接口

然后回到项目中查看data/log中是否生成相关日志

日志正常输出,再到filebeat服务中,查看文件是否同步上去:

# 进入容器
docker exec -it 231bf79f3d5e21cea153bd94bf29693e67360113256e0e3c67a693e727d0b660 /bin/sh
# 查看目录
cd /var/lib/docker/containers
ls
user-api user-rpc

然后我们再到kafka的容器中

# 进入到容器
docker exec -it cb764aeb86e8296a805e47c85f65ac5334c3ed15630fe36e7a39a81ca1bad67f /bin/sh # 到bin目录下
cd /opt/bitnami/kafka/bin # 可以看到这些调试脚本
$ ls
connect-distributed.sh kafka-cluster.sh kafka-consumer-perf-test.sh kafka-get-offsets.sh kafka-producer-perf-test.sh kafka-server-stop.sh kafka-verifiable-consumer.sh zookeeper-server-start.sh
connect-mirror-maker.sh kafka-configs.sh kafka-delegation-tokens.sh kafka-leader-election.sh kafka-reassign-partitions.sh kafka-storage.sh kafka-verifiable-producer.sh zookeeper-server-stop.sh
connect-standalone.sh kafka-console-consumer.sh kafka-delete-records.sh kafka-log-dirs.sh kafka-replica-verification.sh kafka-streams-application-reset.sh trogdor.sh zookeeper-shell.sh
kafka-acls.sh kafka-console-producer.sh kafka-dump-log.sh kafka-metadata-shell.sh kafka-run-class.sh kafka-topics.sh windows
kafka-broker-api-versions.sh kafka-consumer-groups.sh kafka-features.sh kafka-mirror-maker.sh kafka-server-start.sh kafka-transactions.sh zookeeper-security-migration.sh
$

先看下有没有创建courseware-log的topic,如果没有就创建一个

$ ./kafka-topics.sh --bootstrap-server kafka:9092 --list
__consumer_offsets
courseware-log # 没有就创建,创建的命令。最新版的kafka不需要指定zookeeper
./kafka-topics.sh --create --bootstrap-server kafka:9092 --replication-factor 1 --partitions 1 --topic courseware-log # 建错了删除用这个
./kafka-topics.sh --delete --bootstrap-server kafka:9092 --topic courseware-log # 发布消息用这个
./kafka-console-producer.sh --broker-list kafka:9092 --topic courseware-log # 消费用这个
./kafka-console-consumer.sh --bootstrap-server kafka:9092 --topic courseware-log --from-beginning

我们执行消费脚本看下日志会不会过来。

现在还没有日志进来,我们请求一下接口让接口报错,可以看到日志开始消费了

到这里日志已经流转到kafka中了。

下面是go-stash从kafka拉取日志处理并保存到elasticsearch的流程:

go-stash需要引入配置文件go-stash.yml,内容如下:

参数可参考github go-stash

Clusters:
- Input:
Kafka:
Name: go-stash
Brokers:
- "kafka:9092"
Topics:
- courseware-log
Group: pro
Consumers: 16
Filters:
- Action: drop
Conditions:
- Key: k8s_container_name
Value: "-rpc"
Type: contains
- Key: level
Value: info
Type: match
Op: and
- Action: remove_field
Fields:
# - message
- _source
- _type
- _score
- _id
- "@version"
- topic
- index
- beat
- docker_container
- offset
- prospector
- source
- stream
- "@metadata"
- Action: transfer
Field: message
Target: data
Output:
ElasticSearch:
Hosts:
- "http://elasticsearch:9200"
Index: "courseware-{{yyyy-MM-dd}}"

问题:

但是这里mac上又遇到一个问题就是对接go-stash时mac上的docker中会报错

2022/09/08 21:51:10 {"@timestamp":"2022-09-08T21:51:10.346+08:00","level":"error","content":"cpu_linux.go:29 open cpuacct.usage_percpu: no such file or directory"}

具体可以看这里https://github.com/zeromicro/go-zero/issues/311 还没有找到好的解决办法。

后续:

之后又重启了下docker发现问题解决了,同步到es生效了。

接下来我们请求下用户服务的接口,到es查看,索引已经创建,错误信息已经写进去了



然后我们访问http://127.0.0.1:5601/进到kibana后台,点击Discover,并创建索引



搜索到课件服务的索引后点击下一步

选择@timestamp,点击创建

重新点击Discover之后可以看到课件的日志服务创建完成

go-zero docker-compose 搭建课件服务(九):http统一返回和集成日志服务的更多相关文章

  1. Istio入门实战与架构原理——使用Docker Compose搭建Service Mesh

    本文将介绍如何使用Docker Compose搭建Istio.Istio号称支持多种平台(不仅仅Kubernetes).然而,官网上非基于Kubernetes的教程仿佛不是亲儿子,写得非常随便,不仅缺 ...

  2. 利用 Docker Compose 搭建 SpringBoot 运行环境(超详细步骤和分析)

    0.前言 相信点进来看这篇文章的同学们已经对 Docker Dompose 有一定的了解了,下面,我们拿最简单的例子来介绍如何使用 Docker Compose 来管理项目. 本文例子: 一个应用服务 ...

  3. 使用Docker Compose搭建Service Mesh

    使用Docker Compose搭建Service Mesh 本文将介绍如何使用Docker Compose搭建Istio.Istio号称支持多种平台(不仅仅Kubernetes).然而,官网上非基于 ...

  4. Docker Compose 搭建 Redis Cluster 集群环境

    在前文<Docker 搭建 Redis Cluster 集群环境>中我已经教过大家如何搭建了,本文使用 Docker Compose 再带大家搭建一遍,其目的主要是为了让大家感受 Dock ...

  5. Docker Compose搭建Redis一主二从三哨兵高可用集群

    一.Docker Compose介绍 https://docs.docker.com/compose/ Docker官方的网站是这样介绍Docker Compose的: Compose是用于定义和运行 ...

  6. docker compose搭建redis7.0.4高可用一主二从三哨兵集群并整合SpringBoot【图文完整版】

    一.前言 redis在我们企业级开发中是很常见的,但是单个redis不能保证我们的稳定使用,所以我们要建立一个集群. redis有两种高可用的方案: High availability with Re ...

  7. Docker Compose搭建ELK

    Elasticsearch默认使用mmapfs目录来存储索引.操作系统默认的mmap计数太低可能导致内存不足,我们可以使用下面这条命令来增加内存: sysctl -w vm.max_map_count ...

  8. 基于Docker Compose搭建mysql主从复制(1主2从)

    系统环境 * 3 Ubuntu 16.04 mysql 8.0.12 docker 18.06.1-ce docker-compose 1.23.0-rc3 *3 ==> PS  ###我用的是 ...

  9. Docker Compose部署GitLab服务,搭建自己的代码托管平台(图文教程)

    场景 Docker-Compose简介与Ubuntu Server 上安装Compose: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...

随机推荐

  1. 4 亿用户,7W+ 作业调度难题,Bigo 基于 Apache DolphinScheduler 巧化解

    点击上方 蓝字关注我们 ✎ 编 者 按 成立于 2014 年的 Bigo,成立以来就聚焦于在全球范围内提供音视频服务.面对 4 亿多用户,Bigo 大数据团队打造的计算平台基于 Apache Dolp ...

  2. 重构、插件化、性能提升 20 倍,Apache DolphinScheduler 2.0 alpha 发布亮点太多!

    点击上方 蓝字关注我们 社区的小伙伴们,好消息!经过 100 多位社区贡献者近 10 个月的共同努力,我们很高兴地宣布 Apache DolphinScheduler 2.0 alpha 发布.这是 ...

  3. django中的静态文件

    静态文件 1.什么是静态文件 在django中静态文件是指那些图片.css样式.js样式.视频.音频等静态资源. 2.为什么要配置静态文件 这些静态文件往往不需要频繁的进行变动,如果我们将这些静态文件 ...

  4. Redis 08 地理位置

    参考源 https://www.bilibili.com/video/BV1S54y1R7SB?spm_id_from=333.999.0.0 版本 本文章基于 Redis 6.2.6 Redis 的 ...

  5. which 和 that 在定语从句中作介词宾语的用法

    关系代词在定语从句中作介词的宾语,且介词在关系代词之前时,关系代词应该用 which:介词在定语从句句末,关系代词可以用 that 或 which. (一)He teaches in a school ...

  6. C# winfrom ListView控件实现自由设置每一行字体及背景色等

    背景:公司经常会需要将日志信息,输出到一个对话框中显示出来.之前一直采用的listbox控件,操作简单,使用方便,但是遗憾的是,不能自由控制每一行的状态. 于是想了如下几个方案: (1)重绘listb ...

  7. 第七十八篇:写一个按需展示的文本框和按钮(使用ref)

    好家伙, 我们又又又来了一个客户 用户说: 我想我的页面上有一个搜索框, 当我不需要他的时候,它就是一个按钮 当我想要搜索的时候,我就点一下它, 然后按钮消失,搜索框出现, 当我在浏览其他东西时,这个 ...

  8. pathlib路径问题

    下面是我的文件框架 app ------ file1---- .py1 file2---- .py2 config.py 我在config文件中设置了变量参数 BASE_DIR = pathlib.P ...

  9. Web开发框架『express』的基本使用 —— { }

    基本 res.send([body]) 和 res.end([data] [, encoding]) 的区别 1.参数的区别: res.send([body]): body这个参数可以是[Buffer ...

  10. 回溯剪枝,dfs,bfs

    dfs: 给定一个整数n,将数字1~n排成一排,将会有很多种排列方法. 现在,请你按照字典序将所有的排列方法输出. 输入格式 共一行,包含一个整数n. 输出格式 按字典序输出所有排列方案,每个方案占一 ...