0%

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
一、鸿蒙的“官方推荐”架构

鸿蒙官方在 ArkUI/ArkTS 和 Java HarmonyOS Ability 开发里,推荐用:

1.MVVM(官方最推)

ArkUI/ArkTS 天然支持 数据驱动,也就是 MVVM 核心思想:

Model:数据实体,普通类或 JS 对象

ViewModel:@Observed, @State, @Computed 等管理状态

View:ArkUI Column, Row, Stack 等 UI 组件

数据变化 → UI 自动刷新(无需手动 findView/updateView)

典型标志:

@Observed
class DeviceData {
batteryPercent: number;
online: boolean;
}

@Component
struct DeviceItem {
@ObjectLink data: DeviceData;
build() {
Column() {
Text("电量: " + this.data.batteryPercent + "%")
}
}
}


这个就是 ArkUI 的 MVVM 核心方式。

2.MVP / MVI(非官方也可用)

MVP:

Model: 数据实体

View: ArkUI 页面

Presenter: 普通 TypeScript / Java 类负责业务逻辑

这种方式在鸿蒙 Java 端和 ArkUI 前端都可以实现,但不如 MVVM 官方支持好。

使用场景:你想完全分离逻辑和 UI,类似 Android 老项目迁移。

MVI:

也是数据驱动,但强调 状态不可变 + 单向数据流

对于复杂页面(设备列表、巡检表格、地图轨迹)非常适合

二、鸿蒙开发特点与 Android 不同点
# Android 与 鸿蒙 ArkUI/ArkTS 特性对比

| 特性 | Android | 鸿蒙 ArkUI / ArkTS |
|------|---------|------------------|
| **数据绑定** | LiveData / ViewModel | `@State`, `@Observed` |
| **UI 组件** | XML / Jetpack Compose | Column, Row, Stack, Text, Image |
| **事件绑定** | `setOnClickListener` | `onClick={() => {...}}` |
| **生命周期** | Activity / Fragment | Ability + Component |
| **官方推荐架构** | MVVM + Jetpack | MVVM + ArkUI |

总结:鸿蒙官方就是 ArkUI + MVVM,几乎天然支持,直接用 @Observed/@State 就能做双向绑定。

三、鸿蒙项目常用 MVVM 模式结构
project/

├─ model/ # 数据模型
│ └─ Device.ts

├─ view/ # 页面组件
│ └─ DevicePage.ts

├─ viewmodel/ # 管理状态、提供数据
│ └─ DeviceViewModel.ts

├─ service/ # 网络/设备/数据库服务
│ └─ DeviceService.ts
└─ utils/


使用方法:

ViewModel 中保存状态

View 使用 @Observed 或 @State 绑定 ViewModel 数据

数据变化 → UI 自动刷新,无需手动更新

四、安卓开发经验迁移到鸿蒙

LiveData → @Observed/@State

ViewModel → ArkTS/Java 对应 ViewModel 类

DataBinding / Jetpack Compose → Column/Row/Stack + build()

MVP 的 Presenter → ArkTS 类 + 自己调用更新函数

核心区别:鸿蒙更偏 声明式 + 数据驱动,MVVM 天然支持,MVP 只能自己写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
1.在module.json5中
"requestPermissions": [
{"name": "ohos.permission.INTERNET"},
{"name": "ohos.permission.GET_BUNDLE_INFO"},
{"name": "ohos.permission.KEEP_BACKGROUND_RUNNING"}
],

