init: 初始框架

This commit is contained in:
Shiqvlizi Name
2026-04-11 22:36:47 +08:00
parent de69f988a4
commit e7a6dc8b33
6 changed files with 1063 additions and 0 deletions

428
.gitignore vendored Normal file
View File

@@ -0,0 +1,428 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.env
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Dd]ebug/x64/
[Dd]ebugPublic/x64/
[Rr]elease/x64/
[Rr]eleases/x64/
bin/x64/
obj/x64/
[Dd]ebug/x86/
[Dd]ebugPublic/x86/
[Rr]elease/x86/
[Rr]eleases/x86/
bin/x86/
obj/x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
**/.paket/paket.exe
paket-files/
# FAKE - F# Make
**/.fake/
# CodeRush personal settings
**/.cr/personal
# Python Tools for Visual Studio (PTVS)
**/__pycache__/
*.pyc
# Cake - Uncomment if you are using it
#tools/**
#!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
**/.mfractor/
# Local History for Visual Studio
**/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
**/.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp

36
Program.cs Normal file
View File

@@ -0,0 +1,36 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using cloudPrintWinAgent;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "CloudPrint WinAgent";
});
builder.Services.AddHostedService<Worker>();
builder.Logging.ClearProviders();
if (Environment.UserInteractive)
{
// 交互式运行
builder.Logging.AddConsole();
}
else
{
// 作为 Windows 服务运行
builder.Logging.AddEventLog(eventLogSettings =>
{
eventLogSettings.SourceName = "CloudPrint WinAgent";
eventLogSettings.LogName = "CloudPrint";
});
}
var host = builder.Build();
await host.RunAsync();

32
Worker.cs Normal file
View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace cloudPrintWinAgent
{
public sealed class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("WinAgent started at: {Time}", DateTimeOffset.Now);
while (!stoppingToken.IsCancellationRequested)
{
// TODO: 后续放心跳、订阅任务、状态回传
_logger.LogInformation("WinAgent heartbeat tick at: {Time}", DateTimeOffset.Now);
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
_logger.LogInformation("WinAgent stopping at: {Time}", DateTimeOffset.Now);
}
}
}

21
cloudPrintWinAgent.csproj Normal file
View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.34.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.66.0" />
<PackageReference Include="Grpc.Tools" Version="2.66.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.1" />
</ItemGroup>
<ItemGroup>
<Protobuf Include=".\proto\cloudprint.proto" GrpcServices="Client" />
</ItemGroup>
</Project>

3
cloudPrintWinAgent.slnx Normal file
View File

@@ -0,0 +1,3 @@
<Solution>
<Project Path="cloudPrintWinAgent.csproj" />
</Solution>

543
proto/cloudprint.proto Normal file
View File

