资深Golang全栈开发工程师(远程职位)
资深Golang全栈开发工程师(远程职位) 大家好,
你们好吗?希望你和你的家人都平安健康。 我正在寻找一位能够开发以下应用程序的开发者。
这是一个运行在 Windows 10 上的客户端-服务器-API 应用程序,能够自定义安装在 Windows 10 上的应用程序(.exe)在多个显示器上的打开/关闭位置(像素)和大小。 例如,4个 Firefox 实例、3个 Chrome 实例、VLC、Vscode、Photoshop 等等…… 类似于一个窗口管理器。 客户端可以使用 HTML5/CSS/JS/React/Angular/Vue。
最终产品的示例如下。
- https://www.dropbox.com/home?preview=DSC_0585.MOV
- https://www.dropbox.com/s/ts94dly4k6fl9b6/NACCHY_VIDEO.mp4?dl=0
该应用程序需要以客户端-服务器-API 的方式工作,WEB JS 客户端通过图形界面控制应用程序(.exe)如何在服务器端(另一台连接显示器的 Windows 10 计算机)打开。 支持多用户,并能集成 LDAP 进行注册。 将预设保存到数据库。每个预设类似于视频中展示的内容。
- 多个不同的客户端可以在服务器机器上编程布局和更改布局,控制程序的显示/行为方式。
- 客户端可以创建多个不同的布局,保存它们,并能像视频中演示的那样快速调用。https://www.dropbox.com/home?preview=DSC_0585.MOV
谢谢。
更多关于资深Golang全栈开发工程师(远程职位)的实战教程也可以访问 https://www.itying.com/category-94-b0.html
每个链接都要求我登录 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
}
}
这个实现提供了完整的窗口管理后端,包括:
- Windows原生API调用进行窗口控制
- REST API供Web前端调用
- 多用户支持和LDAP集成
- 布局预设保存和快速调用
- WebSocket实时控制支持
- 数据库持久化存储
前端可以使用React/Vue/Angular通过API与这个Go后端通信,实现视频中展示的窗口管理功能。


