diff --git a/.gitignore b/.gitignore index 0d6d3ba3..9a4b0c48 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ *.vsix __debug_bin +__debug_bin* .idea .vscode diff --git a/constants/order.go b/constants/order.go index 64549a44..08fe2bac 100644 --- a/constants/order.go +++ b/constants/order.go @@ -121,3 +121,8 @@ const ( // 云仓完成 STATUS_FONT_COMPLETED_CLOUD Order = 8 ) + +// 订单取消时间 +const ( + CANCLE_ORDER_EXPIRE int64 = 48 * 3600 +) diff --git a/constants/paging.go b/constants/paging.go index 638a38de..a700ca96 100644 --- a/constants/paging.go +++ b/constants/paging.go @@ -8,3 +8,6 @@ const DEFAULT_PAGE_SIZE = 20 // 最大每页显示数量 const MAX_PAGE_SIZE = 300 + +// 最大分页 +const MAX_PAGE = 100 diff --git a/model/gmodel/fs_order_detail_logic.go b/model/gmodel/fs_order_detail_logic.go index 840a57f9..0513dfa6 100755 --- a/model/gmodel/fs_order_detail_logic.go +++ b/model/gmodel/fs_order_detail_logic.go @@ -24,3 +24,7 @@ func (d *FsProductDesignModel) FindOne(ctx context.Context, id int64, userId int err = d.db.WithContext(ctx).Model(&FsProductDesign{}).Where("`id` = ? and `user_id` = ? and `status` = ?", id, userId, 1).First(&resp).Error return resp, err } + +func (m *FsOrderDetailModel) TableName() string { + return m.name +} diff --git a/model/gmodel/fs_order_logic.go b/model/gmodel/fs_order_logic.go index ef332996..eda7880c 100755 --- a/model/gmodel/fs_order_logic.go +++ b/model/gmodel/fs_order_logic.go @@ -3,8 +3,11 @@ package gmodel import ( "context" "fusenapi/constants" + "reflect" "time" + "fusenapi/utils/handler" + "gorm.io/gorm" ) @@ -51,3 +54,75 @@ func (o *FsOrderModel) FindLastSuccessOneOrder(ctx context.Context, userId int64 err = o.db.WithContext(ctx).Model(&FsOrder{}).Where("`user_id` = ? and `status` > ?", userId, statusGt).Order("id DESC").Take(&order).Error return order, err } + +// 分页查询的订单 +func (o *FsOrderModel) FindPageListByPage(ctx context.Context, rowBuilder *gorm.DB, page *int64, pageSize *int64, filterMap map[string]string, orderBy string) ([]*FsOrderRel, error) { + var resp []*FsOrderRel + // 过滤 + if filterMap != nil { + rowBuilder = rowBuilder.Scopes(handler.FilterData(filterMap)) + } + + // 排序 + if orderBy != "" { + var fieldsMap = make(map[string]struct{}) + s := reflect.TypeOf(&FsOrder{}).Elem() //通过反射获取type定义 + for i := 0; i < s.NumField(); i++ { + fieldsMap[s.Field(i).Tag.Get("json")] = struct{}{} + } + rowBuilder = rowBuilder.Scopes(handler.OrderCheck(orderBy, fieldsMap)) + } + + // 分页 + rowBuilder = rowBuilder.Scopes(handler.Paginate(page, pageSize)) + + // 结果 + result := rowBuilder.WithContext(ctx).Find(&resp) + if result.Error != nil { + return nil, result.Error + } else { + return resp, nil + } +} + +type FsOrderRel struct { + FsOrder + FsOrderDetails []FsOrderDetails `gorm:"foreignKey:order_id;references:id"` +} + +type FsOrderDetails struct { + FsOrderDetail + FsOrderDetailTemplateInfo FsOrderDetailTemplate `gorm:"foreignKey:id;references:order_detail_template_id"` + FsProductInfo FsProduct `gorm:"foreignKey:id;references:product_id"` +} + +func (m *FsOrderModel) RowSelectBuilder(selectData []string) *gorm.DB { + var rowBuilder = m.db.Table(m.name) + + if selectData != nil { + rowBuilder = rowBuilder.Select(selectData) + } else { + rowBuilder = rowBuilder.Select("*") + } + return rowBuilder +} + +func (m *FsOrderModel) FindCount(ctx context.Context, countBuilder *gorm.DB, filterMap map[string]string) (int64, error) { + var count int64 + + // 过滤 + if filterMap != nil { + countBuilder = countBuilder.Scopes(handler.FilterData(filterMap)) + } + + result := countBuilder.WithContext(ctx).Limit(1).Count(&count) + if result.Error != nil { + return 0, result.Error + } else { + return count, nil + } +} + +func (m *FsOrderModel) TableName() string { + return m.name +} diff --git a/server/orders/internal/handler/getuserorderlisthandler.go b/server/orders/internal/handler/getuserorderlisthandler.go new file mode 100644 index 00000000..89309f16 --- /dev/null +++ b/server/orders/internal/handler/getuserorderlisthandler.go @@ -0,0 +1,78 @@ +package handler + +import ( + "errors" + "net/http" + + "github.com/zeromicro/go-zero/core/logx" + "github.com/zeromicro/go-zero/rest/httpx" + + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "fusenapi/server/orders/internal/logic" + "fusenapi/server/orders/internal/svc" + "fusenapi/server/orders/internal/types" +) + +func GetUserOrderListHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var ( + // 定义错误变量 + err error + // 定义用户信息变量 + userinfo *auth.UserInfo + ) + // 解析JWT token,并对空用户进行判断 + claims, err := svcCtx.ParseJwtToken(r) + // 如果解析JWT token出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 401, // 返回401状态码,表示未授权 + Message: "unauthorized", // 返回未授权信息 + }) + logx.Info("unauthorized:", err.Error()) // 记录错误日志 + return + } + + if claims != nil { + // 从token中获取对应的用户信息 + userinfo, err = auth.GetUserInfoFormMapClaims(claims) + // 如果获取用户信息出错,则返回未授权的JSON响应并记录错误消息 + if err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 401, + Message: "unauthorized", + }) + logx.Info("unauthorized:", err.Error()) + return + } + } else { + // 如果claims为nil,则认为用户身份为白板用户 + userinfo = &auth.UserInfo{UserId: 0, GuestId: 0} + } + + var req types.GetUserOrderListReq + // 如果端点有请求结构体,则使用httpx.Parse方法从HTTP请求体中解析请求数据 + if err := httpx.Parse(r, &req); err != nil { + httpx.OkJsonCtx(r.Context(), w, &basic.Response{ + Code: 510, + Message: "parameter error", + }) + logx.Info(err) + return + } + // 创建一个业务逻辑层实例 + l := logic.NewGetUserOrderListLogic(r.Context(), svcCtx) + resp := l.GetUserOrderList(&req, userinfo) + // 如果响应不为nil,则使用httpx.OkJsonCtx方法返回JSON响应; + if resp != nil { + httpx.OkJsonCtx(r.Context(), w, resp) + } else { + err := errors.New("server logic is error, resp must not be nil") + httpx.ErrorCtx(r.Context(), w, err) + logx.Error(err) + } + } +} diff --git a/server/orders/internal/handler/routes.go b/server/orders/internal/handler/routes.go index 467c0530..e22a9566 100644 --- a/server/orders/internal/handler/routes.go +++ b/server/orders/internal/handler/routes.go @@ -22,6 +22,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/order/detail", Handler: GetOrderDetailHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/api/user/order-list", + Handler: GetUserOrderListHandler(serverCtx), + }, }, ) } diff --git a/server/orders/internal/logic/getuserorderlistlogic.go b/server/orders/internal/logic/getuserorderlistlogic.go new file mode 100644 index 00000000..28f8e4a8 --- /dev/null +++ b/server/orders/internal/logic/getuserorderlistlogic.go @@ -0,0 +1,146 @@ +package logic + +import ( + "context" + "errors" + "fusenapi/constants" + "fusenapi/model/gmodel" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + "fusenapi/utils/format" + "math" + + "fusenapi/server/orders/internal/svc" + "fusenapi/server/orders/internal/types" + + "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" +) + +type GetUserOrderListLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetUserOrderListLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetUserOrderListLogic { + return &GetUserOrderListLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +func (l *GetUserOrderListLogic) GetUserOrderList(req *types.GetUserOrderListReq, userinfo *auth.UserInfo) (resp *basic.Response) { + // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) + // userinfo 传入值时, 一定不为null + orderDetailModel := gmodel.NewFsOrderDetailModel(l.svcCtx.MysqlConn) + orderModel := gmodel.NewFsOrderModel(l.svcCtx.MysqlConn) + rowBuilder := orderModel.RowSelectBuilder(nil) + if userinfo == nil || userinfo.UserId == 0 { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order not found") + } + + // 查询条件 + var page = req.Page + var pageSize = req.PageSize + var listRes []*gmodel.FsOrderRel + rowBuilder = rowBuilder.Where("user_id =?", userinfo.UserId).Where("status =?", req.Status) + + // 查询总数 + total, err := orderModel.FindCount(l.ctx, rowBuilder, nil) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order not found") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get order info") + } + + // 查询数据 + if total > 0 { + rowBuilder = rowBuilder.Preload("FsOrderDetails", func(dbPreload *gorm.DB) *gorm.DB { + return dbPreload.Table(orderDetailModel.TableName()).Preload("FsOrderDetailTemplateInfo").Preload("FsProductInfo") + }) + listRes, err = orderModel.FindPageListByPage(l.ctx, rowBuilder, &page, &pageSize, nil, "") + } + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "order not found") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get order info") + } + listResLen := len(listRes) + + var respList []types.Items + if listResLen > 0 { + // 数据处理 + for _, item := range listRes { + var pbData types.Items + pbData.ID = item.Id + pbData.Sn = *item.Sn + pbData.UserID = *item.UserId + pbData.TotalAmount = *item.TotalAmount + pbData.Ctime = format.TimeIntToFormat(*item.Ctime) + pbData.Status = *item.Status + pbData.DeliveryMethod = *item.DeliveryMethod + pbData.TsTime = format.TimeToFormat(*item.TsTime) + pbData.IsPayCompleted = *item.IsPayCompleted + pbData.DeliverSn = *item.DeliverSn + + var pcsBox int64 + var pcs int64 + var productList []*types.Product + if len(item.FsOrderDetails) > 0 { + for _, fsOrderDetailItem := range item.FsOrderDetails { + fsOrderDetailBuyNum := *fsOrderDetailItem.FsOrderDetail.BuyNum + fsOrderDetailEachBoxNum := *fsOrderDetailItem.FsOrderDetailTemplateInfo.EachBoxNum + pcs = pcs + fsOrderDetailBuyNum + pcsBoxNum := fsOrderDetailBuyNum / fsOrderDetailEachBoxNum + var csBoxNumF int64 + if (fsOrderDetailBuyNum % fsOrderDetailEachBoxNum) > 0 { + csBoxNumF = 1 + } + pcsBox = pcsBox + pcsBoxNum + csBoxNumF + + var product types.Product + product.Cover = *fsOrderDetailItem.Cover + product.Fitting = *fsOrderDetailItem.OptionalTitle + product.OptionPrice = *fsOrderDetailItem.OptionPrice + product.OrderDetailTemplateId = *fsOrderDetailItem.OrderDetailTemplateId + product.OrderId = *fsOrderDetailItem.OrderId + product.Pcs = fsOrderDetailBuyNum + product.PcsBox = pcsBox + product.Price = *fsOrderDetailItem.FsOrderDetail.Amount + product.ProductId = *fsOrderDetailItem.OptionPrice + //product.Size = *fsOrderDetailItem.FsProductInfo.s + product.Title = *fsOrderDetailItem.FsProductInfo.Title + + productList = append(productList, &product) + } + pbData.ProductList = productList + } + + pbData.PcsBox = pcsBox + pbData.Pcs = pcs + pbData.SurplusAt = *item.Ctime + constants.CANCLE_ORDER_EXPIRE + pbData.LogisticsStatus = 1 + pbData.Deposit = *item.TotalAmount / 2 + pbData.Remaining = pbData.Deposit + respList = append(respList, pbData) + } + + } + + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetUserOrderListRsp{ + Items: respList, + Meta: types.Meta{ + TotalCount: total, + PageCount: int64(math.Ceil(float64(total) / float64(pageSize))), + CurrentPage: int(page), + PerPage: int(pageSize), + }, + }) +} diff --git a/server/orders/internal/types/types.go b/server/orders/internal/types/types.go index 703e7835..32ce3df9 100644 --- a/server/orders/internal/types/types.go +++ b/server/orders/internal/types/types.go @@ -76,6 +76,47 @@ type Deposit struct { TransNo string `json:"trans_no"` } +type GetUserOrderListReq struct { + Page int64 `form:"page"` // 分页 + PageSize int64 `form:"page_size"` // 每页数量 + Status int64 `form:"status"` // 状态筛选 + Time int64 `form:"time"` // 时间筛选 + Total int64 `form:"total"` // 总数 + Size int64 `form:"size"` // 图片尺寸 +} + +type GetUserOrderListRsp struct { + Items []Items `json:"items"` + Meta Meta `json:"_meta"` +} + +type StatusTimes struct { + Key int `json:"key"` + Time string `json:"time"` +} + +type Items struct { + ID int64 `json:"id"` + Sn string `json:"sn"` + UserID int64 `json:"user_id"` + TotalAmount int64 `json:"total_amount"` + Ctime string `json:"ctime"` + Status int64 `json:"status"` + DeliveryMethod int64 `json:"delivery_method"` + TsTime string `json:"ts_time"` + IsPayCompleted int64 `json:"is_pay_completed"` + DeliverSn string `json:"deliver_sn"` + PcsBox int64 `json:"pcs_box"` + Pcs int64 `json:"pcs"` + SurplusAt int64 `json:"surplus_at"` + LogisticsStatus int64 `json:"logistics_status"` + StatusTimes []*StatusTimes `json:"status_times"` + Deposit int64 `json:"deposit"` + Remaining int64 `json:"remaining"` + ProductList []*Product `json:"productList"` + IsStop int64 `json:"is_stop"` +} + type Request struct { } diff --git a/server/orders/orders_test.go b/server/orders/orders_test.go new file mode 100644 index 00000000..ef649768 --- /dev/null +++ b/server/orders/orders_test.go @@ -0,0 +1,7 @@ +package main + +import "testing" + +func TestMain(t *testing.T) { + main() +} diff --git a/server_api/orders.api b/server_api/orders.api index 3bb63dde..e3015778 100644 --- a/server_api/orders.api +++ b/server_api/orders.api @@ -16,6 +16,10 @@ service orders { //获取订单详情 @handler GetOrderDetailHandler get /api/order/detail (GetOrderDetailReq) returns (response); + + //获取订单列表 + @handler GetUserOrderListHandler + get /api/user/order-list (GetUserOrderListReq) returns (response); } //获取订单发票 @@ -82,4 +86,48 @@ type PayInfo { type Deposit { Method string `json:"method"` TransNo string `json:"trans_no"` +} + +// 获取订单列表 +type ( + GetUserOrderListReq { + Page int64 `form:"page"` // 分页 + PageSize int64 `form:"page_size"` // 每页数量 + Status int64 `form:"status"` // 状态筛选 + Time int64 `form:"time"` // 时间筛选 + Total int64 `form:"total"` // 总数 + Size int64 `form:"size"` // 图片尺寸 + } + + GetUserOrderListRsp { + Items []Items `json:"items"` + Meta Meta `json:"_meta"` + } +) + +type StatusTimes { + Key int `json:"key"` + Time string `json:"time"` +} + +type Items { + ID int64 `json:"id"` + Sn string `json:"sn"` + UserID int64 `json:"user_id"` + TotalAmount int64 `json:"total_amount"` + Ctime string `json:"ctime"` + Status int64 `json:"status"` + DeliveryMethod int64 `json:"delivery_method"` + TsTime string `json:"ts_time"` + IsPayCompleted int64 `json:"is_pay_completed"` + DeliverSn string `json:"deliver_sn"` + PcsBox int64 `json:"pcs_box"` + Pcs int64 `json:"pcs"` + SurplusAt int64 `json:"surplus_at"` + LogisticsStatus int64 `json:"logistics_status"` + StatusTimes []*StatusTimes `json:"status_times"` + Deposit int64 `json:"deposit"` + Remaining int64 `json:"remaining"` + ProductList []*Product `json:"productList"` + IsStop int64 `json:"is_stop"` } \ No newline at end of file diff --git a/utils/format/time.go b/utils/format/time.go new file mode 100644 index 00000000..17666331 --- /dev/null +++ b/utils/format/time.go @@ -0,0 +1,11 @@ +package format + +import "time" + +func TimeToFormat(t time.Time) string { + return time.Time(t).Format("2006-01-02 15:04:05") +} + +func TimeIntToFormat(unixTime int64) string { + return time.Unix(unixTime, 0).Format("2006-01-02 15:04:05") +} diff --git a/utils/handler/gormHandler.go b/utils/handler/gormHandler.go new file mode 100644 index 00000000..d239931e --- /dev/null +++ b/utils/handler/gormHandler.go @@ -0,0 +1,146 @@ +package handler + +import ( + "encoding/json" + "fmt" + "fusenapi/constants" + "strconv" + "strings" + "unicode" + + "gorm.io/gorm" +) + +// FilterData 条件过滤 +func FilterData(filterMap map[string]string) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + // 组合索引 + db = db.Where("id <> ?", 0) + for fieldName, item := range filterMap { + options := strings.Split(item, "|") + conditionKey := options[1] + + var conditionValueType string + if len(options) == 3 { + conditionValueType = options[2] + } + + var conditionValue interface{} + + if conditionKey == "=" || conditionKey == "<>" || conditionKey == ">=" || conditionKey == "<=" { + conditionStr := options[0] + if conditionValueType == "string" { + conditionValue = options[0] + } else { + if ss := CheckIsDigit(conditionStr); ss != 0 { + conditionValue = ss + } else { + conditionValue = options[0] + } + } + } else { + var conditionArrNew []int64 + conditionArr := strings.Split(options[0], ",") + for _, s := range conditionArr { + if ss := CheckIsDigit(s); ss != 0 { + conditionArrNew = append(conditionArrNew, ss) + } + } + if len(conditionArrNew) > 0 { + conditionValue = conditionArrNew + } else { + conditionValue = conditionArr + } + } + + switch conditionKey { + case "LIKE": + db = db.Where(fmt.Sprintf("%v LIKE ?", fieldName), fmt.Sprintf("%%%v%%", conditionValue)) + case "BETWEEN": + db = db.Where(fmt.Sprintf("%v BETWEEN ? AND ?", fieldName), conditionValue.([]interface{})[0], conditionValue.([]interface{})[1]) + default: + db = db.Where(fmt.Sprintf("%v %v ?", fieldName, conditionKey), conditionValue) + } + } + return db + } +} + +// OrderCheck 公共排序--检测 +func OrderCheck(orderData string, fields map[string]struct{}) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + var orderType = "desc" + if orderData != "" { + var sortData []map[string]interface{} + _ = json.Unmarshal([]byte(orderData), &sortData) + + sortCount := len(sortData) + for i := 0; i < sortCount; i++ { + data := sortData[i] + prop, existProp := data["prop"] + sort, existSort := data["order"] + if existProp && existSort { + propData := strings.TrimSpace(prop.(string)) + sortData := strings.TrimSpace(sort.(string)) + if propData != "" && sortData != "" { + // 判断数据库字段 + _, existFields := fields[propData] + if existFields { + if sortData == "descending" || sortData == "desc" { + orderType = "desc" + } else { + orderType = "asc" + } + db.Order(fmt.Sprintf("%v %v", prop, orderType)) + } + } + } + } + return db + } else { + return db.Order("id asc") + } + } +} + +// Paginate 公共分页 +func Paginate(page *int64, pageSize *int64) func(db *gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + page, _ := strconv.Atoi(strconv.FormatInt(*page, 10)) + + switch { + case page == 0: + page = constants.DEFAULT_PAGE + case page > constants.MAX_PAGE: + page = constants.MAX_PAGE + } + + pageSize, _ := strconv.Atoi(strconv.FormatInt(*pageSize, 10)) + switch { + case pageSize > constants.MAX_PAGE_SIZE: + pageSize = constants.MAX_PAGE_SIZE + case pageSize <= 0: + pageSize = constants.DEFAULT_PAGE_SIZE + } + + offset := (page - 1) * pageSize + return db.Offset(offset).Limit(pageSize) + } +} + +// CheckIsDigit 判断int +func CheckIsDigit(s string) int64 { + isDigit := true + for _, r := range s { + if !unicode.IsDigit(r) { + isDigit = false + break + } + } + if isDigit { + ss, _ := strconv.ParseInt(s, 10, 64) + return ss + } else { + return 0 + } +}