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
+187
View File
@@ -0,0 +1,187 @@
package app
import (
"context"
"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/do"
"git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
// 只处理终端用户绑卡。企业用户的持卡范围由管理平台权限逻辑决定,不写终端卡关联表。
type appSimTerminalBindRow struct {
Id int64 `orm:"id"`
TerminalId int64 `orm:"terminal_id"`
DeptId int64 `orm:"dept_id"`
CurrentPackageCode string `orm:"current_package_code"`
}
func (s *sAppCard) Bind(ctx context.Context, param *model.AppCardBindParam) (res *model.AppCardModel, err error) {
user, err := appCurrentUser(ctx)
if err != nil {
return nil, err
}
if !s.isTerminalUser(ctx, user) {
return nil, gerror.New("企业用户无需绑定卡,请在我的卡片中查询当前可见卡")
}
card, err := s.findCardByValue(ctx, user.TenantId, param.CardValue)
if err != nil {
return nil, err
}
if card == nil {
return nil, gerror.New("卡信息不存在,请确认查询号码是否正确")
}
now := gtime.Now()
alreadyBound := false
err = dao.TSimTerminal.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
terminal, err := s.ensureTerminal(ctx, tx, user, now)
if err != nil {
return err
}
var bind *appSimTerminalBindRow
err = dao.TSimTerminal.Ctx(ctx).TX(tx).
Where(dao.TSimTerminal.Columns().TenantId, user.TenantId).
Where(dao.TSimTerminal.Columns().Iccid, card.Iccid).
Where(dao.TSimTerminal.Columns().TerminalId, user.UserId).
Scan(&bind)
if err != nil {
return err
}
if bind != nil {
alreadyBound = true
if bind.DeptId != card.DeptId || strings.TrimSpace(bind.CurrentPackageCode) != strings.TrimSpace(card.CurrentPackageCode) {
_, err = dao.TSimTerminal.Ctx(ctx).TX(tx).
Where(dao.TSimTerminal.Columns().Id, bind.Id).
Data(do.TSimTerminal{
DeptId: card.DeptId,
CurrentPackageCode: strings.TrimSpace(card.CurrentPackageCode),
UpdatedBy: user.UserId,
UpdatedAt: now,
}).
Update()
}
return err
}
_, err = dao.TSimTerminal.Ctx(ctx).TX(tx).Data(do.TSimTerminal{
Iccid: card.Iccid,
Msisdn: card.Msisdn,
Imsi: card.Imsi,
TerminalId: user.UserId,
TerminalName: terminal.TerminalName,
DeptId: card.DeptId,
CurrentPackageCode: strings.TrimSpace(card.CurrentPackageCode),
Remark: param.Remark,
TenantId: user.TenantId,
CreatedDept: appUserDeptOrDefault(user),
CreatedBy: user.UserId,
CreatedAt: now,
UpdatedBy: user.UserId,
UpdatedAt: now,
}).Insert()
return err
})
if err != nil {
if isOldSimTerminalUniqueKeyError(err) {
return nil, gerror.New("终端卡关联表唯一索引未更新,当前数据库仍限制一个终端用户只能绑定一张卡")
}
if isSimTerminalCardUniqueKeyError(err) {
return nil, gerror.New("该卡已绑定当前用户,请刷新后重试")
}
return nil, err
}
if alreadyBound {
return nil, gerror.New("该卡已绑定当前用户")
}
cardRow, err := s.findVisibleCard(ctx, user, card.Iccid)
if err != nil {
return nil, err
}
if cardRow == nil {
return nil, gerror.New("绑卡成功,但刷新卡信息失败")
}
return s.cardRowToModel(ctx, cardRow, user, appCardOwnerTerminal, true), nil
}
func (s *sAppCard) ensureTerminal(ctx context.Context, tx gdb.TX, user *entity.SysUser, now *gtime.Time) (*entity.TTerminal, error) {
var terminal *entity.TTerminal
err := dao.TTerminal.Ctx(ctx).TX(tx).
Where(dao.TTerminal.Columns().TerminalId, user.UserId).
Scan(&terminal)
if err != nil {
return nil, err
}
if terminal != nil {
if strings.TrimSpace(terminal.TerminalName) == "" {
terminal.TerminalName = user.NickName
}
return terminal, nil
}
name := user.NickName
if name == "" {
name = user.UserName
}
_, err = dao.TTerminal.Ctx(ctx).TX(tx).Data(do.TTerminal{
TerminalId: user.UserId,
TerminalName: name,
Phone: user.Phonenumber,
Status: consts.SysUserStatusNormal,
FullName: name,
LegalPersonName: name,
LegalPersonIdCard: "000000000000000000",
Balance: 0,
TenantId: user.TenantId,
CreatedDept: appUserDeptOrDefault(user),
CreatedBy: user.UserId,
CreatedAt: now,
UpdatedBy: user.UserId,
UpdatedAt: now,
}).Insert()
if err != nil {
return nil, err
}
return &entity.TTerminal{
TerminalId: user.UserId,
TerminalName: name,
Phone: user.Phonenumber,
TenantId: user.TenantId,
CreatedDept: appUserDeptOrDefault(user),
}, nil
}
func (s *sAppCard) isCardBoundToUser(ctx context.Context, iccid string, user *entity.SysUser) bool {
count, err := dao.TSimTerminal.Ctx(ctx).
Where(dao.TSimTerminal.Columns().TenantId, user.TenantId).
Where(dao.TSimTerminal.Columns().TerminalId, user.UserId).
Where(dao.TSimTerminal.Columns().Iccid, iccid).
Count()
return err == nil && count > 0
}
func isOldSimTerminalUniqueKeyError(err error) bool {
if err == nil {
return false
}
errText := err.Error()
return strings.Contains(errText, "Duplicate entry") &&
strings.Contains(errText, "uk_package_terminal")
}
func isSimTerminalCardUniqueKeyError(err error) bool {
if err == nil {
return false
}
errText := err.Error()
return strings.Contains(errText, "Duplicate entry") &&
strings.Contains(errText, "uk_sim_terminal_card")
}