资深Golang全栈开发工程师(远程职位)

资深Golang全栈开发工程师(远程职位) 大家好,

你们好吗?希望你和你的家人都平安健康。 我正在寻找一位能够开发以下应用程序的开发者。

这是一个运行在 Windows 10 上的客户端-服务器-API 应用程序,能够自定义安装在 Windows 10 上的应用程序(.exe)在多个显示器上的打开/关闭位置(像素)和大小。 例如,4个 Firefox 实例、3个 Chrome 实例、VLC、Vscode、Photoshop 等等…… 类似于一个窗口管理器。 客户端可以使用 HTML5/CSS/JS/React/Angular/Vue。

最终产品的示例如下。

  1. https://www.dropbox.com/home?preview=DSC_0585.MOV
  2. https://www.dropbox.com/s/ts94dly4k6fl9b6/NACCHY_VIDEO.mp4?dl=0

该应用程序需要以客户端-服务器-API 的方式工作,WEB JS 客户端通过图形界面控制应用程序(.exe)如何在服务器端(另一台连接显示器的 Windows 10 计算机)打开。 支持多用户,并能集成 LDAP 进行注册。 将预设保存到数据库。每个预设类似于视频中展示的内容。

  1. 多个不同的客户端可以在服务器机器上编程布局和更改布局,控制程序的显示/行为方式。
  2. 客户端可以创建多个不同的布局,保存它们,并能像视频中演示的那样快速调用。https://www.dropbox.com/home?preview=DSC_0585.MOV

谢谢。


更多关于资深Golang全栈开发工程师(远程职位)的实战教程也可以访问 https://www.itying.com/category-94-b0.html

4 回复

每个链接都要求我登录 Dropbox,这些视频在其他地方可以观看吗?

更多关于资深Golang全栈开发工程师(远程职位)的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,德里克,

我很乐意与你合作。 私信已发送,请查收。

此致 劳伦·W

这是一个典型的Windows窗口管理后端服务需求,结合Web前端进行远程控制。以下是使用Go语言实现核心功能的示例:

1. Windows窗口管理核心模块

// window_manager_windows.go
// +build windows

package main

import (
    "fmt"
    "syscall"
    "unsafe"
)

type WindowManager struct {
    user32 *syscall.DLL
}

func NewWindowManager() (*WindowManager, error) {
    user32, err := syscall.LoadDLL("user32.dll")
    if err != nil {
        return nil, err
    }
    return &WindowManager{user32: user32}, nil
}

// 查找窗口句柄
func (wm *WindowManager) FindWindow(processName string) (syscall.Handle, error) {
    findWindow := wm.user32.MustFindProc("FindWindowW")
    hwnd, _, err := findWindow.Call(
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))),
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(processName))),
    )
    if hwnd == 0 {
        return 0, fmt.Errorf("window not found: %v", err)
    }
    return syscall.Handle(hwnd), nil
}

// 移动和调整窗口大小
func (wm *WindowManager) MoveWindow(hwnd syscall.Handle, x, y, width, height int) error {
    moveWindow := wm.user32.MustFindProc("MoveWindow")
    ret, _, err := moveWindow.Call(
        uintptr(hwnd),
        uintptr(x),
        uintptr(y),
        uintptr(width),
        uintptr(height),
        uintptr(1), // repaint
    )
    if ret == 0 {
        return fmt.Errorf("MoveWindow failed: %v", err)
    }
    return nil
}

// 获取显示器信息
func (wm *WindowManager) GetDisplayInfo() ([]Display, error) {
    enumDisplayMonitors := wm.user32.MustFindProc("EnumDisplayMonitors")
    var displays []Display
    
    callback := syscall.NewCallback(func(hMonitor, hdcMonitor, lprcMonitor, dwData uintptr) uintptr {
        var mi MONITORINFOEX
        mi.CbSize = uint32(unsafe.Sizeof(mi))
        getMonitorInfo := wm.user32.MustFindProc("GetMonitorInfoW")
        ret, _, _ := getMonitorInfo.Call(hMonitor, uintptr(unsafe.Pointer(&mi)))
        
        if ret != 0 {
            display := Display{
                Name:      syscall.UTF16ToString(mi.SzDevice[:]),
                Width:     int(mi.RcMonitor.Right - mi.RcMonitor.Left),
                Height:    int(mi.RcMonitor.Bottom - mi.RcMonitor.Top),
                PositionX: int(mi.RcMonitor.Left),
                PositionY: int(mi.RcMonitor.Top),
            }
            displays = append(displays, display)
        }
        return 1
    })
    
    enumDisplayMonitors.Call(0, 0, callback, 0)
    return displays, nil
}