@@ -0,0 +1,543 @@
syntax = "proto3";
package cloudprint;
option go_package = "github.com/cloudprint/cloudprint-backend/proto/generated";
import "google/protobuf/empty.proto";
import "google/protobuf/timestamp.proto";
// ============ 通用消息 ============
message Empty {}
message StatusResponse {
int32 code = 1;
string message = 2;
}
// ============ 用户相关 ============
message User {
int32 id = 1;
string openid = 2;
string nickname = 3;
string avatar = 4;
double balance = 5;
google.protobuf.Timestamp created_at = 6;
}
message LoginRequest {
string code = 1; // 微信授权码
}
message LoginResponse {
string access_token = 1;
string refresh_token = 2;
User user = 3;
}
message RefreshRequest {
string refresh_token = 1;
}
message RefreshResponse {
string access_token = 1;
}
message GetUserInfoRequest {}
message UpdateUserRequest {
string nickname = 1;
string avatar = 2;
}
// ============ 文件相关 ============
message FileInfo {
int32 id = 1;
string file_name = 2;
string file_path = 3;
string file_type = 4;
int64 file_size = 5;
string uploader_type = 6;
int32 uploader_id = 7;
google.protobuf.Timestamp created_at = 8;
}
message UploadRequest {
string file_name = 1;
string file_type = 2;
int64 file_size = 3;
bytes data = 4;
bool is_last_chunk = 5;
string chunk_id = 6;
int32 chunk_index = 7;
int32 total_chunks = 8;
}
message UploadResponse {
int32 file_id = 1;
string file_path = 2;
string file_url = 3;
}
message GetFileRequest {
int32 file_id = 1;
}
message FileData {
bytes data = 1;
bool is_last = 2;
}
message DeleteFileRequest {
int32 file_id = 1;
}
message WatermarkRequest {
int32 file_id = 1;
string text = 2;
float opacity = 3;
int32 position = 4; // 1: 左上 2: 右上 3: 左下 4: 右下 5: 居中
int32 font_size = 5;
}
message WatermarkResponse {
int32 file_id = 1;
string file_path = 2;
}
message GetFileListRequest {
string uploader_type = 1;
int32 uploader_id = 2;
int32 page = 3;
int32 page_size = 4;
}
message FileListResponse {
repeated FileInfo files = 1;
int32 total = 2;
}
// ============ 订单相关 ============
message Order {
int32 id = 1;
string order_no = 2;
int32 user_id = 3;
int32 status = 4;
double total_amount = 5;
string remark = 6;
google.protobuf.Timestamp created_at = 7;
google.protobuf.Timestamp paid_at = 8;
repeated OrderItem items = 9;
}
message OrderItem {
int32 id = 1;
int32 order_id = 2;
int32 file_id = 3;
FileInfo file = 4;
int32 printer_id = 5;
int32 copies = 6;
bool color = 7;
bool duplex = 8;
string page_range = 9;
double price = 10;
}
message CreateOrderRequest {
repeated CreateOrderItem items = 1;
string remark = 2;
}
message CreateOrderItem {
int32 file_id = 1;
int32 printer_id = 2;
int32 copies = 3;
bool color = 4;
bool duplex = 5;
string page_range = 6;
}
message CreateOrderResponse {
string order_no = 1;
double total_amount = 2;
}
message GetOrderRequest {
string order_no = 1;
}
message GetOrderListRequest {
int32 user_id = 1;
int32 status = 2;
int32 page = 3;
int32 page_size = 4;
}
message OrderListResponse {
repeated Order orders = 1;
int32 total = 2;
}
message UpdateOrderStatusRequest {
string order_no = 1;
int32 status = 2;
}
message CancelOrderRequest {
string order_no = 1;
}
// ============ 打印机相关 ============
message Printer {
int32 id = 1;
string name = 2;
string display_name = 3;
bool is_active = 4;
int32 status = 5; // 0: 空闲 1: 打印中 2: 离线
google.protobuf.Timestamp last_heartbeat = 6;
}
message GetPrintersRequest {}
message PrinterListResponse {
repeated Printer printers = 1;
}
message GetPrinterStatusRequest {
int32 printer_id = 1;
}
message PrinterStatusResponse {
Printer printer = 1;
int32 queue_count = 2;
}
message RegisterPrinterRequest {
string name = 1;
string display_name = 2;
string api_key = 3;
}
message RegisterPrinterResponse {
int32 printer_id = 1;
string token = 2;
}
message PrinterHeartbeatRequest {
int32 printer_id = 1;
int32 status = 2;
string token = 3;
}
// ============ 打印任务相关 ============
message PrintJob {
int32 id = 1;
string job_no = 2;
int32 printer_id = 3;
int32 order_item_id = 4;
string status = 5; // pending/printing/completed/failed/cancelled
int32 progress = 6;
string error_msg = 7;
google.protobuf.Timestamp started_at = 8;
google.protobuf.Timestamp completed_at = 9;
OrderItem order_item = 10;
}
message SubmitPrintJobRequest {
int32 order_item_id = 1;
int32 printer_id = 2;
string file_path = 3;
int32 copies = 4;
bool color = 5;
bool duplex = 6;
string page_range = 7;
}
message SubmitPrintJobResponse {
string job_no = 1;
}
message CancelPrintJobRequest {
string job_no = 1;
}
message SubscribeJobsRequest {
int32 printer_id = 1;
string token = 2;
}
message ReportJobStatusRequest {
string job_no = 1;
string status = 2;
int32 progress = 3;
string error_msg = 4;
}
// ============ 文库相关 ============
message LibraryCategory {
int32 id = 1;
string name = 2;
string icon = 3;
int32 sort_order = 4;
}
message LibraryFile {
int32 id = 1;
int32 category_id = 2;
int32 file_id = 3;
string title = 4;
string description = 5;
int32 page_count = 6;
int32 download_count = 7;
FileInfo file = 8;
google.protobuf.Timestamp created_at = 9;
}
message GetCategoriesRequest {}
message CategoryListResponse {
repeated LibraryCategory categories = 1;
}
message GetLibraryFilesRequest {
int32 category_id = 1;
int32 page = 2;
int32 page_size = 3;
}
message LibraryFileListResponse {
repeated LibraryFile files = 1;
int32 total = 2;
}
message UploadLibraryFileRequest {
int32 category_id = 1;
string title = 2;
string description = 3;
bytes data = 4;
string file_name = 5;
string file_type = 6;
}
message UploadLibraryFileResponse {
int32 library_file_id = 1;
}
message DeleteLibraryFileRequest {
int32 library_file_id = 1;
}
// ============ 价目表相关 ============
message PriceItem {
int32 id = 1;
string name = 2;
string type = 3;
double price = 4;
string unit = 5;
bool color_enabled = 6;
bool is_active = 7;
}
message GetPriceListRequest {}
message PriceListResponse {
repeated PriceItem items = 1;
}
message CreatePriceItemRequest {
string name = 1;
string type = 2;
double price = 3;
string unit = 4;
bool color_enabled = 5;
}
message UpdatePriceItemRequest {
int32 id = 1;
string name = 2;
string type = 3;
double price = 4;
string unit = 5;
bool color_enabled = 6;
bool is_active = 7;
}
// ============ 支付相关 ============
message WxPayRequest {
string order_no = 1;
double amount = 2;
string description = 3;
string openid = 4;
string client_ip = 5;
}
message WxPayResponse {
string prepay_id = 1;
string code_url = 2;
string mweb_url = 3;
}
message QueryPayStatusRequest {
string order_no = 1;
}
message PayStatusResponse {
int32 status = 1; // 0: 待支付 1: 支付中 2: 成功 3: 失败
string transaction_id = 2;
}
message PayCallbackRequest {
string body = 1;
}
message PayCallbackResponse {
string return_code = 1;
string return_msg = 2;
}
message PayByBalanceRequest {
string order_no = 1;
int32 user_id = 2;
}
// ============ 管理后台相关 ============
message Admin {
int32 id = 1;
string username = 2;
string nickname = 3;
int32 role = 4;
google.protobuf.Timestamp created_at = 5;
}
message AdminLoginRequest {
string username = 1;
string password = 2;
}
message AdminLoginResponse {
string access_token = 1;
Admin admin = 2;
}
message GetStatisticsRequest {}
message Statistics {
int32 total_users = 1;
int32 total_orders = 2;
int32 today_orders = 3;
double today_revenue = 4;
int32 pending_orders = 5;
int32 active_printers = 6;
}
// 创建管理员请求
message CreateAdminRequest {
string username = 1;
string password = 2;
string nickname = 3;
int32 role = 4; // 1:普通管理员 2:超级管理员
}
// 创建管理员响应
message CreateAdminResponse {
Admin admin = 1;
}
// 获取管理员列表请求
message GetAdminListRequest {
int32 page = 1;
int32 page_size = 2;
}
// 管理员列表响应
message AdminListResponse {
repeated Admin admins = 1;
int32 total = 2;
}
// 更新管理员请求
message UpdateAdminRequest {
int32 id = 1;
string nickname = 2;
int32 role = 3;
}
// 删除管理员请求
message DeleteAdminRequest {
int32 id = 1;
}
// ============ 服务定义 ============
service AuthService {
rpc Login(LoginRequest) returns (LoginResponse);
rpc RefreshToken(RefreshRequest) returns (RefreshResponse);
rpc GetUserInfo(GetUserInfoRequest) returns (User);
rpc UpdateUser(UpdateUserRequest) returns (User);
}
service FileService {
rpc Upload(stream UploadRequest) returns (UploadResponse);
rpc GetFile(GetFileRequest) returns (stream FileData);
rpc DeleteFile(DeleteFileRequest) returns (Empty);
rpc AddWatermark(WatermarkRequest) returns (WatermarkResponse);
rpc GetFileList(GetFileListRequest) returns (FileListResponse);
}
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc GetOrderList(GetOrderListRequest) returns (OrderListResponse);
rpc UpdateOrderStatus(UpdateOrderStatusRequest) returns (Empty);
rpc CancelOrder(CancelOrderRequest) returns (Empty);
}
service PrintService {
rpc GetPrinters(GetPrintersRequest) returns (PrinterListResponse);
rpc GetPrinterStatus(GetPrinterStatusRequest) returns (PrinterStatusResponse);
rpc RegisterPrinter(RegisterPrinterRequest) returns (RegisterPrinterResponse);
rpc PrinterHeartbeat(PrinterHeartbeatRequest) returns (Empty);
rpc SubmitPrintJob(SubmitPrintJobRequest) returns (SubmitPrintJobResponse);
rpc CancelPrintJob(CancelPrintJobRequest) returns (Empty);
rpc SubscribeJobs(SubscribeJobsRequest) returns (stream PrintJob);
rpc ReportJobStatus(ReportJobStatusRequest) returns (Empty);
}
service LibraryService {
rpc GetCategories(GetCategoriesRequest) returns (CategoryListResponse);
rpc GetFiles(GetLibraryFilesRequest) returns (LibraryFileListResponse);
rpc UploadFile(stream UploadLibraryFileRequest) returns (UploadLibraryFileResponse);
rpc DeleteFile(DeleteLibraryFileRequest) returns (Empty);
}
service PriceService {
rpc GetPriceList(GetPriceListRequest) returns (PriceListResponse);
rpc CreatePriceItem(CreatePriceItemRequest) returns (PriceItem);
rpc UpdatePriceItem(UpdatePriceItemRequest) returns (PriceItem);
}
service PayService {
rpc CreateWxPayOrder(WxPayRequest) returns (WxPayResponse);
rpc QueryPayStatus(QueryPayStatusRequest) returns (PayStatusResponse);
rpc HandlePayCallback(PayCallbackRequest) returns (PayCallbackResponse);
rpc PayByBalance(PayByBalanceRequest) returns (StatusResponse);
}
service AdminService {
rpc Login(AdminLoginRequest) returns (AdminLoginResponse);
rpc GetStatistics(GetStatisticsRequest) returns (Statistics);
rpc CreateAdmin(CreateAdminRequest) returns (CreateAdminResponse);
rpc GetAdminList(GetAdminListRequest) returns (AdminListResponse);
rpc UpdateAdmin(UpdateAdminRequest) returns (Admin);
rpc DeleteAdmin(DeleteAdminRequest) returns (Empty);
}