V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  ryalu  ›  全部回复第 1 页 / 共 20 页
回复总数  394
1  2  3  4  5  6  7  8  9  10 ... 20  
2 天前
回复了 zy0829 创建的主题 职场话题 作为普通的程序员怎么“舔”领导
《自洽的程序员》- https://www.v2ex.com/t/1104211
3 天前
回复了 ljzxloaf 创建的主题 程序员 protobuf 不支持泛型?
@wunonglin #16 这是 json 的,如果请求协议里是 application/proto 序列化编码方式就不行了。
em....公司**需求要支持 json 和 proto 两种序列化方式(屎山),分享下实现(性能不算好,能用就行),在 encode 的地方动态构造 proto 对象,这样就不用所有 response 包一层了。

```
package http

import (
"bytes"
"fmt"
"io"
"log/slog"
stdhttp "net/http"
"reflect"
"strconv"
"strings"
"sync"
"time"

"github.com/go-kratos/kratos/v2/encoding"
ejson "github.com/go-kratos/kratos/v2/encoding/json"
eproto "github.com/go-kratos/kratos/v2/encoding/proto"
"github.com/go-kratos/kratos/v2/errors"
"github.com/go-kratos/kratos/v2/transport/http"
pproto "github.com/golang/protobuf/proto"
"github.com/jhump/protoreflect/desc"
"github.com/jhump/protoreflect/desc/builder"
"github.com/jhump/protoreflect/dynamic"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/proto"

".../api/_gen/go/ecode"
)

var (
messagePool = &sync.Map{}
defaultErrorMessageDescriptor *desc.MessageDescriptor

// MarshalOptions is a configurable JSON format marshaller.
jsonMarshalOptions = protojson.MarshalOptions{
EmitUnpopulated: true,
}
// UnmarshalOptions is a configurable JSON format parser.
jsonUnmarshalOptions = protojson.UnmarshalOptions{
DiscardUnknown: true,
}

jsonCodecHeaders = []string{"application/json", "text/json"}
protoCodecHeaders = []string{"application/x-protobuf", "application/proto", "application/octet-stream"}
jsonCodec = encoding.GetCodec(ejson.Name)
protoCodec = encoding.GetCodec(eproto.Name)
registeredCodecs = make(map[string]encoding.Codec)
)

func init() {
for _, contentType := range jsonCodecHeaders {
registeredCodecs[contentType] = jsonCodec
}
for _, contentType := range protoCodecHeaders {
registeredCodecs[contentType] = protoCodec
}

defaultErrorMessageDescriptor, _ = builder.NewMessage("Response").
AddField(builder.NewField("code", builder.FieldTypeInt32()).SetNumber(1)).
AddField(builder.NewField("message", builder.FieldTypeString()).SetNumber(2)).
AddField(builder.NewField("ts", builder.FieldTypeInt64()).SetNumber(3)).Build()
}

func requestDecoder(r *http.Request, v any) error {
codec, _, ok := codecForRequest(r, "Content-Type")
if !ok {
return errors.BadRequest("CODEC", fmt.Sprintf("unregister Content-Type: %s", r.Header.Get("Content-Type")))
}

data, err := io.ReadAll(r.Body)
if err != nil {
return errors.BadRequest("CODEC", err.Error())
}

if len(data) == 0 {
return nil
}

if err = codec.Unmarshal(data, v); err != nil {
return errors.BadRequest("CODEC", fmt.Sprintf("body unmarshal err: %s, body: %s", err.Error(), string(data)))
}

r.Body = io.NopCloser(bytes.NewBuffer(data))
return nil
}

func ErrorEncoder(w http.ResponseWriter, r *http.Request, err error) {
er := errors.FromError(err)

// 获取业务错误码
code, ok := ecode.ServiceErrorReason_value[er.Reason]
if !ok || code == 0 { // 异常情况直接使用 errors.code
code = er.Code
}

codec, contentType, ok := codecForRequest(r, "Accept")
if !ok {
codec, contentType, _ = codecForRequest(r, "Content-Type")
}

switch codec.Name() {
case ejson.Name:
bt, err := encodeJSONResponse(code, er.Message, []byte("{}"))
if err != nil {
slog.Error("fail to encode json response: %v", err)
w.WriteHeader(stdhttp.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", contentType)
w.WriteHeader(stdhttp.StatusOK)
_, _ = w.Write(bt)
return

case eproto.Name:
bt, err := encodeProtoResponse(code, er.Message, nil)
if err != nil {
slog.Error("fail to encode json response: %v", err)
w.WriteHeader(stdhttp.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", contentType)
w.WriteHeader(stdhttp.StatusOK)
_, _ = w.Write(bt)
return

}

return
}

func responseEncoder(w http.ResponseWriter, r *http.Request, i any) error {
codec, contentType, ok := codecForRequest(r, "Accept")
if !ok {
codec, contentType, _ = codecForRequest(r, "Content-Type")
}

m, ok := i.(proto.Message)
if !ok {
return errors.BadRequest("CODEC", fmt.Sprintf("response is not proto.Message: %s", reflect.TypeOf(i)))
}

switch codec.Name() {
case ejson.Name:
data, err := jsonMarshalOptions.Marshal(m)
if err != nil {
return err
}

bt, err := encodeJSONResponse(200, "success", data)
if err != nil {
return err
}

w.Header().Set("Content-Type", contentType)
w.WriteHeader(stdhttp.StatusOK)
_, _ = w.Write(bt)
return nil

case eproto.Name:
bt, err := encodeProtoResponse(200, "success", m)
if err != nil {
return err
}

w.Header().Set("Content-Type", contentType)
w.WriteHeader(stdhttp.StatusOK)
_, _ = w.Write(bt)
return nil

}
return nil
}

// get codec for request
func codecForRequest(r *http.Request, name string) (encoding.Codec, string, bool) {
contentType := r.Header.Get(name)
right := strings.Index(contentType, ";")
if right == -1 {
right = len(contentType)
}

c := contentType[:right]
codec := registeredCodecs[c]
if codec != nil {
return codec, c, true
}
return jsonCodec, "application/json", false
}

func encodeJSONResponse(code int32, message string, data []byte) ([]byte, error) {
buf := new(bytes.Buffer)
buf.WriteString("{\"code\":")
buf.WriteString(strconv.FormatInt(int64(code), 10))
buf.WriteString(",\"message\":\"")
buf.WriteString(message)
buf.WriteString("\",\"ts\":" + strconv.FormatInt(time.Now().Unix(), 10) + ",")
buf.WriteString("\"data\":")
buf.Write(data)
buf.WriteString("}")
return buf.Bytes(), nil
}

func encodeProtoResponse(code int32, message string, data proto.Message) ([]byte, error) {
build, err := getProtoBuilder(data)
if err != nil {
return nil, err
}

response := dynamic.NewMessage(build)
response.SetFieldByNumber(1, code)
response.SetFieldByNumber(2, message)
response.SetFieldByNumber(3, int32(time.Now().Unix()))
if data != nil {
_ = response.TrySetFieldByNumber(4, data)
}
return response.Marshal()
}

func getProtoBuilder(message proto.Message) (*desc.MessageDescriptor, error) {
if message == nil {
return defaultErrorMessageDescriptor, nil
}

key := message.ProtoReflect().Type().Descriptor().Name()
v, ok := messagePool.Load(key)
if !ok || v == nil {
anyDesc, err := desc.LoadMessageDescriptorForMessage(pproto.MessageV1(message))
if err != nil {
return nil, fmt.Errorf("loadMessageDescriptorForMessage err: %w", err)
}
build, err := builder.NewMessage("Response").
AddField(builder.NewField("code", builder.FieldTypeInt32()).SetNumber(1)).
AddField(builder.NewField("message", builder.FieldTypeString()).SetNumber(2)).
AddField(builder.NewField("ts", builder.FieldTypeInt64()).SetNumber(3)).
AddField(builder.NewField("data", builder.FieldTypeImportedMessage(anyDesc)).SetNumber(4)).Build()
if err != nil {
return nil, fmt.Errorf("build new message err: %w", err)
}

messagePool.Store(key, build)
return build, nil
}
return v.(*desc.MessageDescriptor), nil
}

```
建议直接一次两个阻生都拔了,省得折腾两次。拔这种牙尽量去正规医院挂个主任医生的号,之前一次拔过三个,两个阻生和上面一个出来的,费用靠近 2000 左右
6 天前
回复了 cxhello 创建的主题 Go 编程语言 Go 框架使用调研
@sky3hao9 #44 如果能把框架内集成的 log 那一套直接替换成 slog 就更爽了
感谢分享,希望能有后续的更新~例如招聘、面试过程等等
10 天前
回复了 CKAJ555 创建的主题 生活 兄弟们 大家平时都用类似大宝的润肤霜吗
标婷维生素 E 乳、京润珍珠保湿霜
来自果子的人文关怀

