已完成用户APP首期功能开发

交付用户端首期页面、配套接口、后台资源及测试文档。用户APP构建、静态分析和三个管理后台构建通过;完整测试仍有2项失败,后端模型注释检查未通过,详见交付记录。
This commit is contained in:
czl231
2026-09-13 00:57:32 +08:00
parent a0a4bb1218
commit 3ef33b531d
793 changed files with 40959 additions and 1204 deletions

View File

@@ -0,0 +1,65 @@
// 功能描述:只扩展用户地址的联系人和幂等字段,不执行全库迁移或初始化。
// 版本1.0.0。
package main
import (
"fmt"
"git.apinb.com/bsm-sdk/core/database"
dbsql "git.apinb.com/bsm-sdk/core/database/sql"
"git.apinb.com/heqiapp/platforms/backend/api/internal/config"
"gorm.io/gorm"
)
// migrateUserAddress 使用当前环境的已有数据库配置,事务失败全部回滚。
func migrateUserAddress() error {
return migrateClientColumns([]string{
`ALTER TABLE user_address ADD COLUMN IF NOT EXISTS contact_name varchar(64) NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS contact_phone varchar(32) NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS request_no varchar(64) NOT NULL DEFAULT ''`,
`COMMENT ON COLUMN user_address.contact_name IS '收货联系人姓名;历史记录为空,编辑时补齐'`,
`COMMENT ON COLUMN user_address.contact_phone IS '收货联系人手机号;历史记录为空,编辑时补齐'`,
`COMMENT ON COLUMN user_address.request_no IS '客户端新增请求幂等标识,同一用户重试不重复新增,历史记录为空'`,
})
}
// migrateTicketContact 仅补齐报修故障分类和提交时的联系人快照。
func migrateTicketContact() error {
return migrateClientColumns([]string{
`ALTER TABLE cs_ticket ADD COLUMN IF NOT EXISTS fault_type varchar(32) NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS contact_name varchar(64) NOT NULL DEFAULT '', ADD COLUMN IF NOT EXISTS contact_phone varchar(32) NOT NULL DEFAULT ''`,
`COMMENT ON COLUMN cs_ticket.fault_type IS '故障类型leak=燃气泄漏valve=阀门故障alarm=报警器故障other=其他;历史记录为空'`,
`COMMENT ON COLUMN cs_ticket.contact_name IS '用户提交工单时的联系人姓名快照,历史记录为空'`,
`COMMENT ON COLUMN cs_ticket.contact_phone IS '用户提交工单时的联系电话快照,历史记录为空'`,
})
}
// migrateTicketContract 只增加可选合同关联旧工单保持0且不改变原处理规则。
func migrateTicketContract() error {
return migrateClientColumns([]string{
`ALTER TABLE cs_ticket ADD COLUMN IF NOT EXISTS gasorder_contract_id bigint NOT NULL DEFAULT 0`,
`COMMENT ON COLUMN cs_ticket.gasorder_contract_id IS '关联供气合同内部主键0=非合同申请非0由本人合同校验后写入不代表合同已修改'`,
`CREATE INDEX IF NOT EXISTS idx_cs_ticket_gasorder_contract_id ON cs_ticket(gasorder_contract_id)`,
})
}
// migrateClientColumns 在已有远程配置中执行明确字段扩展,锁超时或失败全部回滚。
func migrateClientColumns(statements []string) error {
config.New(serviceKey)
if config.Spec.Databases == nil {
return fmt.Errorf("database configuration required")
}
db, err := database.NewDatabase(config.Spec.Databases.Driver, config.Spec.Databases.Source, dbsql.SetOptions(nil))
if err != nil {
return fmt.Errorf("connect configured database failed")
}
sqlDB, err := db.DB()
if err != nil {
return err
}
defer sqlDB.Close()
return db.Transaction(func(tx *gorm.DB) error {
for _, statement := range append([]string{`SET LOCAL lock_timeout = '5s'`}, statements...) {
if err := tx.Exec(statement).Error; err != nil {
return err
}
}
return nil
})
}

View File