"pages": "$profile:main_pages",
"abilities": [
{
"name": "EntryAbility",
"backgroundModes": [
"dataTransfer" // 对应 BackgroundMode.DATA_TRANSFER
],



2.在EntryAblility中
async onForeground(): Promise<void> {
// Ability has brought to foreground
hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground');
try {
const wantAgentInfo: wantAgent.WantAgentInfo = {
wants: [{ bundleName: 'com.ht.cotton', abilityName: 'EntryAbility' }],
actionType: wantAgent.OperationType.START_ABILITY,
requestCode: 0,
actionFlags: [wantAgent.WantAgentFlags.UPDATE_PRESENT_FLAG]
};

const wantAgentObj = await wantAgent.getWantAgent(wantAgentInfo);
const taskTypes = ['dataTransfer']; // 或 ['audioPlayback'], ['audioRecording'] 等
const res = await backgroundTaskManager.startBackgroundRunning(this.context, backgroundTaskManager.BackgroundMode.DATA_TRANSFER, wantAgentObj);
// console.info('后台长时任务已申请, notificationId:', res.notificationId);
} catch (e) {
console.error('申请后台长时任务失败:', JSON.stringify(e));
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
"app": {
"bundleName": "com.ht.cotton",
"bundleType": "app",
"versionCode": 1000000,
"versionName": "1.0.0",
"label": "Htconnn",
"deployDomain": "codehelps.online",
"icons": {
"normal": "https://codehelps.online/logo.png",
"large": "https://codehelps.online/logo2.png"
},
"minAPIVersion": "5.0.5(17)",
"targetAPIVersion": "5.0.5(17)",
"modules": [
{
"name": "entry",
"type": "entry",
"deviceTypes": [
"tablet",
"2in1",
"phone"
],
"packageUrl": "https://codehelps.online/a.hap",
"packageHash": "56c1438593fc5f3d6e49196b2751a4bb99d3a83da744cbe8deecad27d07bda8e"
}
]
},
"sign": "描述文件签名"
}

1.打出包对应的packageHash
certutil -hashfile "D:\homyspace\htcotton\entry\build\default\outputs\default\entry-default-signed.hap" SHA256

2.描述文件签名
https://gitee.com/arkin-internal-testing/internal-testing
下载后运行manifest-sign.bat

-operation sign -mode localjks -inputFile D:\file\hmmswj\htcotton.json5 -outputFile D:\file\hmmswj\adhocnew.json5 -keystore D:\homyspace\htcotton\csr\htcotton.p12 -keystorepasswd htnova1003 -keyaliaspasswd htnova1003 -privatekey htcotto

3.生成的前json5文件上传到自己的服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
在 ArkTS(ArkUI)里,ForEach 的第三个参数是 key,用来标识每个列表项的唯一性。它有个“复用组件”的机制:

key 不变 → 组件复用,不会重新渲染

key 变化 → 组件重建,UI 才会刷新

ForEach(this.yardList, (item: DataBean, index: number) => {
ListItem() {
this.BuildYardItemPage(item, index)
}
}, (item: DataBean) => item.sn)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
先在oh-package.json5中引用库
"@ohos/crypto-js": "2.0.5"



在创建工具类,供外部调用

// CryptoUtils.ts
import { CryptoJS } from '@ohos/crypto-js';

export class DesEncryptorJS {

/**
* DES 加密(ECB + PKCS7)
* @param data 明文
* @param keyStr 密钥(会自动补齐 8 位)
* @returns Base64 加密结果
*/
static desEncrypt(data: string, keyStr: string): string {
// DES key 必须 8 字节
let key = keyStr.padEnd(8, '0');
let parsedKey = CryptoJS.enc.Utf8.parse(key);

let encrypted = CryptoJS.DES.encrypt(data, parsedKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});

return encrypted.toString();
}

/**
* DES 解密(ECB + PKCS7)
* @param encryptedData Base64 密文
* @param keyStr 密钥
* @returns 明文
*/
static desDecrypt(encryptedData: string, keyStr: string): string {
let key = keyStr.padEnd(8, '0');
let parsedKey = CryptoJS.enc.Utf8.parse(key);

let decrypted = CryptoJS.DES.decrypt(encryptedData, parsedKey, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});

return decrypted.toString(CryptoJS.enc.Utf8);
}

/**
* AES 加密(默认 ECB + PKCS7)
* @param data 明文
* @param keyStr 密钥(16/24/32 字节)
* @returns Base64 密文
*/
static aesEncrypt(data: string, keyStr: string): string {
let key = CryptoJS.enc.Utf8.parse(keyStr);
let encrypted = CryptoJS.AES.encrypt(data, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});

return encrypted.toString();
}

/**
* AES 解密
* @param encryptedData Base64 密文
* @param keyStr 密钥
* @returns 明文
*/
static aesDecrypt(encryptedData: string, keyStr: string): string {
let key = CryptoJS.enc.Utf8.parse(keyStr);
let decrypted = CryptoJS.AES.decrypt(encryptedData, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7,
});

return decrypted.toString(CryptoJS.enc.Utf8);
}

