chore: 初始开源发布

iotlcm-app:小程序端 + 用户端服务。

- app/:uni-app 小程序前端(基于 unibest,MIT)
  - 微信 AppID → YOUR_WX_APPID,uni-app AppID → __UNI__YOURAPPID
  - 内网后端地址 → admin.iotlcm.example.com
  - 应用标题与 unibest 模板 README 重写(保留上游致谢与 MIT 声明)
- server/:GoFrame 用户端服务(基于 XiuAdmin,Apache-2.0)
  - module xiuadmin → git.5iot.com/iotlcm-public/iotlcm-app(360 文件)
  - 支付回调地址 → app.iotlcm.example.com,商户名与应用名泛化
  - 真实手机号 → 合法 11 位编造号(保留号段前 3 位 + 后 4 位)
  - 依赖号码尾部的断言同步更新
  - 补 server/LICENSE(Apache-2.0)
- 移除内部资产:CI 触发器
- 补根 README,LF 行尾 + .gitattributes

已知:TestSysDept_RefreshDeptAncestors 在源仓同一提交上同样失败,属既有缺陷。
This commit is contained in:
MiFaZhan
2026-10-26 14:01:25 +08:00
commit 2ed5dfa6a1
1013 changed files with 138466 additions and 0 deletions
+343
View File
@@ -0,0 +1,343 @@
package app
import (
"context"
"encoding/json"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/service"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
)
type sAppOrder struct{}
func NewAppOrder() *sAppOrder {
return &sAppOrder{}
}
func init() {
service.RegisterAppOrder(NewAppOrder())
}
func (s *sAppOrder) List(ctx context.Context, param *model.AppOrderListParam) (items []*model.AppOrderModel, total int, err error) {
if param == nil {
param = &model.AppOrderListParam{}
}
if param.Page <= 0 {
param.Page = 1
}
if param.PageSize <= 0 {
param.PageSize = 10
}
if param.PageSize > 100 {
param.PageSize = 100
}
user, err := appCurrentUser(ctx)
if err != nil {
return nil, 0, err
}
mod := s.ownerOrderModel(ctx, user)
mod = s.applyOrderFilters(mod, param)
total, err = mod.Count()
if err != nil {
return nil, 0, gerror.Wrap(err, "查询订单数量失败")
}
if total == 0 {
return []*model.AppOrderModel{}, 0, nil
}
var rows []*entity.TOrder
if err = mod.
Page(param.Page, param.PageSize).
OrderDesc(dao.TOrder.Columns().CreatedAt).
OrderDesc(dao.TOrder.Columns().Id).
Scan(&rows); err != nil {
return nil, 0, gerror.Wrap(err, "查询订单列表失败")
}
orderSns := make([]string, 0, len(rows))
for _, row := range rows {
if row != nil && strings.TrimSpace(row.OrderSn) != "" {
orderSns = append(orderSns, row.OrderSn)
}
}
itemMap, err := s.orderItemMap(ctx, user.TenantId, orderSns)
if err != nil {
return nil, 0, err
}
items = make([]*model.AppOrderModel, 0, len(rows))
for _, row := range rows {
items = append(items, s.mapOrder(ctx, row, itemMap[row.OrderSn], false))
}
return items, total, nil
}
func (s *sAppOrder) Detail(ctx context.Context, param *model.AppOrderDetailParam) (*model.AppOrderModel, error) {
if param == nil {
param = &model.AppOrderDetailParam{}
}
orderSn := strings.TrimSpace(param.OrderSn)
if orderSn == "" {
return nil, gerror.New("订单号不能为空")
}
user, err := appCurrentUser(ctx)
if err != nil {
return nil, err
}
var order *entity.TOrder
if err = s.ownerOrderModel(ctx, user).
Where(dao.TOrder.Columns().OrderSn, orderSn).
Limit(1).
Scan(&order); err != nil {
return nil, gerror.Wrap(err, "查询订单详情失败")
}
if order == nil || order.Id == 0 {
return nil, gerror.New("订单不存在或无权查看")
}
itemMap, err := s.orderItemMap(ctx, user.TenantId, []string{order.OrderSn})
if err != nil {
return nil, err
}
return s.mapOrder(ctx, order, itemMap[order.OrderSn], true), nil
}
func (s *sAppOrder) ownerOrderModel(ctx context.Context, user *entity.SysUser) *gdb.Model {
orderTable := dao.TOrder.Table()
mod := dao.TOrder.Ctx(ctx).
Where(orderTable+"."+dao.TOrder.Columns().TenantId, user.TenantId)
if appIsTerminalUser(ctx, user) {
return mod.Where(orderTable+"."+dao.TOrder.Columns().CreatedBy, user.UserId)
}
return appApplyEnterpriseDataScope(
ctx,
user,
mod,
orderTable+"."+dao.TOrder.Columns().CreatedDept,
orderTable+"."+dao.TOrder.Columns().CreatedBy,
func(legacyMod *gdb.Model) *gdb.Model {
return legacyMod.Where(orderTable+"."+dao.TOrder.Columns().CreatedBy, user.UserId)
},
)
}
func (s *sAppOrder) applyOrderFilters(mod *gdb.Model, param *model.AppOrderListParam) *gdb.Model {
if param.OrderType > 0 {
mod = mod.Where(dao.TOrder.Columns().OrderType, param.OrderType)
}
if param.PayStatus != nil && *param.PayStatus >= 0 {
mod = mod.Where(dao.TOrder.Columns().PayStatus, *param.PayStatus)
}
keyword := strings.TrimSpace(param.Keyword)
if keyword == "" {
keyword = strings.TrimSpace(param.PageInfo.Keyword)
}
if keyword != "" {
like := "%" + keyword + "%"
orderTable := dao.TOrder.Table()
itemTable := dao.TOrderItem.Table()
mod = mod.Where(
"("+orderTable+"."+dao.TOrder.Columns().OrderSn+" LIKE ? OR "+
orderTable+"."+dao.TOrder.Columns().PayOrderSn+" LIKE ? OR "+
orderTable+"."+dao.TOrder.Columns().Remark+" LIKE ? OR EXISTS (SELECT 1 FROM "+itemTable+" oi WHERE oi."+dao.TOrderItem.Columns().OrderSn+" = "+orderTable+"."+dao.TOrder.Columns().OrderSn+" AND oi."+dao.TOrderItem.Columns().ProductName+" LIKE ?))",
like, like, like, like,
)
}
return mod
}
func (s *sAppOrder) orderItemMap(ctx context.Context, tenantId string, orderSns []string) (map[string][]*entity.TOrderItem, error) {
result := make(map[string][]*entity.TOrderItem)
if len(orderSns) == 0 {
return result, nil
}
var rows []*entity.TOrderItem
if err := dao.TOrderItem.Ctx(ctx).
Where(dao.TOrderItem.Columns().TenantId, tenantId).
WhereIn(dao.TOrderItem.Columns().OrderSn, orderSns).
OrderAsc(dao.TOrderItem.Columns().ItemId).
Scan(&rows); err != nil {
return nil, gerror.Wrap(err, "查询订单项失败")
}
for _, row := range rows {
if row == nil {
continue
}
result[row.OrderSn] = append(result[row.OrderSn], row)
}
return result, nil
}
func (s *sAppOrder) mapOrder(ctx context.Context, row *entity.TOrder, items []*entity.TOrderItem, includeItems bool) *model.AppOrderModel {
if row == nil {
return nil
}
mappedItems := make([]*model.AppOrderItemModel, 0, len(items))
for _, item := range items {
if item == nil {
continue
}
mappedItems = append(mappedItems, &model.AppOrderItemModel{
ItemId: item.ItemId,
ProductName: item.ProductName,
ProductSn: item.ProductSn,
Price: appRoundAmount(item.Price),
PriceText: appFormatMoney(item.Price),
Quantity: item.Quantity,
OriginPrice: appRoundAmount(item.OriginPrice),
OriginPriceText: appFormatMoney(item.OriginPrice),
ProductType: item.ProductType,
ServiceBeginTime: item.ServiceBeginTime,
ServiceEndTime: item.ServiceEndTime,
})
}
itemSummary := "订单商品"
if len(mappedItems) > 0 && strings.TrimSpace(mappedItems[0].ProductName) != "" {
itemSummary = strings.TrimSpace(mappedItems[0].ProductName)
if len(mappedItems) > 1 {
itemSummary += " 等"
}
}
orderTypeText := appOrderTypeText(row.OrderType)
switch appOrderBusinessFromItems(items) {
case "miniapp_card_change_batch":
orderTypeText = "套餐变更"
case "miniapp_card_renew_batch":
orderTypeText = "套餐续费"
}
paymentExpireAt, paymentRemainingSeconds, paymentExpired := appBalanceRechargePaymentTiming(ctx, row)
res := &model.AppOrderModel{
OrderId: row.Id,
OrderSn: row.OrderSn,
OrderType: row.OrderType,
OrderTypeText: orderTypeText,
OrderStatus: row.OrderStatus,
OrderStatusText: appOrderStatusText(row.OrderStatus),
PayType: row.PayType,
PayTypeText: appOrderPayTypeText(row.PayType),
PayStatus: row.PayStatus,
PayStatusText: appOrderPayStatusText(row.PayStatus),
PayOrderSn: row.PayOrderSn,
ReturnStatus: row.ReturnStatus,
ReturnStatusText: appOrderReturnStatusText(row.ReturnStatus),
PaymentExpireAt: paymentExpireAt,
PaymentRemainingSeconds: paymentRemainingSeconds,
PaymentExpired: paymentExpired,
TotalAmount: appRoundAmount(row.TotalAmount),
TotalAmountText: appFormatMoney(row.TotalAmount),
PaidAmount: appRoundAmount(row.PaidAmount),
PaidAmountText: appFormatMoney(row.PaidAmount),
OnlinePaidAmount: appRoundAmount(row.OnlinePaidAmount),
OnlinePaidAmountText: appFormatMoney(row.OnlinePaidAmount),
Balance: appRoundAmount(row.Balance),
BalanceText: appFormatMoney(row.Balance),
UnpaidAmount: appRoundAmount(row.UnpaidAmount),
UnpaidAmountText: appFormatMoney(row.UnpaidAmount),
UnrefundAmount: appRoundAmount(row.UnrefundAmount),
UnrefundAmountText: appFormatMoney(row.UnrefundAmount),
BuyerNote: row.BuyerNote,
Remark: row.Remark,
CreatedAt: row.CreatedAt,
PayTime: row.PayTime,
ItemSummary: itemSummary,
ItemCount: len(mappedItems),
}
if includeItems {
res.Items = mappedItems
}
return res
}
func appOrderBusinessFromItems(items []*entity.TOrderItem) string {
for _, item := range items {
if item == nil || strings.TrimSpace(item.ExtraSkuData) == "" {
continue
}
var extra struct {
Business string `json:"business"`
}
if err := json.Unmarshal([]byte(item.ExtraSkuData), &extra); err != nil {
continue
}
if strings.TrimSpace(extra.Business) != "" {
return strings.TrimSpace(extra.Business)
}
}
return ""
}
func appOrderTypeText(orderType int) string {
switch orderType {
case consts.OrderTypeCardSubscribe:
return "套餐续费"
case consts.OrderTypeBalanceRecharge:
return "余额充值"
default:
return "其他订单"
}
}
func appOrderStatusText(status int) string {
switch status {
case consts.OrderStatusPendingPay:
return "待支付"
case consts.OrderStatusCompleted:
return "已完成"
default:
return "处理中"
}
}
func appOrderPayTypeText(payType int) string {
switch payType {
case consts.PayTypeBalance:
return "余额"
case consts.PayTypeOnline:
return "线上支付"
default:
return "未支付"
}
}
func appOrderPayStatusText(payStatus int) string {
switch payStatus {
case consts.PayStatusUnpaid:
return "待支付"
case consts.PayStatusPaid:
return "已支付"
default:
return "处理中"
}
}
func appOrderReturnStatusText(status int) string {
switch status {
case consts.OrderReturnStatusPending:
return "退款申请中"
case consts.OrderReturnStatusRunning:
return "退款处理中"
case consts.OrderReturnStatusPartial:
return "退款异常"
case consts.OrderReturnStatusSuccess:
return "已退款"
default:
return "未退款"
}
}