@@ -0,0 +1,93 @@
// 功能描述仅新增押金规则、押金记录及完整中文数据库注释版本1.0.0。
package main
// migrateDeposit 创建首期押金事实表,不写入规则、金额或用户业务数据。
func migrateDeposit() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS deposit_policy (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,2,3)),
product_type_id bigint NOT NULL REFERENCES product_type(id), name varchar(128) NOT NULL,
amount bigint NOT NULL CHECK (amount >= 0), rule_text text NOT NULL DEFAULT '',
CONSTRAINT ux_deposit_policy_product_type UNIQUE (product_type_id)
)`,
`CREATE TABLE IF NOT EXISTS deposit_record (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)),
deposit_status integer NOT NULL DEFAULT 10 CHECK (deposit_status IN (10,20,23)),
deposit_no varchar(64) NOT NULL UNIQUE, user_account_id bigint NOT NULL REFERENCES user_account(id),
product_info_id bigint NOT NULL REFERENCES product_info(id), policy_id bigint NOT NULL REFERENCES deposit_policy(id),
gasorder_id bigint NOT NULL DEFAULT 0, amount bigint NOT NULL CHECK (amount >= 0),
paid_at timestamptz NOT NULL, refunded_at timestamptz
)`,
`CREATE TABLE IF NOT EXISTS deposit_return_request (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)),
return_status integer NOT NULL DEFAULT 10 CHECK (return_status IN (10,20,30,40,50)),
request_no varchar(128) NOT NULL UNIQUE, deposit_record_id bigint NOT NULL UNIQUE REFERENCES deposit_record(id),
user_account_id bigint NOT NULL REFERENCES user_account(id), user_address_id bigint NOT NULL REFERENCES user_address(id),
address_snapshot varchar(255) NOT NULL, contact_name varchar(64) NOT NULL, contact_phone varchar(32) NOT NULL,
appointment_start timestamptz NOT NULL, appointment_end timestamptz NOT NULL,
estimated_amount bigint NOT NULL CHECK (estimated_amount >= 0),
deduction_amount bigint NOT NULL DEFAULT 0 CHECK (deduction_amount >= 0),
refund_amount bigint NOT NULL DEFAULT 0 CHECK (refund_amount >= 0),
inspection_remark text NOT NULL DEFAULT '', operator_identity varchar(36) NOT NULL DEFAULT '',
picked_up_at timestamptz, inspected_at timestamptz, completed_at timestamptz
)`,
`COMMENT ON TABLE deposit_policy IS '气瓶产品类型押金规则;金额与退还说明由平台运营配置'`,
`COMMENT ON COLUMN deposit_policy.id IS '押金规则内部自增主键,不向客户端暴露'`,
`COMMENT ON COLUMN deposit_policy.identity IS '押金规则公开UUID V7标识'`,
`COMMENT ON COLUMN deposit_policy.created_at IS '规则创建时间'`,
`COMMENT ON COLUMN deposit_policy.updated_at IS '规则最后更新时间'`,
`COMMENT ON COLUMN deposit_policy.deleted_at IS '软删除时间'`,
`COMMENT ON COLUMN deposit_policy.status IS '通用状态1=启用2=停用3=归档'`,
`COMMENT ON COLUMN deposit_policy.product_type_id IS '适用气瓶产品类型内部主键'`,
`COMMENT ON COLUMN deposit_policy.name IS '押金规则名称'`,
`COMMENT ON COLUMN deposit_policy.amount IS '单只气瓶押金金额,单位分'`,
`COMMENT ON COLUMN deposit_policy.rule_text IS '用户端展示的押金退还规则正文'`,
`COMMENT ON TABLE deposit_record IS '用户每只实体气瓶的押金收取及退回事实'`,
`COMMENT ON COLUMN deposit_record.id IS '押金记录内部自增主键,不向客户端暴露'`,
`COMMENT ON COLUMN deposit_record.identity IS '押金记录公开UUID V7标识'`,
`COMMENT ON COLUMN deposit_record.created_at IS '押金记录创建时间'`,
`COMMENT ON COLUMN deposit_record.updated_at IS '押金状态最后更新时间'`,
`COMMENT ON COLUMN deposit_record.deleted_at IS '软删除时间;资金事实通常不删除'`,
`COMMENT ON COLUMN deposit_record.status IS '通用状态1=有效3=归档'`,
`COMMENT ON COLUMN deposit_record.deposit_status IS '押金状态10=使用中20=退款中23=已退回'`,
`COMMENT ON COLUMN deposit_record.deposit_no IS '押金业务编号'`,
`COMMENT ON COLUMN deposit_record.user_account_id IS '押金所属用户内部主键'`,
`COMMENT ON COLUMN deposit_record.product_info_id IS '绑定实体气瓶内部主键'`,
`COMMENT ON COLUMN deposit_record.policy_id IS '计费时采用的押金规则内部主键'`,
`COMMENT ON COLUMN deposit_record.gasorder_id IS '来源供气订单内部主键0表示历史导入'`,
`COMMENT ON COLUMN deposit_record.amount IS '实收押金金额,单位分'`,
`COMMENT ON COLUMN deposit_record.paid_at IS '押金缴纳时间'`,
`COMMENT ON COLUMN deposit_record.refunded_at IS '押金实际退回时间'`,
`COMMENT ON TABLE deposit_return_request IS '退瓶上门回收、验收扣减及押金入账申请表'`,
`COMMENT ON COLUMN deposit_return_request.id IS '退瓶申请内部自增主键'`,
`COMMENT ON COLUMN deposit_return_request.identity IS '退瓶申请公开UUID V7标识'`,
`COMMENT ON COLUMN deposit_return_request.created_at IS '申请创建时间'`,
`COMMENT ON COLUMN deposit_return_request.updated_at IS '状态最后更新时间'`,
`COMMENT ON COLUMN deposit_return_request.deleted_at IS '软删除时间;完成资金事实不删除'`,
`COMMENT ON COLUMN deposit_return_request.status IS '通用状态1=有效3=归档'`,
`COMMENT ON COLUMN deposit_return_request.return_status IS '退瓶状态10=待上门20=已回收待验收30=已验收待退款40=已完成50=已取消'`,
`COMMENT ON COLUMN deposit_return_request.request_no IS '用户端幂等请求号'`,
`COMMENT ON COLUMN deposit_return_request.deposit_record_id IS '对应押金记录内部主键'`,
`COMMENT ON COLUMN deposit_return_request.user_account_id IS '申请用户内部主键'`,
`COMMENT ON COLUMN deposit_return_request.user_address_id IS '上门回收地址内部主键'`,
`COMMENT ON COLUMN deposit_return_request.address_snapshot IS '申请时上门地址快照'`,
`COMMENT ON COLUMN deposit_return_request.contact_name IS '申请时联系人快照'`,
`COMMENT ON COLUMN deposit_return_request.contact_phone IS '申请时联系手机快照'`,
`COMMENT ON COLUMN deposit_return_request.appointment_start IS '预约上门时段开始时间'`,
`COMMENT ON COLUMN deposit_return_request.appointment_end IS '预约上门时段结束时间'`,
`COMMENT ON COLUMN deposit_return_request.estimated_amount IS '申请时预计可退押金,单位分'`,
`COMMENT ON COLUMN deposit_return_request.deduction_amount IS '验收后损坏或缺件扣减,单位分'`,
`COMMENT ON COLUMN deposit_return_request.refund_amount IS '最终退入余额账户金额,单位分'`,
`COMMENT ON COLUMN deposit_return_request.inspection_remark IS '验收结果及扣减理由'`,
`COMMENT ON COLUMN deposit_return_request.operator_identity IS '最后操作的平台账号标识'`,
`COMMENT ON COLUMN deposit_return_request.picked_up_at IS '实际回收时间'`,
`COMMENT ON COLUMN deposit_return_request.inspected_at IS '完成验收时间'`,
`COMMENT ON COLUMN deposit_return_request.completed_at IS '押金退回完成时间'`,
})
}

View File

@@ -0,0 +1,37 @@
// 功能独立创建用户设备分组表及产品归组字段不迁移其他业务表版本1.0.0。
package main
// migrateDeviceGroups 创建分组资料并补充产品归组字段,重复执行不清空现有数据。
func migrateDeviceGroups() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS user_device_group (
id bigserial PRIMARY KEY,
identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)),
user_account_id bigint NOT NULL REFERENCES user_account(id),
name varchar(32) NOT NULL,
request_no varchar(36) NOT NULL,
sort_no integer NOT NULL DEFAULT 0,
UNIQUE(user_account_id, request_no)
)`,
`COMMENT ON TABLE user_device_group IS '用户本人设备展示分组;不授予设备控制、共享或物联网权限'`,
`COMMENT ON COLUMN user_device_group.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_device_group.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_device_group.created_at IS '分组创建时间'`,
`COMMENT ON COLUMN user_device_group.updated_at IS '分组最后修改时间'`,
`COMMENT ON COLUMN user_device_group.deleted_at IS '软删除时间;当前删除操作使用状态归档'`,
`COMMENT ON COLUMN user_device_group.status IS '状态1=启用3=已归档'`,
`COMMENT ON COLUMN user_device_group.user_account_id IS '分组所属用户内部主键,仅本人可读写'`,
`COMMENT ON COLUMN user_device_group.name IS '用户自定义分组名称最多32字'`,
`COMMENT ON COLUMN user_device_group.request_no IS '客户端新增请求UUID同一用户内唯一且归档后不复用'`,
`COMMENT ON COLUMN user_device_group.sort_no IS '同一用户内显示顺序,数值越小越靠前'`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_device_group_active_name ON user_device_group(user_account_id,name) WHERE status=1 AND deleted_at IS NULL`,
`CREATE INDEX IF NOT EXISTS idx_device_group_user_sort ON user_device_group(user_account_id,status,sort_no,id)`,
`ALTER TABLE product_info ADD COLUMN IF NOT EXISTS device_group_id bigint NOT NULL DEFAULT 0`,
`COMMENT ON COLUMN product_info.device_group_id IS '当前用户设备分组内部主键0表示未分组产品归属变化时必须清空'`,
`CREATE INDEX IF NOT EXISTS idx_product_device_group ON product_info(user_account_id,device_group_id) WHERE device_group_id <> 0`,
})
}

View File

@@ -0,0 +1,12 @@
// 功能只扩展产品分类和厂商映射字段不猜测或改写既有归属版本1.0.0。
package main
// migrateDeviceMapping 保留历史档案为unknown非空厂商编号全局唯一。
func migrateDeviceMapping() error {
return migrateClientColumns([]string{
`ALTER TABLE product_info ADD COLUMN IF NOT EXISTS device_kind varchar(16) NOT NULL DEFAULT 'unknown', ADD COLUMN IF NOT EXISTS vendor_device_id varchar(16) NOT NULL DEFAULT ''`,
`COMMENT ON COLUMN product_info.device_kind IS '设备分类unknown=未分类valve=智能阀alarm=报警器cylinder=钢瓶;后台明确设置,不按产品名称推断'`,
`COMMENT ON COLUMN product_info.vendor_device_id IS '厂商16位十进制设备编号空表示未建立映射同一编号仅能关联一份平台档案不代表在线或可控制'`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_product_vendor_device_id ON product_info(vendor_device_id) WHERE vendor_device_id <> ''`,
})
}

