初始化2
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
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
|
||||
);
|
||||
|
||||
-- 操作日志(可追溯):含工单号与操作人
|
||||
CREATE TABLE IF NOT EXISTS event_log (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
work_order_no TEXT,
|
||||
operator TEXT,
|
||||
action TEXT, -- login | process_done | temp_store | scan_report | logout ...
|
||||
detail TEXT,
|
||||
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 {
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO users(username, password_hash, real_name, role) VALUES(?,?,?,?)`,
|
||||
u.Username, u.PasswordHash, u.RealName, u.Role,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
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"` // 毫秒时间戳
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// ---------- 操作日志 ----------
|
||||
|
||||
// AddEventLog 记录一条操作日志(含工单号与操作人),用于追溯。
|
||||
func (s *Store) AddEventLog(workOrderNo, operator, action, detail string) {
|
||||
_, err := s.db.Exec(
|
||||
`INSERT INTO event_log(work_order_no, operator, action, detail, created_at) VALUES(?,?,?,?,?)`,
|
||||
workOrderNo, operator, action, detail, time.Now().Unix(),
|
||||
)
|
||||
if err != nil {
|
||||
return // 日志写入失败不影响主流程
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"github.com/zeromicro/go-zero/core/logx"
|
||||
)
|
||||
|
||||
// SeedIfEmpty 无任何用户时创建内置账号:
|
||||
// - admin / admin123(显示名 系统管理员,角色 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", "系统管理员", "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,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user