feat: add run.ps1 script to start projects easily

add a powershell script that supports starting MES, WMS, WMSClient, Workstation and Dashboard projects with one command, includes build and run logic for backend services and npm dev for dashboard
This commit is contained in:
SunYF
2026-08-28 17:03:03 +08:00
parent a9c3fbeb41
commit cbd55338f1
30 changed files with 592 additions and 61 deletions
+18 -14
View File
@@ -10,16 +10,15 @@ import (
// Config 全局配置
type Config struct {
rest.RestConf
Host string `json:",default=0.0.0.0"`
Port int `json:",default=8080"`
Database db.DatabaseConf
Redis RedisConfWrapper
Auth AuthConf
Internal InternalConf
Logger logx.LogConf
Wms WmsConf
Plc PlcConf
rest.RestConf // 自带 Host/Port 等
Database db.DatabaseConf
Redis RedisConfWrapper
Auth AuthConf
Internal InternalConf
Cli CliConf
Logger logx.LogConf
Wms WmsConf
Plc PlcConf
}
type RedisConfWrapper struct {
@@ -38,6 +37,11 @@ type InternalConf struct {
Token string `json:",default=Hardman_2026"`
}
// CliConf 命令行敏感操作门禁密码(与管理员账号密码保持一致,改密时自动同步)
type CliConf struct {
AdminPassword string `json:"AdminPassword,default=Hardman_2026"`
}
// WmsConf WMS 内部服务地址
type WmsConf struct {
BaseURL string `json:",default="`
@@ -46,7 +50,7 @@ type WmsConf struct {
// PlcConf PLC 对接配置(mock 时不用)
type PlcConf struct {
Enable bool `json:",default=false"`
Host string `json:",default=127.0.0.1"`
Port int `json:",default=102"`
}
Enable bool `json:",default=false"`
Host string `json:",default=127.0.0.1"`
Port int `json:",default=102"`
}
+41 -1
View File
@@ -1,6 +1,7 @@
package db
import (
"context"
"database/sql"
"fmt"
"time"
@@ -54,4 +55,43 @@ func NewDB(c DatabaseConf) (*sql.DB, error) {
return nil, err
}
return sqlDB, nil
}
}
// AutoMigrate 执行 ent 自动建表/补齐(只增,不删数据)。供命令行 migrate 使用。
func AutoMigrate(c DatabaseConf) error {
client, sqlDB := MustNewDB(c)
defer sqlDB.Close()
return client.Schema.Create(context.Background())
}
// DropAllTables 清空 public schema 下所有业务表(破坏性,仅供命令行 reset-all 使用)。
func DropAllTables(c DatabaseConf) error {
sqlDB, err := NewDB(c)
if err != nil {
return err
}
defer sqlDB.Close()
rows, err := sqlDB.Query(`SELECT tablename FROM pg_tables WHERE schemaname='public'`)
if err != nil {
return err
}
var names []string
for rows.Next() {
var t string
if err := rows.Scan(&t); err != nil {
rows.Close()
return err
}
names = append(names, t)
}
rows.Close()
for _, t := range names {
if _, err := sqlDB.Exec(`DROP TABLE IF EXISTS "` + t + `" CASCADE`); err != nil {
return err
}
}
return nil
}
+37 -2
View File
@@ -3,8 +3,10 @@ package handler
import (
"net/http"
"strconv"
"strings"
"bj_power_mes/common/httpx"
tokenx "bj_power_mes/common/token"
"bj_power_mes/internal/logic"
"bj_power_mes/internal/svc"
@@ -27,6 +29,22 @@ func ctxUserId(r *http.Request) int {
return 0
}
// uidFromRequest 取当前登录用户ID:优先 context,缺失时回退解析 Authorization Bearer token(更稳健)
func uidFromRequest(r *http.Request, svcCtx *svc.ServiceContext) int {
if uid := ctxUserId(r); uid > 0 {
return uid
}
auth := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
if auth == "" || auth == r.Header.Get("Authorization") {
return 0
}
claims, err := tokenx.Parse(svcCtx.Config.Auth.AccessSecret, auth)
if err == nil && claims.UserId > 0 {
return claims.UserId
}
return 0
}
// ctxOperator 当前操作人:优先取 name,其次 username
func ctxOperator(r *http.Request) string {
if v, ok := r.Context().Value("name").(string); ok && v != "" {
@@ -82,7 +100,7 @@ func RefreshHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
func UserInfoHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
data, err := logic.New(svcCtx).UserInfo(r.Context(), ctxUserId(r))
data, err := logic.New(svcCtx).UserInfo(r.Context(), uidFromRequest(r, svcCtx))
if err != nil {
httpx.Fail(w, 1003, err.Error())
return
@@ -154,6 +172,23 @@ func DeleteUserHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
}
// ---------- 修改自己的密码 ----------
func ChangePasswordHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req logic.ChangePasswordReq
if err := httpx.ParseJSON(r, &req); err != nil {
httpx.BadRequest(w, "请求体解析失败")
return
}
if err := logic.New(svcCtx).ChangePassword(r.Context(), uidFromRequest(r, svcCtx), req); err != nil {
httpx.Fail(w, 1017, err.Error())
return
}
httpx.OkMessage(w, "密码修改成功", nil)
}
}
// ---------- 角色管理 ----------
func ListRolesHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
@@ -258,4 +293,4 @@ func DeletePermissionHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
}
httpx.OkMessage(w, "删除成功", nil)
}
}
}
+2 -1
View File
@@ -20,6 +20,7 @@ func RegisterRoutes(server *rest.Server, serverCtx *svc.ServiceContext) {
jwtAdmin := []rest.Route{
{Method: http.MethodGet, Path: "/userinfo", Handler: UserInfoHandler(serverCtx)},
{Method: http.MethodPost, Path: "/seed", Handler: SeedHandler(serverCtx)},
{Method: http.MethodPost, Path: "/user/change-password", Handler: ChangePasswordHandler(serverCtx)},
{Method: http.MethodGet, Path: "/users", Handler: ListUsersHandler(serverCtx)},
{Method: http.MethodPost, Path: "/users", Handler: CreateUserHandler(serverCtx)},
@@ -40,4 +41,4 @@ func RegisterRoutes(server *rest.Server, serverCtx *svc.ServiceContext) {
rest.WithPrefix("/api/v1"),
rest.WithJwt(serverCtx.Config.Auth.AccessSecret),
)
}
}
+30
View File
@@ -42,6 +42,11 @@ type UserInfoResp struct {
PermissionCodes []string `json:"permissionCodes"`
}
type ChangePasswordReq struct {
OldPassword string `json:"oldPassword"`
NewPassword string `json:"newPassword"`
}
type permissionItem struct {
Code string `json:"code"`
Name string `json:"name"`
@@ -148,6 +153,31 @@ func (s *Service) UserInfo(ctx context.Context, userId int) (UserInfoResp, error
return out, nil
}
// ChangePassword 当前登录用户修改自己的密码
func (s *Service) ChangePassword(ctx context.Context, userId int, req ChangePasswordReq) error {
if userId <= 0 {
return errors.New("未登录")
}
if req.OldPassword == "" || req.NewPassword == "" {
return errors.New("请输入旧密码与新密码")
}
if len(req.NewPassword) < 6 {
return errors.New("新密码长度至少 6 位")
}
usr, err := s.ctx.EntClient.User.Get(ctx, userId)
if err != nil {
return errors.New("用户不存在")
}
if bcrypt.CompareHashAndPassword([]byte(usr.Password), []byte(req.OldPassword)) != nil {
return errors.New("旧密码不正确")
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
if err != nil {
return err
}
return s.ctx.EntClient.User.UpdateOneID(userId).SetPassword(string(hash)).Exec(ctx)
}
// ------ 用户 / 角色 / 权限 管理 ------
type UserReq struct {
+12 -8
View File
@@ -33,14 +33,18 @@ func (s *Service) Seed(ctx context.Context) error {
_ = s.ctx.EntClient.Role.Create().SetName(r.name).SetCode(r.code).SetPermissionCodes(codes).Exec(ctx)
}
// 2. 超级管理员账号 admin / Hardman_2026
if !s.ctx.EntClient.User.Query().Where(user.Username("admin")).ExistX(ctx) {
hash, _ := bcrypt.GenerateFromPassword([]byte("Hardman_2026"), bcrypt.DefaultCost)
adminRole, _ := s.ctx.EntClient.Role.Query().Where(role.Code("SUPER_ADMIN")).First(ctx)
roleId := 0
if adminRole != nil {
roleId = adminRole.ID
}
// 2. 超级管理员账号 admin / Hardman_2026(对已存在的旧行做对齐,避免残留禁用状态)
hash, _ := bcrypt.GenerateFromPassword([]byte("Hardman_2026"), bcrypt.DefaultCost)
adminRole, _ := s.ctx.EntClient.Role.Query().Where(role.Code("SUPER_ADMIN")).First(ctx)
roleId := 0
if adminRole != nil {
roleId = adminRole.ID
}
if s.ctx.EntClient.User.Query().Where(user.Username("admin")).ExistX(ctx) {
_ = s.ctx.EntClient.User.Update().
Where(user.Username("admin")).
SetPassword(string(hash)).SetStatus("ENABLED").SetRoleId(roleId).Exec(ctx)
} else {
_ = s.ctx.EntClient.User.Create().
SetUsername("admin").SetPassword(string(hash)).SetName("系统管理员").
SetRoleId(roleId).SetStatus("ENABLED").Exec(ctx)