View File

@@ -0,0 +1,35 @@
// 功能事务创建独立联系人资料表不迁移其他业务表版本1.0.0。
package main
// migrateEmergencyContacts 新增联系人表、字段注释和唯一约束,重复执行不清空数据。
func migrateEmergencyContacts() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS user_emergency_contact (
id bigserial PRIMARY KEY,
identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)),
user_account_id bigint NOT NULL REFERENCES user_account(id),
name varchar(64) NOT NULL,
phone varchar(32) NOT NULL,
relationship varchar(32) NOT NULL DEFAULT '',
request_no varchar(36) NOT NULL,
UNIQUE(user_account_id, request_no)
)`,
`COMMENT ON TABLE user_emergency_contact IS '用户本人紧急联系人资料;不代表设备授权或告警订阅已生效'`,
`COMMENT ON COLUMN user_emergency_contact.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_emergency_contact.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_emergency_contact.created_at IS '创建时间,也用于联系人默认排列顺序'`,
`COMMENT ON COLUMN user_emergency_contact.updated_at IS '最后修改时间'`,
`COMMENT ON COLUMN user_emergency_contact.deleted_at IS '软删除时间;当前删除操作使用状态归档保留幂等记录'`,
`COMMENT ON COLUMN user_emergency_contact.status IS '状态1=启用3=已归档'`,
`COMMENT ON COLUMN user_emergency_contact.user_account_id IS '资料所属用户内部主键,仅本人可读写'`,
`COMMENT ON COLUMN user_emergency_contact.name IS '联系人姓名最多64字'`,
`COMMENT ON COLUMN user_emergency_contact.phone IS '联系人大陆手机号,仅本人接口返回用于编辑及联系'`,
`COMMENT ON COLUMN user_emergency_contact.relationship IS '与用户关系描述最多32字空字符串表示未填写'`,
`COMMENT ON COLUMN user_emergency_contact.request_no IS '客户端新增请求UUID同一用户重试唯一且归档后不复活'`,
`CREATE UNIQUE INDEX IF NOT EXISTS idx_emergency_contact_active_phone ON user_emergency_contact(user_account_id,phone) WHERE status=1 AND deleted_at IS NULL`,
})
}

View File

@@ -0,0 +1,66 @@
// 功能家庭成员与设备共享表的增量迁移版本1.0.0。
package main
// migrateFamilySharing 只新增家庭共享相关表、约束、索引和中文注释。
func migrateFamilySharing() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS user_family_member (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)), owner_user_account_id bigint NOT NULL REFERENCES user_account(id), member_user_account_id bigint NOT NULL DEFAULT 0,
name varchar(64) NOT NULL, phone varchar(32) NOT NULL, relationship varchar(32) NOT NULL DEFAULT '', invite_status integer NOT NULL DEFAULT 10 CHECK (invite_status IN (10,20,30,40)),
request_no varchar(36) NOT NULL, expires_at timestamptz NOT NULL, accepted_at timestamptz, revoked_at timestamptz,
UNIQUE(owner_user_account_id, request_no))`,
`COMMENT ON TABLE user_family_member IS '家庭成员邀请与确认关系;紧急联系人不自动成为家庭成员'`,
`COMMENT ON COLUMN user_family_member.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_family_member.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_family_member.created_at IS '邀请创建时间'`,
`COMMENT ON COLUMN user_family_member.updated_at IS '最后修改时间'`,
`COMMENT ON COLUMN user_family_member.deleted_at IS '软删除时间'`,
`COMMENT ON COLUMN user_family_member.status IS '通用状态1=启用3=归档'`,
`COMMENT ON COLUMN user_family_member.owner_user_account_id IS '房主用户内部主键'`,
`COMMENT ON COLUMN user_family_member.member_user_account_id IS '已接受邀请的成员用户内部主键0=尚未接受'`,
`COMMENT ON COLUMN user_family_member.name IS '房主填写的成员称呼'`,
`COMMENT ON COLUMN user_family_member.phone IS '受邀手机号;接口仅脱敏显示'`,
`COMMENT ON COLUMN user_family_member.relationship IS '与房主关系描述'`,
`COMMENT ON COLUMN user_family_member.invite_status IS '邀请状态10=待确认20=已接受30=已拒绝40=已撤销'`,
`COMMENT ON COLUMN user_family_member.request_no IS '同一房主邀请请求幂等UUID'`,
`COMMENT ON COLUMN user_family_member.expires_at IS '邀请失效时间'`,
`COMMENT ON COLUMN user_family_member.accepted_at IS '成员接受邀请时间'`,
`COMMENT ON COLUMN user_family_member.revoked_at IS '房主撤销成员时间'`,
`CREATE UNIQUE INDEX IF NOT EXISTS ux_family_active_phone ON user_family_member(owner_user_account_id,phone) WHERE status=1 AND invite_status IN (10,20) AND deleted_at IS NULL`,
`CREATE INDEX IF NOT EXISTS idx_family_invitee ON user_family_member(phone,invite_status,status)`,
`CREATE TABLE IF NOT EXISTS user_device_share (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)), family_member_id bigint NOT NULL REFERENCES user_family_member(id), product_info_id bigint NOT NULL REFERENCES product_info(id),
can_view boolean NOT NULL DEFAULT false, can_alert boolean NOT NULL DEFAULT false, can_control boolean NOT NULL DEFAULT false, UNIQUE(family_member_id,product_info_id))`,
`COMMENT ON TABLE user_device_share IS '家庭成员按设备分配的查看、告警和控制权限'`,
`COMMENT ON COLUMN user_device_share.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_device_share.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_device_share.created_at IS '授权创建时间'`,
`COMMENT ON COLUMN user_device_share.updated_at IS '授权最后修改时间'`,
`COMMENT ON COLUMN user_device_share.deleted_at IS '软删除时间'`,
`COMMENT ON COLUMN user_device_share.status IS '授权状态1=启用3=归档'`,
`COMMENT ON COLUMN user_device_share.family_member_id IS '家庭成员关系内部主键'`,
`COMMENT ON COLUMN user_device_share.product_info_id IS '房主名下设备内部主键'`,
`COMMENT ON COLUMN user_device_share.can_view IS '是否允许查看设备资料和状态'`,
`COMMENT ON COLUMN user_device_share.can_alert IS '是否允许接收危险告警'`,
`COMMENT ON COLUMN user_device_share.can_control IS '是否允许发起远程控制;执行时仍需二次确认'`,
`CREATE TABLE IF NOT EXISTS user_family_audit (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE, created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1, owner_user_account_id bigint NOT NULL REFERENCES user_account(id), family_member_id bigint NOT NULL DEFAULT 0,
actor_user_account_id bigint NOT NULL REFERENCES user_account(id), action varchar(32) NOT NULL, summary varchar(512) NOT NULL DEFAULT '')`,
`COMMENT ON TABLE user_family_audit IS '家庭邀请和设备授权操作审计,只记录必要摘要'`,
`COMMENT ON COLUMN user_family_audit.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_family_audit.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_family_audit.created_at IS '操作发生时间'`,
`COMMENT ON COLUMN user_family_audit.updated_at IS '记录更新时间'`,
`COMMENT ON COLUMN user_family_audit.deleted_at IS '软删除时间,审计记录正常不删除'`,
`COMMENT ON COLUMN user_family_audit.status IS '记录状态1=有效'`,
`COMMENT ON COLUMN user_family_audit.owner_user_account_id IS '房主用户内部主键'`,
`COMMENT ON COLUMN user_family_audit.family_member_id IS '关联成员内部主键0=无成员'`,
`COMMENT ON COLUMN user_family_audit.actor_user_account_id IS '执行操作的用户内部主键'`,
`COMMENT ON COLUMN user_family_audit.action IS '操作类型invite/accept/reject/update_permissions/revoke'`,
`COMMENT ON COLUMN user_family_audit.summary IS '不含手机号的操作摘要'`,
`CREATE INDEX IF NOT EXISTS idx_family_audit_owner_time ON user_family_audit(owner_user_account_id,created_at DESC)`,
})
}