https://i.imgur.com/mjQXjT2.png
27 天前
回复了 Flowing 创建的主题 生活 我想问问 有多少人是和爱情结婚的
相信,但不觉得会发生在自己身上
32 天前
回复了 notapple 创建的主题 macOS 求推荐 macOS 必装软件
推荐两个小而美的 App
PrettyClean: 轻量小巧的清理、卸载工具。
pap.er:壁纸应用
既然你是程序员而且能上 v 站,就说明你的认知能力已经超过大部分人了。
先基于自己财产状况想好自己能承受的出资额度呗,然后再和父母好好沟通下,交个底自己能出多少,看看其他人能帮出多少借多少还差多少,帮规划下这么多钱能建到什么程度(不一定要一次投入几十万把房子建好,我们那边去年邻居光水泥现浇的一层大平层架子也就在十七八万左右吧),网上搜集下优化成本的技巧.....

emm...看情况父母拉扯大三个确实也挺不容易的,毕竟不是每个父母都能成为百万富翁,可能父母觉得孩子都大了自己也老了,趁现在自己还稍微能动,最后帮他们把个房子弄起来,不然以后要是带人回来,房间都没得一个.....都是至亲,不要太看重钱,毕竟当你真正有难的时候,他们才是真心且帮的上你的一群人。