/**
* MD5 加密
* @param data 明文
* @returns MD5 十六进制字符串
*/
static md5(data: string): string {
return CryptoJS.MD5(data).toString(CryptoJS.enc.Hex);
}
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
Step 1️⃣ 安装 Certbot(CentOS / Rocky / Alma)
sudo yum install -y epel-release
sudo yum install -y certbot python3-certbot-nginx

Step 2️⃣ 一条命令申请 HTTPS(⭐ 核心)
sudo certbot --nginx -d example.com -d www.example.com

Step 3️⃣ 立刻生效(不用重启)

Certbot 会自动:

申请证书

配置 Nginx

打开 443

加 SSL 配置

现在直接访问:

https://example.com


🔒 浏览器出现小锁 = 成功



如果是自己解压安装的nginx,建立软连接,指向安装目录,
sudo ln -s /usr/local/nginx/sbin/nginx /usr/sbin/nginx
sudo certbot --nginx --nginx-server-root /usr/local/nginx/conf -d codehelps.online -d www.codehelps.online




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
repositories {
maven { url 'https://jitpack.io' }
}

dependencies {
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}

布局引用
<com.github.mikephil.charting.charts.CombinedChart
android:id="@+id/combinedChart"
android:layout_width="350dp"
android:layout_height="270dp"
android:layout_margin="20dp"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>


private lateinit var lineDataSet: LineDataSet//折线数据集

/** 初始化 CombinedChart **/
private fun setupChart() {
val chart = binding.combinedChart
// 折线数据集 参数设置
lineDataSet = LineDataSet(mutableListOf(Entry(0f, 0f)), "实时曲线").apply {
color = ColorTemplate.rgb("1E88E5")
lineWidth = 2f
setDrawCircles(false)
setDrawValues(false)
setDrawFilled(true) // 曲线下方填充
mode = LineDataSet.Mode.CUBIC_BEZIER // 平滑曲线 LINEAR
axisDependency = YAxis.AxisDependency.LEFT
}
lineDataSet.setDrawFilled(true)
lineDataSet.fillColor = Color.parseColor("#82F689") // 填充颜色
lineDataSet.fillAlpha = 80 // 透明度 0~255



// 曲面折线
redDataSet = LineDataSet(mutableListOf(), "GC 谱图").apply {
setDrawHorizontalHighlightIndicator(false)
setDrawVerticalHighlightIndicator(false)
lineWidth = 0f // 安全
color = Color.TRANSPARENT // 即使透明也不影响
mode = LineDataSet.Mode.LINEAR // 不要贝塞尔

setCircleColor(Color.RED) // 圆点颜色为红色
setDrawCircleHole(false) // 圆点不留白
setDrawCircles(true) // 不画圆点
setDrawFilled(false) // 曲线下方填充
fillColor = Color.TRANSPARENT
}


val lineData = LineData(redDataSet,lineDataSet)

// 柱状数据集(初始化为空)
barDataSet = BarDataSet(mutableListOf(), "目标气体峰").apply {
color = Color.RED
}
val barData = BarData(barDataSet).apply { barWidth = 0.8f }

// CombinedData
val combinedData = CombinedData().apply {
setData(lineData)
setData(barData)
}
chart.data = combinedData

// 限制最大缩放比例
chart.viewPortHandler.setMaximumScaleX(Float.MAX_VALUE) // X轴无限放大
chart.viewPortHandler.setMinimumScaleX(0f) // 无限缩小(到很小)
chart.viewPortHandler.setMaximumScaleY(Float.MAX_VALUE) // Y轴无限放大
chart.viewPortHandler.setMinimumScaleY(0f) // 无限缩小

// X轴设置
chart.xAxis.apply {
position = XAxis.XAxisPosition.BOTTOM
granularity = 1f
axisMinimum = 0f
}

// Y轴设置
// chart.axisLeft.apply {
// axisMinimum = 0f
// valueFormatter = object : ValueFormatter() {
// override fun getFormattedValue(value: Float): String = "${value.toInt()} mV"
// }
// setAxisMinValue(0f)//强制y轴最小值
// }
chart.axisLeft.apply {
axisMinimum = 0f // 下限固定 0(PID 不会为负)
spaceTop = 80f // 上方预留 20%,防止贴顶
spaceBottom = 5f // 下方预留一点更美观
setDrawGridLines(true)
setDrawZeroLine(false)

// Y 轴单位
valueFormatter = object : ValueFormatter() {
override fun getFormattedValue(value: Float): String = "${value.toInt()} mV"
}
}

chart.axisRight.isEnabled = false

// 缩放 + 拖动 + 图表一次最多能看到多少 X 点(宽度限制)
chart.setScaleEnabled(true)
chart.setPinchZoom(true)
chart.setVisibleXRangeMaximum(maxVisiblePoints)

chart.description.isEnabled = false



//替换默认的renderer为自定义的SampleHighlightRenderer,画高亮区间
val highlightPaint = Paint().apply {
style = Paint.Style.FILL
color = Color.parseColor("#5533B5E5") // 半透明蓝色
}
val renderer = SampleHighlightRenderer(binding.combinedChart, binding.combinedChart.viewPortHandler, highlightPaint)
binding.combinedChart.renderer = renderer


//设置高亮的mark
// val marker = MyMarkerView(this)
// marker.chartView = binding.combinedChart
// binding.combinedChart.marker = marker
//
// chart.isHighlightPerTapEnabled = true
// chart.isHighlightPerDragEnabled = true
chart.invalidate()
}



