feat: 添加评价微服务,支持密封互评与订单状态联动

This commit is contained in:
zetaloop
2026-04-24 10:43:15 +08:00
parent 3b56910100
commit 6edf15996c
53 changed files with 7891 additions and 39 deletions
+12
View File
@@ -0,0 +1,12 @@
package config
import (
"github.com/zeromicro/go-zero/rest"
"github.com/zeromicro/go-zero/zrpc"
)
type Config struct {
rest.RestConf
ReviewRpcConf zrpc.RpcClientConf
OrderRpcConf zrpc.RpcClientConf
}
@@ -0,0 +1,32 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"juwan-backend/app/review/api/internal/logic/review"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
)
// 获取订单评价
func GetOrderReviewsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.GetOrderReviewsReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := review.NewGetOrderReviewsLogic(r.Context(), svcCtx)
resp, err := l.GetOrderReviews(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
@@ -0,0 +1,32 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"juwan-backend/app/review/api/internal/logic/review"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
)
// 获取公开评价列表
func ListReviewsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.PageReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := review.NewListReviewsLogic(r.Context(), svcCtx)
resp, err := l.ListReviews(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
@@ -0,0 +1,32 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"juwan-backend/app/review/api/internal/logic/review"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
)
// 获取用户收到的评价
func ListUserReviewsHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ListUserReviewsReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := review.NewListUserReviewsLogic(r.Context(), svcCtx)
resp, err := l.ListUserReviews(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
@@ -0,0 +1,32 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"net/http"
"github.com/zeromicro/go-zero/rest/httpx"
"juwan-backend/app/review/api/internal/logic/review"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
)
// 提交评价
func SubmitReviewHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.SubmitReviewReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := review.NewSubmitReviewLogic(r.Context(), svcCtx)
resp, err := l.SubmitReview(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}
+51
View File
@@ -0,0 +1,51 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.10.1
package handler
import (
"net/http"
review "juwan-backend/app/review/api/internal/handler/review"
"juwan-backend/app/review/api/internal/svc"
"github.com/zeromicro/go-zero/rest"
)
func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) {
server.AddRoutes(
[]rest.Route{
{
// 提交评价
Method: http.MethodPost,
Path: "/orders/:id/review",
Handler: review.SubmitReviewHandler(serverCtx),
},
{
// 获取订单评价
Method: http.MethodGet,
Path: "/orders/:id/reviews",
Handler: review.GetOrderReviewsHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
server.AddRoutes(
[]rest.Route{
{
// 获取公开评价列表
Method: http.MethodGet,
Path: "/reviews",
Handler: review.ListReviewsHandler(serverCtx),
},
{
// 获取用户收到的评价
Method: http.MethodGet,
Path: "/users/:id/reviews",
Handler: review.ListUserReviewsHandler(serverCtx),
},
},
rest.WithPrefix("/api/v1"),
)
}
@@ -0,0 +1,54 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"context"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
reviewpb "juwan-backend/app/review/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type GetOrderReviewsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 获取订单评价
func NewGetOrderReviewsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetOrderReviewsLogic {
return &GetOrderReviewsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *GetOrderReviewsLogic) GetOrderReviews(req *types.GetOrderReviewsReq) (resp *types.ReviewListResp, err error) {
orderId := req.Id
result, err := l.svcCtx.ReviewRpc.SearchReviews(l.ctx, &reviewpb.SearchReviewsReq{
Offset: 0,
Limit: 10,
OrderId: &orderId,
})
if err != nil {
return nil, err
}
items := make([]types.Review, 0, len(result.GetReviews()))
for _, r := range result.GetReviews() {
items = append(items, toAPIReview(r))
}
return &types.ReviewListResp{
Items: items,
Meta: types.PageMeta{
Total: int64(len(items)),
Offset: 0,
Limit: 10,
},
}, nil
}
@@ -0,0 +1,28 @@
package review
import (
"time"
"juwan-backend/app/review/api/internal/types"
reviewpb "juwan-backend/app/review/rpc/pb"
)
func formatUnix(ts int64) string {
if ts <= 0 {
return ""
}
return time.Unix(ts, 0).UTC().Format(time.RFC3339)
}
func toAPIReview(r *reviewpb.Reviews) types.Review {
return types.Review{
Id: r.GetId(),
OrderId: r.GetOrderId(),
FromUserId: r.GetFromUserId(),
FromUserName: r.GetFromUserName(),
Rating: int(r.GetRating()),
Content: r.GetContent(),
Sealed: r.GetSealed(),
CreatedAt: formatUnix(r.GetCreatedAt()),
}
}
@@ -0,0 +1,54 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"context"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
reviewpb "juwan-backend/app/review/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListReviewsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 获取公开评价列表
func NewListReviewsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListReviewsLogic {
return &ListReviewsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListReviewsLogic) ListReviews(req *types.PageReq) (resp *types.ReviewListResp, err error) {
sealed := false
result, err := l.svcCtx.ReviewRpc.SearchReviews(l.ctx, &reviewpb.SearchReviewsReq{
Offset: req.Offset,
Limit: req.Limit,
Sealed: &sealed,
})
if err != nil {
return nil, err
}
items := make([]types.Review, 0, len(result.GetReviews()))
for _, r := range result.GetReviews() {
items = append(items, toAPIReview(r))
}
return &types.ReviewListResp{
Items: items,
Meta: types.PageMeta{
Total: int64(len(items)),
Offset: req.Offset,
Limit: req.Limit,
},
}, nil
}
@@ -0,0 +1,56 @@
// Code scaffolded by goctl. Safe to edit.
// goctl 1.10.1
package review
import (
"context"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
reviewpb "juwan-backend/app/review/rpc/pb"
"github.com/zeromicro/go-zero/core/logx"
)
type ListUserReviewsLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// 获取用户收到的评价
func NewListUserReviewsLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ListUserReviewsLogic {
return &ListUserReviewsLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *ListUserReviewsLogic) ListUserReviews(req *types.ListUserReviewsReq) (resp *types.ReviewListResp, err error) {
sealed := false
toUserId := req.Id
result, err := l.svcCtx.ReviewRpc.SearchReviews(l.ctx, &reviewpb.SearchReviewsReq{
Offset: req.Offset,
Limit: req.Limit,
ToUserId: &toUserId,
Sealed: &sealed,
})
if err != nil {
return nil, err
}
items := make([]types.Review, 0, len(result.GetReviews()))
for _, r := range result.GetReviews() {
items = append(items, toAPIReview(r))
}
return &types.ReviewListResp{
Items: items,
Meta: types.PageMeta{
Total: int64(len(items)),
Offset: req.Offset,
Limit: req.Limit,
},
}, nil
}
@@ -0,0 +1,104 @@
package review
import (
"context"
"errors"
"time"
"juwan-backend/app/order/rpc/orderservice"
"juwan-backend/app/review/api/internal/svc"
"juwan-backend/app/review/api/internal/types"
reviewpb "juwan-backend/app/review/rpc/pb"
"juwan-backend/common/utils/contextj"
"github.com/zeromicro/go-zero/core/logx"
)
type SubmitReviewLogic struct {
logx.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
func NewSubmitReviewLogic(ctx context.Context, svcCtx *svc.ServiceContext) *SubmitReviewLogic {
return &SubmitReviewLogic{
Logger: logx.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *SubmitReviewLogic) SubmitReview(req *types.SubmitReviewReq) (resp *types.EmptyResp, err error) {
uid, err := contextj.UserIDFrom(l.ctx)
if err != nil {
return nil, err
}
orderResp, err := l.svcCtx.OrderRpc.GetOrdersById(l.ctx, &orderservice.GetOrdersByIdReq{Id: req.Id})
if err != nil {
return nil, err
}
order := orderResp.GetOrders()
if order == nil {
return nil, errors.New("order not found")
}
if order.GetStatus() != "pending_review" {
return nil, errors.New("order is not in pending_review status")
}
var fromUserId, toUserId int64
if uid == order.GetConsumerId() {
fromUserId = order.GetConsumerId()
toUserId = order.GetPlayerId()
} else if uid == order.GetPlayerId() {
fromUserId = order.GetPlayerId()
toUserId = order.GetConsumerId()
} else {
return nil, errors.New("not a participant of this order")
}
_, err = l.svcCtx.ReviewRpc.AddReviews(l.ctx, &reviewpb.AddReviewsReq{
OrderId: req.Id,
FromUserId: fromUserId,
ToUserId: toUserId,
Rating: int32(req.Rating),
Content: req.Content,
Sealed: true,
})
if err != nil {
return nil, err
}
existing, err := l.svcCtx.ReviewRpc.SearchReviews(l.ctx, &reviewpb.SearchReviewsReq{
Offset: 0,
Limit: 2,
OrderId: &req.Id,
})
if err != nil {
return nil, err
}
if len(existing.GetReviews()) >= 2 {
now := time.Now().Unix()
sealed := false
for _, r := range existing.GetReviews() {
_, _ = l.svcCtx.ReviewRpc.UpdateReviews(l.ctx, &reviewpb.UpdateReviewsReq{
Id: r.GetId(),
Sealed: &sealed,
UnsealedAt: &now,
})
}
completedStatus := "completed"
completedAt := now
updatedAt := now
_, _ = l.svcCtx.OrderRpc.UpdateOrders(l.ctx, &orderservice.UpdateOrdersReq{
Id: req.Id,
Status: &completedStatus,
CompletedAt: &completedAt,
UpdatedAt: &updatedAt,
})
}
return &types.EmptyResp{}, nil
}
@@ -0,0 +1,23 @@
package svc
import (
"juwan-backend/app/order/rpc/orderservice"
"juwan-backend/app/review/api/internal/config"
"juwan-backend/app/review/rpc/reviewservice"
"github.com/zeromicro/go-zero/zrpc"
)
type ServiceContext struct {
Config config.Config
ReviewRpc reviewservice.ReviewService
OrderRpc orderservice.OrderService
}
func NewServiceContext(c config.Config) *ServiceContext {
return &ServiceContext{
Config: c,
ReviewRpc: reviewservice.NewReviewService(zrpc.MustNewClient(c.ReviewRpcConf)),
OrderRpc: orderservice.NewOrderService(zrpc.MustNewClient(c.OrderRpcConf)),
}
}
+72
View File
@@ -0,0 +1,72 @@
// Code generated by goctl. DO NOT EDIT.
// goctl 1.10.1
package types
type EmptyResp struct {
}
type GetOrderReviewsReq struct {
ReviewPathId
}
type ListUserReviewsReq struct {
ReviewPathId
PageReq
}
type PageMeta struct {
Total int64 `json:"total"`
Offset int64 `json:"offset"`
Limit int64 `json:"limit"`
}
type PageReq struct {
Offset int64 `form:"offset,default=0"`
Limit int64 `form:"limit,default=20"`
}
type Review struct {
Id int64 `json:"id"`
OrderId int64 `json:"orderId"`
FromUserId int64 `json:"fromUserId"`
FromUserName string `json:"fromUserName"`
Rating int `json:"rating"`
Content string `json:"content"`
Sealed bool `json:"sealed"`
CreatedAt string `json:"createdAt"`
}
type ReviewListResp struct {
Items []Review `json:"items"`
Meta PageMeta `json:"meta"`
}
type ReviewPathId struct {
Id int64 `path:"id"`
}
type SimpleUser struct {
Id string `json:"id"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
}
type SubmitReviewReq struct {
ReviewPathId
Rating int `json:"rating"`
Content string `json:"content,optional"`
}
type UserProfile struct {
Id string `json:"id"`
Username string `json:"username"`
Nickname string `json:"nickname"`
Avatar string `json:"avatar"`
Role string `json:"role"` // consumer, player, owner, admin
VerifiedRoles []string `json:"verifiedRoles"`
VerificationStatus map[string]string `json:"verificationStatus"`
Phone string `json:"phone,optional"`
Bio string `json:"bio,optional"`
CreatedAt string `json:"createdAt"`
}