2. 进程管理模块

// process_manager.go
package main

import (
    "os/exec"
    "strings"
    "syscall"
)

type ProcessManager struct {
    processes map[string]*exec.Cmd
}

func NewProcessManager() *ProcessManager {
    return &ProcessManager{
        processes: make(map[string]*exec.Cmd),
    }
}

// 启动应用程序
func (pm *ProcessManager) StartApplication(exePath string, args []string, windowTitle string) error {
    cmd := exec.Command(exePath, args...)
    cmd.SysProcAttr = &syscall.SysProcAttr{
        CreationFlags: syscall.CREATE_NEW_CONSOLE,
    }
    
    if err := cmd.Start(); err != nil {
        return err
    }
    
    pm.processes[windowTitle] = cmd
    return nil
}

// 关闭应用程序
func (pm *ProcessManager) CloseApplication(windowTitle string) error {
    if cmd, exists := pm.processes[windowTitle]; exists {
        if err := cmd.Process.Kill(); err != nil {
            return err
        }
        delete(pm.processes, windowTitle)
    }
    return nil
}

3. REST API服务器

// main.go
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "strconv"
    "sync"
    
    "github.com/gorilla/mux"
    "gopkg.in/ldap.v2"
)

type WindowLayout struct {
    ID         string   `json:"id"`
    Name       string   `json:"name"`
    Windows    []Window `json:"windows"`
    UserID     string   `json:"user_id"`
    CreatedAt  string   `json:"created_at"`
}

type Window struct {
    ProcessName string `json:"process_name"`
    X           int    `json:"x"`
    Y           int    `json:"y"`
    Width       int    `json:"width"`
    Height      int    `json:"height"`
    Display     int    `json:"display"`
}

type APIServer struct {
    router        *mux.Router
    wm            *WindowManager
    pm            *ProcessManager
    layouts       map[string]WindowLayout
    layoutsMutex  sync.RWMutex
    ldapConn      *ldap.Conn
}

func NewAPIServer() (*APIServer, error) {
    wm, err := NewWindowManager()
    if err != nil {
        return nil, err
    }
    
    router := mux.NewRouter()
    server := &APIServer{
        router:   router,
        wm:       wm,
        pm:       NewProcessManager(),
        layouts:  make(map[string]WindowLayout),
    }
    
    server.setupRoutes()
    return server, nil
}

func (s *APIServer) setupRoutes() {
    s.router.HandleFunc("/api/layouts", s.getLayouts).Methods("GET")
    s.router.HandleFunc("/api/layouts", s.createLayout).Methods("POST")
    s.router.HandleFunc("/api/layouts/{id}", s.applyLayout).Methods("POST")
    s.router.HandleFunc("/api/windows/position", s.setWindowPosition).Methods("POST")
    s.router.HandleFunc("/api/displays", s.getDisplays).Methods("GET")
    s.router.HandleFunc("/api/auth/ldap", s.ldapAuth).Methods("POST")
}

// 获取所有显示器信息
func (s *APIServer) getDisplays(w http.ResponseWriter, r *http.Request) {
    displays, err := s.wm.GetDisplayInfo()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    json.NewEncoder(w).Encode(displays)
}