private fun addGCPoint(value: Float) {
val chart = binding.combinedChart
val entry = Entry(xIndex, value)

xIndex += 1f

lineDataSet.addEntry(entry)
if(xIndex == 10f || xIndex == 20f){
redDataSet.addEntry(entry)
}


chart.data.lineData.notifyDataChanged()
chart.data.notifyDataChanged()
chart.notifyDataSetChanged()

// 固定显示最近 maxVisiblePoints 个点
if (lineDataSet.entryCount > maxVisiblePoints) {
chart.setVisibleXRangeMaximum(maxVisiblePoints)
chart.moveViewToX(lineDataSet.entryCount - maxVisiblePoints)
} else {
chart.moveViewToX(0f)
}

chart.invalidate()

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
先进入/var/www  hexo对应的根目录

然后下载主题:
git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/butterfly


⚙️ 主题配置
启用主题:修改您的 Hexo 配置文件 _config.yml:
theme: butterfly
安装依赖项:如果您尚未安装 pug 和 stylus 渲染器,请运行:
npm install hexo-renderer-pug hexo-renderer-stylus --save

重启:hexo clean && hexo g && hexo server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
使用子域名是多网站部署里最常见的方式。你可以给每个网站分配一个子域名,比如:

www.example.com → 主站

shop.example.com → 电商站

blog.example.com → 博客

1️⃣ DNS 配置

在你的域名解析后台(阿里云、Cloudflare、Namecheap 等)添加子域名 A 记录,指向服务器 IP:

主机记录 类型 记录值 TTL
www A 123.123.123.123 10分钟
shop A 123.123.123.123 10分钟
blog A 123.123.123.123 10分钟

如果使用泛域名 *.example.com,也可以所有子域名都指向同一 IP。

2️⃣ Nginx 配置示例(子域名方式)
# 主站 www
server {
listen 80;
server_name www.example.com;

root /var/www/main;
index index.html;
}

# 电商站 shop
server {
listen 80;
server_name shop.example.com;

root /var/www/shop;
index index.html;
}

# 博客 blog
server {
listen 80;
server_name blog.example.com;

root /var/www/blog;
index index.html;
}

3️⃣ 测试 & 重载
sudo nginx -t
sudo systemctl reload nginx

sudo /usr/local/nginx/sbin/nginx -s reload

然后访问:

http://www.example.com → 主站

http://shop.example.com → 电商站

http://blog.example.com → 博客

Nginx 会根据请求的 Host 自动匹配对应的 server_name,返回对应网页。

✅ 优点

不占用额外端口,所有网站用标准 80/443 端口

DNS 可灵活管理,支持分发到不同服务器

子域名 SSL 证书可独立或用泛域名证书

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
服务器与域名解析详解(国内 & 海外)
1. 服务器概述

服务器是提供数据和服务的计算机或软件系统,分为 物理服务器 和 云服务器,按地理位置又可分为 国内服务器 和 海外服务器。

1.1 国内服务器

提供商:阿里云、腾讯云、华为云等

特点:

访问速度快(对国内用户友好)

受中国法律监管,需要 ICP备案

通常带有完善的云安全和监控系统

适用场景:

面向国内用户的企业网站、电商、应用服务等

1.2 海外服务器

提供商:AWS、DigitalOcean、Linode、Vultr、Hetzner 等

特点:

国际访问速度较快

不需要国内备案

价格相对灵活

适用场景:

面向海外用户的网站或服务

全球访问加速需求的应用

2. 域名与DNS概述

域名是网站在互联网上的“门牌号”,DNS(Domain Name System)是将域名转换为服务器 IP 地址的系统。

2.1 域名的组成

顶级域名(TLD):如 .com, .cn, .org

二级域名:如 example.com

子域名:如 www.example.com

2.2 DNS解析流程

用户在浏览器输入域名 www.example.com

浏览器查询本地缓存,若无记录则请求 操作系统 DNS

DNS 递归解析服务器(如 8.8.8.8 / 阿里 DNS)查找域名对应的 IP

返回 IP 后,浏览器向服务器发起 HTTP/HTTPS 请求

2.3 常用DNS记录类型
类型 描述 示例
A 域名映射到 IPv4 地址 www.example.com -> 115.190.154.26
AAAA 域名映射到 IPv6 地址 www.example.com -> 2400:xxxx:xxxx
CNAME 别名记录,指向另一个域名 blog.example.com -> www.example.com
MX 邮件交换记录 mail.example.com
TXT 文本记录,用于验证或配置 SPF/DKIM google-site-verification=xxx
NS 指定域名服务器 example.com -> dns1.example.com
3. 域名解析流程实例(国内&海外)
3.1 国内域名解析

在 阿里云/腾讯云/华为云注册域名

登录域名管理控制台,设置 A记录/CNAME记录,指向国内服务器 IP

若访问国内用户,建议使用 云解析DNS,提高解析速度

对于网站访问,需完成 ICP备案(法律要求)

示例:

A记录:
@ 115.190.154.26
www 115.190.154.26


注意:国内备案后,域名才能在国内用户正常访问。

3.2 海外域名解析

在 Namecheap / GoDaddy / Cloudflare 等注册域名

设置 A 记录指向海外服务器 IP

无需国内备案,但访问国内用户速度可能受限

可结合 CDN(如 Cloudflare) 加速访问

示例:

A记录:
@ 139.59.12.34
www 139.59.12.34

4. 服务器与域名的配置步骤
4.1 国内服务器配置

服务器部署:

云服务器(ECS/云主机)系统:Linux/Windows

安装 Web 服务:Nginx/Apache

域名解析:

登录域名控制台,添加 A/CNAME 记录

备案流程:

提交个人/企业信息

等待 ICP 审核通过(一般 1~20 个工作日)

Nginx 配置示例:

server {
listen 80;
server_name codehelps.online www.codehelps.online;
root /var/www/html;
index index.html index.htm;
}


生效命令:

nginx -t # 检查配置是否正确
systemctl reload nginx # 重新加载配置

4.2 海外服务器配置

基本步骤与国内类似,但无需备案

可以选择配置 HTTPS(通过 Let’s Encrypt 免费证书)

sudo certbot --nginx -d codehelps.online -d www.codehelps.online

5. 注意事项
场景 注意事项
国内服务器 必须备案,否则访问国内会被屏蔽
海外服务器 国内访问可能慢,可用 CDN 或加速节点
域名到期 域名过期会导致网站无法访问,备案信息不需主动撤销
DNS生效 DNS修改后通常 5~30 分钟生效,最长 48 小时
6. 小结

国内域名+国内服务器:速度快,必须备案

海外域名+海外服务器:无需备案,全球访问方便

混合方案:国内用户走国内服务器,海外用户走海外服务器,结合 CDN 提高访问速度