初始化

This commit is contained in:
SunYF
2026-08-28 14:07:22 +08:00
parent a185247ab1
commit b34325a16f
971 changed files with 291 additions and 220360 deletions
@@ -1,282 +0,0 @@
package store
import (
"database/sql"
"fmt"
"strings"
"time"
_ "modernc.org/sqlite" // 纯 Go SQLite 驱动(驱动名 "sqlite"),避免 CGO
)
// Store 本地 SQLite 缓存库封装(拧紧结果 + 上报队列 + 内置账号)。
type Store struct {
db *sql.DB
}
const ddl = `
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE,
password_hash TEXT,
real_name TEXT,
role TEXT NOT NULL DEFAULT 'operator'
);
CREATE TABLE IF NOT EXISTS torque_results (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_order_no TEXT,
sn TEXT,
dock_code TEXT,
screw_no TEXT,
torque REAL,
angle REAL,
result TEXT,
operator TEXT,
synced INTEGER DEFAULT 0,
created_at INTEGER
);
CREATE TABLE IF NOT EXISTS report_queue (
id INTEGER PRIMARY KEY AUTOINCREMENT,
kind TEXT, -- process_done | temp_store
payload TEXT, -- 待透传给 MES 的 JSON body
synced INTEGER DEFAULT 0,
retry_count INTEGER DEFAULT 0,
created_at INTEGER
);
`
// Open 打开(必要时创建)本地 SQLite 数据库并建表。
func Open(path string) (*Store, error) {
dsn := path +
"?_pragma=busy_timeout(10000)" +
"&_pragma=journal_mode(WAL)" +
"&_pragma=synchronous(NORMAL)"
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
// 单连接串行化读写,规避多连接下的 SQLITE_BUSY 锁冲突(触屏终端低并发场景足够)
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
db.SetConnMaxLifetime(0)
if _, err := db.Exec(ddl); err != nil {
db.Close()
return nil, fmt.Errorf("初始化表结构失败: %w", err)
}
s := &Store{db: db}
if err := s.migrateUsersRole(); err != nil {
db.Close()
return nil, err
}
return s, nil
}
// migrateUsersRole 兼容旧库:role 列为后续补充字段时自动补齐。
func (s *Store) migrateUsersRole() error {
rows, err := s.db.Query(`SELECT name FROM pragma_table_info('users') WHERE name='role'`)
if err != nil {
return err
}
defer rows.Close()
if rows.Next() {
return rows.Err()
}
_, err = s.db.Exec(`ALTER TABLE users ADD COLUMN role TEXT NOT NULL DEFAULT 'operator'`)
return err
}
func (s *Store) Close() error { return s.db.Close() }
// Ping 健康检查用。
func (s *Store) Ping() error {
var one int
return s.db.QueryRow(`SELECT 1`).Scan(&one)
}
// ---------- 用户 ----------
type User struct {
ID int64
Username string
PasswordHash string
RealName string
Role string
}
func (s *Store) CountUsers() (int64, error) {
var n int64
err := s.db.QueryRow(`SELECT COUNT(*) FROM users`).Scan(&n)
return n, err
}
func (s *Store) CreateUser(u User) error {
res, err := s.db.Exec(
`INSERT INTO users(username, password_hash, real_name, role) VALUES(?,?,?,?)`,
u.Username, u.PasswordHash, u.RealName, u.Role,
)
if err != nil {
return err
}
u.ID, _ = res.LastInsertId()
return nil
}
func (s *Store) GetUserByUsername(username string) (*User, error) {
var u User
err := s.db.QueryRow(
`SELECT id, username, password_hash, COALESCE(real_name,''), COALESCE(role,'operator')
FROM users WHERE username = ?`, username,
).Scan(&u.ID, &u.Username, &u.PasswordHash, &u.RealName, &u.Role)
if err != nil {
return nil, err
}
return &u, nil
}
// ---------- 拧紧结果 ----------
type TorqueResult struct {
ID int64 `json:"id"`
WorkOrderNo string `json:"workOrderNo"`
Sn string `json:"sn"`
DockCode string `json:"dockCode"`
ScrewNo string `json:"screwNo"`
Torque float64 `json:"torque"`
Angle float64 `json:"angle"`
Result string `json:"result"` // OK | NG
Operator string `json:"operator"`
Synced bool `json:"synced"`
CreatedAt int64 `json:"createdAt"` // 毫秒时间戳(前端直接 new Date 渲染)
}
func (s *Store) InsertTorqueResult(t TorqueResult) (int64, error) {
res, err := s.db.Exec(
`INSERT INTO torque_results(work_order_no, sn, dock_code, screw_no, torque, angle, result, operator, synced, created_at)
VALUES(?,?,?,?,?,?,?,?,0,?)`,
t.WorkOrderNo, t.Sn, t.DockCode, t.ScrewNo, t.Torque, t.Angle, t.Result, t.Operator, time.Now().Unix(),
)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
// ListTorqueResults 查询本地拧紧结果,最新优先;sn 为空查全部,onlyPending 仅查未同步。
func (s *Store) ListTorqueResults(sn string, onlyPending bool, limit int) ([]TorqueResult, error) {
query := `SELECT id, work_order_no, sn, dock_code, screw_no, torque, angle, result, operator, synced, created_at FROM torque_results`
var conds []string
var args []any
if sn != "" {
conds = append(conds, "sn = ?")
args = append(args, sn)
}
if onlyPending {
conds = append(conds, "synced = 0")
}
if len(conds) > 0 {
query += " WHERE " + strings.Join(conds, " AND ")
}
query += ` ORDER BY id DESC LIMIT ?`
args = append(args, limit)
rows, err := s.db.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
list := make([]TorqueResult, 0)
for rows.Next() {
var t TorqueResult
var synced int64
var createdAt int64
if err := rows.Scan(&t.ID, &t.WorkOrderNo, &t.Sn, &t.DockCode, &t.ScrewNo,
&t.Torque, &t.Angle, &t.Result, &t.Operator, &synced, &createdAt); err != nil {
return nil, err
}
t.Synced = synced == 1
t.CreatedAt = createdAt * 1000 // 秒 → 毫秒
list = append(list, t)
}
return list, rows.Err()
}
func (s *Store) MarkTorqueSynced(ids []int64) error {
tx, err := s.db.Begin()
if err != nil {
return err
}
for _, id := range ids {
if _, err := tx.Exec(`UPDATE torque_results SET synced=1 WHERE id=?`, id); err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
func (s *Store) PendingTorqueCount() (int64, error) {
var n int64
err := s.db.QueryRow(`SELECT COUNT(*) FROM torque_results WHERE synced=0`).Scan(&n)
return n, err
}
// ---------- 上报队列 ----------
type QueueItem struct {
ID int64
Kind string // process_done | temp_store
Payload string
RetryCount int
CreatedAt int64
}
func (s *Store) InsertQueueItem(kind, payload string) error {
_, err := s.db.Exec(
`INSERT INTO report_queue(kind, payload, synced, retry_count, created_at) VALUES(?,?,0,0,?)`,
kind, payload, time.Now().Unix(),
)
return err
}
func (s *Store) PendingQueueItems(limit int) ([]QueueItem, error) {
rows, err := s.db.Query(
`SELECT id, kind, payload, retry_count, created_at FROM report_queue WHERE synced=0 ORDER BY id ASC LIMIT ?`,
limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
list := make([]QueueItem, 0)
for rows.Next() {
var it QueueItem
if err := rows.Scan(&it.ID, &it.Kind, &it.Payload, &it.RetryCount, &it.CreatedAt); err != nil {
return nil, err
}
list = append(list, it)
}
return list, rows.Err()
}
func (s *Store) MarkQueueSynced(id int64) error {
_, err := s.db.Exec(`UPDATE report_queue SET synced=1 WHERE id=?`, id)
return err
}
// BumpQueueRetry 失败累计重试次数(synced 保持 0)。
func (s *Store) BumpQueueRetry(id int64) error {
_, err := s.db.Exec(`UPDATE report_queue SET retry_count=retry_count+1 WHERE id=?`, id)
return err
}
func (s *Store) PendingQueueCount() (int64, error) {
var n int64
err := s.db.QueryRow(`SELECT COUNT(*) FROM report_queue WHERE synced=0`).Scan(&n)
return n, err
}
@@ -1,43 +0,0 @@
package store
import (
"golang.org/x/crypto/bcrypt"
"github.com/zeromicro/go-zero/core/logx"
)
// SeedIfEmpty 无任何用户时创建内置账号:
// - admin / admin123(显示名 wsadmin,角色 admin
// - operator1 / 123456 (显示名 王工, 角色 operator)
func SeedIfEmpty(s *Store) error {
n, err := s.CountUsers()
if err != nil {
return err
}
if n > 0 {
return nil
}
if err := insertUser(s, "admin", "admin123", "wsadmin", "admin"); err != nil {
return err
}
if err := insertUser(s, "operator1", "123456", "王工", "operator"); err != nil {
return err
}
logx.Info("本地无用户,已初始化内置账号:admin/admin123、operator1/123456")
return nil
}
func insertUser(s *Store, username, password, realName, role string) error {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
return s.CreateUser(User{
Username: username,
PasswordHash: string(hash),
RealName: realName,
Role: role,
})
}