View File

@@ -0,0 +1,30 @@
// 功能描述仅新增电商收藏关系表与中文注释版本1.0.0。
package main
// migrateEcFavorite 使用既有环境数据库配置,不执行其他表迁移或初始化数据。
func migrateEcFavorite() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS ec_favorite (
id bigserial PRIMARY KEY,
identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL,
updated_at timestamptz NOT NULL,
deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1, 3)),
user_account_id bigint NOT NULL REFERENCES user_account(id),
ec_product_id bigint NOT NULL REFERENCES ec_product(id),
revision bigint NOT NULL DEFAULT 1 CHECK (revision > 0),
CONSTRAINT ux_ec_favorite_owner_product UNIQUE (user_account_id, ec_product_id)
)`,
`COMMENT ON TABLE ec_favorite IS '电商商品收藏关系;每账户每商品唯一,取消后归档,下架商品保留'`,
`COMMENT ON COLUMN ec_favorite.id IS '收藏关系内部自增主键,不向客户端暴露'`,
`COMMENT ON COLUMN ec_favorite.identity IS '收藏关系公开UUID V7标识'`,
`COMMENT ON COLUMN ec_favorite.created_at IS '首次建立收藏关系的服务端时间'`,
`COMMENT ON COLUMN ec_favorite.updated_at IS '最近一次收藏状态变化的服务端时间'`,
`COMMENT ON COLUMN ec_favorite.deleted_at IS '软删除时间;用户取消收藏不写此字段而使用归档状态'`,
`COMMENT ON COLUMN ec_favorite.status IS '收藏状态1=已收藏3=已取消并归档'`,
`COMMENT ON COLUMN ec_favorite.user_account_id IS '收藏者账户内部关联键对应user_account.id'`,
`COMMENT ON COLUMN ec_favorite.ec_product_id IS '收藏商品内部关联键对应ec_product.id商品下架不删除关系'`,
`COMMENT ON COLUMN ec_favorite.revision IS '正整数并发版本首次为1每次收藏状态变化递增相同目标重试不递增'`,
})
}

View File

@@ -37,6 +37,36 @@ func main() {
os.Exit(1)
}
switch os.Args[1] {
case "migrate-family-sharing":
if err := migrateFamilySharing(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("family sharing tables ready")
case "migrate-usage-messages":
if err := migrateUsageMessages(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("usage statistics and message read tables ready")
case "migrate-device-groups":
if err := migrateDeviceGroups(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("device group table ready")
case "migrate-device-mapping":
if err := migrateDeviceMapping(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("device mapping columns ready")
case "migrate-emergency-contacts":
if err := migrateEmergencyContacts(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("emergency contact table ready")
case "version":
fmt.Println("platform-cli 0.1.0")
case "resource-contract":
@@ -88,11 +118,48 @@ func main() {
os.Exit(1)
}
fmt.Printf("mock refund reviewers repaired: %d\n", count)
case "repair-mock-user-password":
count, err := repairMockUserPassword()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Printf("mock user passwords repaired: %d\n", count)
case "repair-legacy-gas-account":
if err := repairLegacyGasAccount(os.Args[2:], os.Stdout); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
case "migrate-user-address":
if err := migrateUserAddress(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("user address additive migration completed")
case "migrate-ec-favorite":
if err := migrateEcFavorite(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("ec favorite additive migration completed")
case "migrate-deposit":
if err := migrateDeposit(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("deposit additive migration completed")
case "migrate-ticket-contact":
if err := migrateTicketContact(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("ticket contact additive migration completed")
case "migrate-ticket-contract":
if err := migrateTicketContract(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
fmt.Println("ticket contract additive migration completed")
case "migrate":
if err := migrateDatabase(); err != nil {
fmt.Fprintln(os.Stderr, err)
@@ -107,7 +174,7 @@ func main() {
}
func printUsage() {
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|mock-data|repair-mock-gasorder-status|repair-mock-ec-order|repair-mock-track-point|repair-mock-refund-reviewer|repair-legacy-gas-account>")
fmt.Fprintln(os.Stderr, "usage: platform-cli <version|resource-contract|gas-resource-contract|delivery-resource-contract|migrate|migrate-family-sharing|migrate-device-groups|migrate-device-mapping|migrate-emergency-contacts|migrate-user-address|migrate-ticket-contact|migrate-ticket-contract|migrate-ec-favorite|migrate-deposit|mock-data|repair-mock-gasorder-status|repair-mock-ec-order|repair-mock-track-point|repair-mock-refund-reviewer|repair-mock-user-password|repair-legacy-gas-account>")
}
type route struct {
@@ -302,6 +369,23 @@ func repairMockRefundReviewer() (int64, error) {
return count, nil
}
// repairMockUserPassword 使用当前远程配置,只恢复固定开发账号凭据。
func repairMockUserPassword() (int64, error) {
config.New(serviceKey)
if config.Spec.Databases == nil {
return 0, fmt.Errorf("database configuration is required")
}
databaseService, err := database.NewDatabase(
config.Spec.Databases.Driver,
config.Spec.Databases.Source,
dbsql.SetOptions(nil),
)
if err != nil {
return 0, fmt.Errorf("connect database: %w", err)
}
return seed.RepairMockUserPassword(databaseService)
}
// repairLegacyGasAccount 显式修复唯一历史 010 气站账号,并将前后值写入操作审计表。
func repairLegacyGasAccount(arguments []string, output io.Writer) error {
flags := flag.NewFlagSet("repair-legacy-gas-account", flag.ContinueOnError)

View File

@@ -0,0 +1,56 @@
// 功能创建用气统计和用户消息已读表并补齐全部数据库注释版本1.0.0。
package main
// migrateUsageMessages 只新增统计及已读表,不生成计量数据或业务消息。
func migrateUsageMessages() error {
return migrateClientColumns([]string{
`CREATE TABLE IF NOT EXISTS dev_usage_stat (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,2,3)),
product_info_id bigint NOT NULL REFERENCES product_info(id), stat_date date NOT NULL,
usage_kg numeric(12,3) NOT NULL DEFAULT 0 CHECK (usage_kg >= 0),
breakfast_kg numeric(12,3) NOT NULL DEFAULT 0 CHECK (breakfast_kg >= 0),
lunch_kg numeric(12,3) NOT NULL DEFAULT 0 CHECK (lunch_kg >= 0),
dinner_kg numeric(12,3) NOT NULL DEFAULT 0 CHECK (dinner_kg >= 0),
source varchar(32) NOT NULL CHECK (source IN ('meter','device','manual')),
calc_version varchar(32) NOT NULL, calculated_at timestamptz NOT NULL,
CONSTRAINT ux_dev_usage_stat_day UNIQUE(product_info_id,stat_date)
)`,
`COMMENT ON TABLE dev_usage_stat IS '实体设备每日用气统计事实;数据来源和计算口径必须可追溯'`,
`COMMENT ON COLUMN dev_usage_stat.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN dev_usage_stat.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN dev_usage_stat.created_at IS '统计记录创建时间'`,
`COMMENT ON COLUMN dev_usage_stat.updated_at IS '统计记录最后更新时间'`,
`COMMENT ON COLUMN dev_usage_stat.deleted_at IS '软删除时间'`,
`COMMENT ON COLUMN dev_usage_stat.status IS '通用状态1=启用2=停用3=归档'`,
`COMMENT ON COLUMN dev_usage_stat.product_info_id IS '统计所属实体设备内部主键'`,
`COMMENT ON COLUMN dev_usage_stat.stat_date IS '统计日期,按用户服务时区划分'`,
`COMMENT ON COLUMN dev_usage_stat.usage_kg IS '当日总用气量,单位千克'`,
`COMMENT ON COLUMN dev_usage_stat.breakfast_kg IS '早餐时段用气量,单位千克'`,
`COMMENT ON COLUMN dev_usage_stat.lunch_kg IS '午餐时段用气量,单位千克'`,
`COMMENT ON COLUMN dev_usage_stat.dinner_kg IS '晚餐时段用气量,单位千克'`,
`COMMENT ON COLUMN dev_usage_stat.source IS '数据来源meter=计量表device=设备上报manual=人工导入'`,
`COMMENT ON COLUMN dev_usage_stat.calc_version IS '生成统计的计算口径版本'`,
`COMMENT ON COLUMN dev_usage_stat.calculated_at IS '统计计算完成时间'`,
`CREATE INDEX IF NOT EXISTS idx_dev_usage_stat_query ON dev_usage_stat(product_info_id,stat_date,status)`,
`CREATE TABLE IF NOT EXISTS user_message_read (
id bigserial PRIMARY KEY, identity varchar(36) NOT NULL UNIQUE,
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL, deleted_at timestamptz,
status integer NOT NULL DEFAULT 1 CHECK (status IN (1,3)),
user_account_id bigint NOT NULL REFERENCES user_account(id), message_key varchar(96) NOT NULL,
read_at timestamptz NOT NULL, CONSTRAINT ux_user_message_read UNIQUE(user_account_id,message_key)
)`,
`COMMENT ON TABLE user_message_read IS '用户对订单、工单及公告衍生消息的已读回执;消息正文仍取自业务事实表'`,
`COMMENT ON COLUMN user_message_read.id IS '数据库自增内部主键'`,
`COMMENT ON COLUMN user_message_read.identity IS '对外业务UUID标识'`,
`COMMENT ON COLUMN user_message_read.created_at IS '回执创建时间'`,
`COMMENT ON COLUMN user_message_read.updated_at IS '回执最后更新时间'`,
`COMMENT ON COLUMN user_message_read.deleted_at IS '软删除时间'`,
`COMMENT ON COLUMN user_message_read.status IS '通用状态1=启用3=归档'`,
`COMMENT ON COLUMN user_message_read.user_account_id IS '读取消息的用户账号内部主键'`,
`COMMENT ON COLUMN user_message_read.message_key IS '消息来源类型与业务唯一标识组成的稳定键'`,
`COMMENT ON COLUMN user_message_read.read_at IS '用户首次确认已读时间'`,
`CREATE INDEX IF NOT EXISTS idx_user_message_read_lookup ON user_message_read(user_account_id,status)`,
})
}