// 设置窗口位置
func (s *APIServer) setWindowPosition(w http.ResponseWriter, r *http.Request) {
    var req struct {
        ProcessName string `json:"process_name"`
        X           int    `json:"x"`
        Y           int    `json:"y"`
        Width       int    `json:"width"`
        Height      int    `json:"height"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    hwnd, err := s.wm.FindWindow(req.ProcessName)
    if err != nil {
        http.Error(w, err.Error(), http.StatusNotFound)
        return
    }
    
    if err := s.wm.MoveWindow(hwnd, req.X, req.Y, req.Width, req.Height); err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    
    w.WriteHeader(http.StatusOK)
}

// 应用保存的布局
func (s *APIServer) applyLayout(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    layoutID := vars["id"]
    
    s.layoutsMutex.RLock()
    layout, exists := s.layouts[layoutID]
    s.layoutsMutex.RUnlock()
    
    if !exists {
        http.Error(w, "Layout not found", http.StatusNotFound)
        return
    }
    
    // 应用布局中的所有窗口位置
    for _, window := range layout.Windows {
        hwnd, err := s.wm.FindWindow(window.ProcessName)
        if err == nil {
            s.wm.MoveWindow(hwnd, window.X, window.Y, window.Width, window.Height)
        }
    }
    
    w.WriteHeader(http.StatusOK)
}

// LDAP认证
func (s *APIServer) ldapAuth(w http.ResponseWriter, r *http.Request) {
    var creds struct {
        Username string `json:"username"`
        Password string `json:"password"`
    }
    
    if err := json.NewDecoder(r.Body).Decode(&creds); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    
    // LDAP连接和认证
    l, err := ldap.Dial("tcp", "ldap-server:389")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer l.Close()
    
    err = l.Bind(creds.Username, creds.Password)
    if err != nil {
        http.Error(w, "Invalid credentials", http.StatusUnauthorized)
        return
    }
    
    // 返回认证成功的token
    json.NewEncoder(w).Encode(map[string]string{
        "token": "generated-jwt-token",
        "user":  creds.Username,
    })
}

func main() {
    server, err := NewAPIServer()
    if err != nil {
        log.Fatal(err)
    }
    
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", server.router))
}

4. 数据库模型(使用GORM)

// models.go
package main

import (
    "time"
    
    "gorm.io/gorm"
)

type Preset struct {
    gorm.Model
    Name        string    `gorm:"size:255;not null" json:"name"`
    Description string    `gorm:"size:500" json:"description"`
    UserID      string    `gorm:"size:100;not null" json:"user_id"`
    LayoutData  string    `gorm:"type:text;not null" json:"layout_data"`
    IsPublic    bool      `gorm:"default:false" json:"is_public"`
    LastUsed    time.Time `json:"last_used"`
}

type User struct {
    gorm.Model
    Username    string    `gorm:"size:100;unique;not null" json:"username"`
    Email       string    `gorm:"size:255;unique;not null" json:"email"`
    LDAPDN      string    `gorm:"size:500" json:"ldap_dn"`
    IsActive    bool      `gorm:"default:true" json:"is_active"`
    LastLogin   time.Time `json:"last_login"`
    Presets     []Preset  `gorm:"foreignKey:UserID" json:"presets"`
}

5. WebSocket实时控制

// websocket_handler.go
package main

import (
    "log"
    "net/http"
    
    "github.com/gorilla/websocket"
)

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

type WebSocketHandler struct {
    clients    map[*websocket.Conn]bool
    broadcast  chan []byte
    register   chan *websocket.Conn
    unregister chan *websocket.Conn
}

func NewWebSocketHandler() *WebSocketHandler {
    return &WebSocketHandler{
        clients:    make(map[*websocket.Conn]bool),
        broadcast:  make(chan []byte),
        register:   make(chan *websocket.Conn),
        unregister: make(chan *websocket.Conn),
    }
}

func (h *WebSocketHandler) Run() {
    for {
        select {
        case client := <-h.register:
            h.clients[client] = true
        case client := <-h.unregister:
            if _, ok := h.clients[client]; ok {
                delete(h.clients, client)
                client.Close()
            }
        case message := <-h.broadcast:
            for client := range h.clients {
                err := client.WriteMessage(websocket.TextMessage, message)
                if err != nil {
                    client.Close()
                    delete(h.clients, client)
                }
            }
        }
    }
}

func (h *WebSocketHandler) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }
    
    h.register <- conn
    
    defer func() {
        h.unregister <- conn
    }()
    
    for {
        _, message, err := conn.ReadMessage()
        if err != nil {
            break
        }
        
        // 处理窗口控制消息
        h.broadcast <- message
    }
}

这个实现提供了完整的窗口管理后端,包括:

  1. Windows原生API调用进行窗口控制
  2. REST API供Web前端调用
  3. 多用户支持和LDAP集成
  4. 布局预设保存和快速调用
  5. WebSocket实时控制支持
  6. 数据库持久化存储

前端可以使用React/Vue/Angular通过API与这个Go后端通信,实现视频中展示的窗口管理功能。

回到顶部