返回提交历史
Modified
README.md
+5
-3
Modified
artifacts/SHA256.json
+6
-6
Added
artifacts/verification/android-home.png
+0
-0
Added
artifacts/verification/android-purchased-stock.png
+0
-0
Added
artifacts/verification/android-recycled-stock.png
+0
-0
Modified
artifacts/verification/api-checks.txt
+1
-1
Modified
artifacts/verification/apk-signature.txt
+9
-0
Modified
artifacts/verification/quote-live.json
+13
-13
Modified
docs/服务器部署.md
+2
-2
Added
docs/本地联调测试.md
+41
-0
Modified
docs/验收步骤.md
+2
-2
Modified
src/JiDangJia.Server/ServerStore.cs
+1
-1
Modified
src/JiDangJia/JiDangJia.csproj
+1
-0
Modified
src/JiDangJia/MainPage.xaml.cs
+3
-2
Modified
src/JiDangJia/Platforms/Android/MainActivity.cs
+22
-1
Modified
src/JiDangJia/Services/AuthClient.cs
+3
-8
Added
src/JiDangJia/Services/ServerSettings.cs
+7
-0
Modified
src/JiDangJia/Views/AccountPages.cs
+2
-2
Modified
src/JiDangJia/Views/Ui.cs
+2
-1
Added
tools/android-ui.cjs
+35
-0
Modified
tools/package-delivery.py
+1
-1
XFEstudio/STHApplication
fix: lock server endpoint and verify Pixel 7 local integration
a93cf09
代码差异
21 个文件
+156
-43
@@ -16,7 +16,7 @@ Android .NET MAUI 应用与独立 ASP.NET Core 登录授权服务器。管理员
16
16
.\tools\start-server.ps1
17
17
```
18
18
19
APP 登录页先「配置服务器地址」,再使用管理员账户登录。Android 模拟器使用 `http://10.0.2.2:5188`。USB 真机连接这台电脑时,可运行 Android SDK 的 `adb reverse tcp:5188 tcp:5188`,APP 填 `http://127.0.0.1:5188`。公网使用部署后的 HTTPS 地址;本项目没有向公网部署服务器。
19
服务器地址硬编码在 `src/JiDangJia/Services/ServerSettings.cs` 的 `BaseUrl` 常量中,APP 无修改入口,也不读取用户地址设置。当前本地测试构建固定为 `http://10.0.2.2:5188`,Android 模拟器直接连接这台电脑的服务器。部署到其他地址时只能修改该常量后重新编译;USB 真机调试可改为 `http://127.0.0.1:5188` 并使用 `adb reverse tcp:5188 tcp:5188`,正式发布改为部署后的 HTTPS 地址。本项目没有向公网部署服务器。
20
20
21
21
## APP 功能
22
22
@@ -48,7 +48,7 @@ APP 登录页先「配置服务器地址」,再使用管理员账户登录。A
48
48
49
49
```powershell
50
50
dotnet workload install maui-android
51
dotnet run --project tests/JiDangJia.Checks
51
dotnet run --project tests/JiDangJia.Checks -c Release
52
52
node tools/check-api.cjs
53
53
node tools/check-quotes.cjs
54
54
.\tools\publish-android.ps1
@@ -57,4 +57,6 @@ dotnet publish src/JiDangJia.Server -c Release -r win-x64 --self-contained true
57
57
58
58
`check-api.cjs` 使用独立测试服务器及数据库。`check-quotes.cjs` 使用真实行情连接,需要联网。APK 构建脚本首次生成专用签名密钥与密码文件,存放在 `artifacts/credentials/`,不包含在源码包中。升级同一安装应用时请继续使用该密钥。[微软 Android 发布说明](https://learn.microsoft.com/en-us/dotnet/maui/android/deployment/publish-cli?view=net-maui-10.0)说明了签名参数。
59
59
60
已通过 75 项业务、备份和授权检查,以及 24 项实际 HTTP 权限检查;已收到黄金、白银、铂金真实报价。当前电脑未启用安卓模拟器所需的硬件加速,模拟器启动未成功;尚未完成真机界面、拍照和打印验收。具体验收步骤在 `docs/验收步骤.md`。
60
已通过 75 项业务、备份和授权检查,以及 24 项实际 HTTP 权限检查。签名 Release APK 已在 Pixel 7 / Android 16 / API 36 模拟器连接本机 5188 服务器完成登录、卡密、管理员权限、行情、导航、回收及铺货入库、财务流水联调,记录与截图见 [本地联调测试](docs/本地联调测试.md)。拍照、实际打印和其他真机能力仍需按 [设备验收步骤](docs/验收步骤.md) 验收。
61
62
本项目关闭 `AndroidEnableMarshalMethods`,修复当前 Android 工具链在 Release 启动时出现的 `MauiApplication.n_onCreate` 链接错误,参见 [dotnet/maui #35209](https://github.com/dotnet/maui/issues/35209)。若从旧构建切换此属性,先执行 `dotnet clean src/JiDangJia/JiDangJia.csproj -c Release -f net10.0-android` 再发布。
@@ -1,17 +1,17 @@
1
1
[
2
2
{
3
3
"name": "寄当家-2.0.0.apk",
4
"bytes": 66132645,
5
"sha256": "2dec7059fc5b3a440ed503e867566df2afdb33d584a3bf54215413bd491a29e7"
4
"bytes": 65806091,
5
"sha256": "cd35b85e97902824d80220f6b7225b2d6a4cf8d5a4ca35b01b34fa0f87874b53"
6
6
},
7
7
{
8
8
"name": "寄当家-服务器-Windows.zip",
9
"bytes": 49591550,
10
"sha256": "3a92aa984d8666731f5dc1a698349489c17af892400be82fd9992ee8064031a0"
9
"bytes": 49592690,
10
"sha256": "072938181be14ea97ff0912f6de615bbaf2aa3c1ecdbf83cd194ca94905bdfbb"
11
11
},
12
12
{
13
13
"name": "寄当家-源码.zip",
14
"bytes": 345999,
15
"sha256": "09d09bb17c06423b9dfb70542744d460301cca11bf4f5efcbe9bf25a25524db0"
14
"bytes": 937222,
15
"sha256": "800e3c8aeb7d991a9ee57c58734503234bfc7e27c5b1062871c53a8ce091367c"
16
16
}
17
17
]
二进制文件已变更,无法进行逐行预览。
二进制文件已变更,无法进行逐行预览。
二进制文件已变更,无法进行逐行预览。
@@ -1 +1 @@
1
PASS: 24 HTTP checks at 2026-10-01T14:36:51.702Z
1
PASS: 24 HTTP checks at 2026-10-01T15:24:13.659Z
@@ -4,5 +4,14 @@ Verified using v2 scheme (APK Signature Scheme v2): true
4
4
Verified using v3 scheme (APK Signature Scheme v3): true
5
5
Verified using v3.1 scheme (APK Signature Scheme v3.1): false
6
6
Verified using v4 scheme (APK Signature Scheme v4): false
7
Verified for SourceStamp: false
7
8
Number of signers: 1
9
Signer #1 certificate DN: CN=JiDangJia, OU=Mobile, O=XFEstudio, C=CN
8
10
Signer #1 certificate SHA-256 digest: 4429050070de338a9e51ff19ca828b743e075fbee9b461fed4d8080ec17fcac4
11
Signer #1 certificate SHA-1 digest: 52d38eccb4807a3cd6aa09d60593da7ecdfb469d
12
Signer #1 certificate MD5 digest: 3f3b97f4a3f5d7d1eddd643c277f0e49
13
Signer #1 key algorithm: RSA
14
Signer #1 key size (bits): 3072
15
Signer #1 public key SHA-256 digest: 818bbf0103eeaf1440e7c19ad2a69c97aa1113a8803a13a59192943f27b98a80
16
Signer #1 public key SHA-1 digest: 86b17d22791755bc1391da8a3a1733b9bfc627fe
17
Signer #1 public key MD5 digest: b7949d2477c55a2177806aedb746ceb8
@@ -1,5 +1,5 @@
1
1
{
2
"checkedAt": "2026-10-01T14:14:47.362Z",
2
"checkedAt": "2026-10-01T15:29:58.215Z",
3
3
"rows": [
4
4
{
5
5
"key": "gold",
@@ -7,10 +7,10 @@
7
7
"unit": "元/克",
8
8
"digits": 2,
9
9
"code": "JZJ_au_PS",
10
"bid": 896.79,
11
"ask": 899.79,
12
"updown": 0.44,
13
"rate": 0.048,
10
"bid": 899.54,
11
"ask": 902.54,
12
"updown": 3.19,
13
"rate": 0.354,
14
14
"preClose": 899.35
15
15
},
16
16
{
@@ -19,10 +19,10 @@
19
19
"unit": "元/克",
20
20
"digits": 3,
21
21
"code": "JZJ_ag_PS",
22
"bid": 13.656,
23
"ask": 13.756,
24
"updown": 0.001,
25
"rate": 0.007,
22
"bid": 13.631,
23
"ask": 13.731,
24
"updown": -0.024,
25
"rate": -0.174,
26
26
"preClose": 13.755
27
27
},
28
28
{
@@ -31,10 +31,10 @@
31
31
"unit": "元/克",
32
32
"digits": 2,
33
33
"code": "JZJ_pt_PS",
34
"bid": 369.2,
35
"ask": 370.7,
36
"updown": 1,
37
"rate": 0.27,
34
"bid": 368.9,
35
"ask": 370.4,
36
"updown": 0.7,
37
"rate": 0.189,
38
38
"preClose": 369.7
39
39
}
40
40
]
@@ -4,7 +4,7 @@
4
4
5
5
## Windows 本机
6
6
7
解压服务器包,运行 `启动服务器.ps1`。默认端口 5188、回环监听。启动后打开 `data/initial-admin.txt`,在 APP 配置服务器并登录此管理员账户。服务器保持运行,管理员后台在 APP 中。
7
解压服务器包,运行 `启动服务器.ps1`。默认端口 5188、回环监听。启动后打开 `data/initial-admin.txt`,使用当前硬编码服务器地址的 APP 登录此管理员账户。模拟器构建使用 `http://10.0.2.2:5188`。服务器保持运行,管理员后台在 APP 中。
8
8
9
9
首次启动前可以通过环境变量 `JDJ_ADMIN_USERNAME`、`JDJ_ADMIN_PASSWORD` 指定初始管理员。仅在数据库中尚无管理员时生效,密码为 8–128 位。未指定时生成随机密码。
10
10
@@ -15,7 +15,7 @@ dotnet publish src/JiDangJia.Server -c Release -o server
15
15
dotnet server/JiDangJia.Server.dll --urls http://127.0.0.1:5188 --DataDirectory /var/lib/jidangjia
16
16
```
17
17
18
通过 Nginx 或其他代理配置已获有效证书的 HTTPS 域名,代理到 `127.0.0.1:5188`。APP 登录页填写该 HTTPS 根地址,例如 `https://api.example.com`。模板域名需替换成实际域名。登录和卡密接口使用 Bearer 令牌;管理接口只接受管理员会话,不需要对外提供网页后台。
18
通过 Nginx 或其他代理配置已获有效证书的 HTTPS 域名,代理到 `127.0.0.1:5188`。开发者将 `src/JiDangJia/Services/ServerSettings.cs` 的 `BaseUrl` 修改为实际 HTTPS 根地址后重新编译 APP。该地址无用户配置入口。登录和卡密接口使用 Bearer 令牌;管理接口只接受管理员会话,不需要对外提供网页后台。
19
19
20
20
将进程配置为 Windows 服务或 systemd 服务,数据目录授予运行账户读写权限。只部署一个写入 SQLite 的服务器实例;扩容为多实例时应迁移到共享数据库。
21
21
@@ -0,0 +1,41 @@
1
# Pixel 7 本地联调记录
2
3
2026-10-01,使用当前运行的 Pixel 7 模拟器(Android 16 / API 36,1080 × 2400)安装本项目签名的 Release APK,连接本机 ASP.NET Core 服务器。通过 ADB 实际操作 MAUI 原生界面,检查控件快照与截图。
4
5
APP 服务器地址固定为 `http://10.0.2.2:5188`,宿主机服务器监听 `http://localhost:5188`。地址唯一配置位置为 `src/JiDangJia/Services/ServerSettings.cs`;登录页、授权页及设置页均无修改入口,客户端不读取旧的 `server_url` 用户设置。地址变更需开发者修改代码并重新编译。
6
7
| 测试内容 | 实际结果 |
8
| --- | --- |
9
| Release 安装和启动 | 安装成功,进入原生登录/工作台界面;修复启动链接错误后可正常冷启动 |
10
| 管理员登录 | 使用本机服务器初始管理员登录;设置页显示「管理员工具 → 进入管理后台」 |
11
| APP 内卡密生成 | 管理员生成一张 1 天授权、7 天兑换有效期的测试卡密 |
12
| CSV 导出 | 打开 Android 系统分享界面,显示卡密 CSV 文件;取消分享,没有发送给外部联系人 |
13
| 普通账户注册 | 在 APP 注册测试账户,进入等待激活页 |
14
| 无效卡密 | 输入无效卡密,显示格式错误,不能进入业务工作台 |
15
| 正常兑换 | 使用管理员生成的卡密激活成功,进入工作台 |
16
| 重复兑换 | 同一账户及设备重复兑换同一卡密,有效期不重复累加 |
17
| 管理员入口隔离 | 普通账户设置页没有「管理员工具」或「进入管理后台」;服务端角色限制另通过 HTTP 测试 |
18
| 首页统计 | 一行横向滑动,顺序为当月利润、库存金额、活当总额、客户总数、今日新开 |
19
| 广告和行情 | 广告自动切换;原生界面持续显示黄金、白银、铂金真实报价,页面切换后行情继续更新;无源报价的黄金 SGE 显示「—」 |
20
| 底部导航 | 点击图标可进入业务、库存、财务;从其他模块返回业务默认回收 |
21
| 不建客户直接回收 | 留空客户档案与姓名,录入测试物品并支付 ¥100,保存成功 |
22
| 新货铺货入库 | 新建供货入库,测试物品成本 ¥200,保存成功 |
23
| 两类库存隔离 | 回收库存仅显示二手物品、成本 ¥100;铺货库存仅显示新货、成本 ¥200 |
24
| 财务与首页汇总 | 流水分别显示回收支出 −¥100、铺货支出 −¥200;首页库存金额为 ¥300 |
25
26
测试过程中修复了 Release 启动错误、行情控件随页面重绘重建、底部绘制图标阻挡点击及 Android 16 状态栏文字对比不足的问题。测试账户及本地测试数据保留用于复查。原始控件快照、账号凭证与完整测试卡密保存在忽略目录 `artifacts/test-data/android-ui`,不包含在仓库和交付包中。
27
28
可公开的测试截图:
29
30
- [工作台与实时行情](../artifacts/verification/android-home.png)
31
- [回收库存](../artifacts/verification/android-recycled-stock.png)
32
- [铺货库存](../artifacts/verification/android-purchased-stock.png)
33
34
自动检查记录:
35
36
- [75 项业务、备份和授权检查](../artifacts/verification/business-checks.txt)
37
- [24 项实际 HTTP 权限检查](../artifacts/verification/api-checks.txt)
38
- [真实行情连接记录](../artifacts/verification/quote-live.json)
39
- [APK 签名验证](../artifacts/verification/apk-signature.txt)
40
41
本次模拟器联调未验证实体相机、实际打印机、24 小时离线等待及完整设备兼容性。继续验收步骤见 [设备验收](验收步骤.md)。
@@ -1,8 +1,8 @@
1
1
# 寄当家设备验收
2
2
3
业务、授权和 HTTP 接口已自动验证。以下步骤用于真机验收 MAUI 界面、相机、文件选取、后台恢复、系统分享和打印。
3
业务、授权和 HTTP 接口已自动验证,Pixel 7 / API 36 已完成本地联调,结果见 [本地联调测试](本地联调测试.md)。以下完整步骤用于继续真机验收 MAUI 界面、相机、文件选取、后台恢复、系统分享和打印。
4
4
5
1. 启动服务器。通过 USB 使用 `adb reverse tcp:5188 tcp:5188`,或部署 HTTPS 服务。安装 APK,配置服务器地址。
5
1. 启动本地服务器,监听 5188。Pixel 7 模拟器使用当前 APK 中固定的 `http://10.0.2.2:5188`,安装后直接登录;确认登录及设置页面没有地址修改入口。其他设备或部署地址由开发者修改 `ServerSettings.BaseUrl` 后重新编译。
6
6
2. 用 `data/initial-admin.txt` 中的账户登录,在设置中确认出现管理员入口。生成两张 30 天卡密,导出 CSV 并复制其中一张。
7
7
3. 退出管理员,注册普通账户。确认普通账户看不到管理员入口。兑换第一张卡密,确认授权有效,再输入同一卡密,确认有效期未重复累加。
8
8
4. 在首页确认配色、五项数据顺序、横向滑动、广告轮播、实时贵金属价格、客户管理按钮和经营图表;在设置中为广告添加图片,回首页查看。
@@ -28,7 +28,7 @@ public sealed class ServerStore
28
28
var password=Environment.GetEnvironmentVariable("JDJ_ADMIN_PASSWORD")??"Jdj!"+Convert.ToHexString(RandomNumberGenerator.GetBytes(10));
29
29
ValidatePassword(password);
30
30
Exec(c,"INSERT INTO Accounts(id,username,name,passwordHash,role,createdAt) VALUES(@p0,@p1,@p2,@p3,'admin',@p4)",Guid.NewGuid().ToString("N"),username,"系统管理员",PasswordHash(password),Now);
31
File.WriteAllText(Path.Combine(dataDirectory,"initial-admin.txt"),$"寄当家管理员初始账户\n账号:{username}\n密码:{password}\n在 APP 登录页配置此服务器地址,使用以上账户登录,然后进入:设置 → 管理员工具 → 进入管理后台。\n首次登录后请修改密码。\n",Encoding.UTF8);
31
File.WriteAllText(Path.Combine(dataDirectory,"initial-admin.txt"),$"寄当家管理员初始账户\n账号:{username}\n密码:{password}\n服务器地址固定在 APP 源码中。使用以上账户登录,然后进入:设置 → 管理员工具 → 进入管理后台。\n首次登录后请修改密码。\n",Encoding.UTF8);
32
32
}
33
33
}
34
34
public static string Now=>DateTime.UtcNow.ToString("O");
@@ -14,6 +14,7 @@
14
14
<SupportedOSPlatformVersion>23.0</SupportedOSPlatformVersion>
15
15
<AndroidPackageFormats>apk</AndroidPackageFormats>
16
16
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
17
<AndroidEnableMarshalMethods>false</AndroidEnableMarshalMethods>
17
18
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
18
19
<PublishTrimmed>false</PublishTrimmed>
19
20
<RunAOTCompilation>false</RunAOTCompilation>
@@ -61,11 +61,12 @@ public partial class MainPage : ContentPage
61
61
private Task NewOrder() => businessTab==4?Switch(2):Push(FormPage.Order(service,businessTab switch {0=>"RECYCLE",1=>"PAWN",2=>"CONSIGN",3=>"RENT",_=>"OTHER"}));
62
62
private void Render()
63
63
{
64
Root.Children.Clear();quoteLabels.Clear();ads=null;quoteFoot=null;
64
foreach(var child in Root.Children.Where(child=>!ReferenceEquals(child,feed)).ToList())Root.Children.Remove(child);
65
quoteLabels.Clear();ads=null;quoteFoot=null;
65
66
View body=tab switch {0=>Dashboard(),1=>Business(),2=>Stock(),3=>Finance(),_=>Community()};
66
67
var refresh=new RefreshView { Content=new ScrollView { Content=body },RefreshColor=Ui.Bronze };
67
68
refresh.Refreshing+=(_,_)=> {Render();feed.SendRawMessage("refresh");refresh.IsRefreshing=false;};
68
Root.Add(refresh); Root.Add(NavigationBar(),0,1); Root.Add(feed);
69
Root.Add(refresh); Root.Add(NavigationBar(),0,1); if(!Root.Children.Contains(feed))Root.Add(feed);
69
70
}
70
71
private View NavigationBar()
71
72
{
@@ -18,7 +18,28 @@ public class MainActivity : MauiAppCompatActivity
18
18
if(Window is { } window)
19
19
{
20
20
var insets=AndroidX.Core.View.WindowCompat.GetInsetsController(window,window.DecorView);
21
if(insets is not null){insets.AppearanceLightStatusBars=OperatingSystem.IsAndroidVersionAtLeast(35);insets.AppearanceLightNavigationBars=true;}
21
if(insets is not null){insets.AppearanceLightStatusBars=false;insets.AppearanceLightNavigationBars=true;}
22
if(OperatingSystem.IsAndroidVersionAtLeast(35)&&window.DecorView is Android.Views.ViewGroup decor)
23
{
24
// Android 15+ 状态栏透明,直接在系统栏区域绘制暖黑背景,保持白色图标清晰。
25
var protection=new Android.Views.View(this){Clickable=false,Focusable=false,ImportantForAccessibility=Android.Views.ImportantForAccessibility.No};
26
protection.SetBackgroundColor(Android.Graphics.Color.ParseColor("#2B211A"));
27
decor.AddView(protection,new Android.Widget.FrameLayout.LayoutParams(Android.Views.ViewGroup.LayoutParams.MatchParent,0,Android.Views.GravityFlags.Top));
28
AndroidX.Core.View.ViewCompat.SetOnApplyWindowInsetsListener(protection,new StatusBarInsets());
29
protection.Post(()=>AndroidX.Core.View.ViewCompat.RequestApplyInsets(protection));
30
}
31
}
32
}
33
34
private sealed class StatusBarInsets : Java.Lang.Object, AndroidX.Core.View.IOnApplyWindowInsetsListener
35
{
36
public AndroidX.Core.View.WindowInsetsCompat? OnApplyWindowInsets(Android.Views.View? view,AndroidX.Core.View.WindowInsetsCompat? insets)
37
{
38
if(view is null||insets is null)return insets;
39
var height=insets.GetInsets(AndroidX.Core.View.WindowInsetsCompat.Type.StatusBars()|AndroidX.Core.View.WindowInsetsCompat.Type.DisplayCutout())?.Top??0;
40
if(view.LayoutParameters is { } layout&&layout.Height!=height){layout.Height=height;view.LayoutParameters=layout;}
41
view.BringToFront();
42
return insets;
22
43
}
23
44
}
24
45
}