以上仅是我看帖子所想到的片面之词,仅供参考。轻喷(手动狗头.jpg )
35 天前
回复了 Jayleonc 创建的主题 MacBook Pro 月薪 1.4w 的程序员,应该升级 Mac 吗?
@wsssss #34 好兄弟友好交流哦,No 爹味说教。25 有女朋友有贷款就不能有点喜好追求了吗?就只能吃泡面不能吃肉了吗?
35 天前
回复了 miaowo 创建的主题 生活 年纪轻轻患癌后续
祝好!一起享受好当下的每一天!
35 天前
回复了 Jayleonc 创建的主题 MacBook Pro 月薪 1.4w 的程序员,应该升级 Mac 吗?
@ryalu #16 (继 16 楼,点错发早了.....)这不是买房买车,没必要那么纠结,按照苹果的贬值速度,你买回来就算没有发挥出买它的想法价值(可以自己折腾本地搭个小模型玩玩,下个 orbstack 或者虚拟机多折腾折腾 k8s ),一年不就相当于亏了个两三千块钱吗?更何况你是在置换。

既然你都选择买了,那就不如好好的肯定自己,就把它当作一次给自己的奖励以及勉励,当你打开它的时候时刻记得买它的初衷,如何真的可以因为这个提升了自己那你会不会觉得很值?你现在问的这个问题在我看来毫无意义,这里每个人的消费观都不一样,有的人喜欢自己折腾虚拟机,有的人就是 mac 死忠粉,
35 天前
回复了 Jayleonc 创建的主题 MacBook Pro 月薪 1.4w 的程序员,应该升级 Mac 吗?
遵循自己的内心,既然想买就干脆买了,这不是买房、买车。🚗但想好你要买它的缘由,
37 天前
回复了 BlackHole1 创建的主题 分享创造 一款全新的工作流 IDE
支持一下
建议别浪费时间,直接考公题刷起来。毕业即上岸比啥实习都有用
39 天前
回复了 congfan 创建的主题 MacBook Pro Macbook Pro 配置请教
最近刚换,内存上了 48 G ,核没选配,日常开发( chrome + idea + k8s + vm ....)都是吃内存比较多,所以上了大内存,正常使用的时候 app 随便开还是挺爽的
1  2  3  4  5  6  7  8  9  10 ... 20  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1009 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 38ms · UTC 23:00 · PVG 07:00 · LAX 16:00 · JFK 19:00
Developed with CodeLauncher
♥ Do have faith in what you're doing.