从 PHP 到 AI + Golang,程序员自救转型手记(五十八):后台系统配置管理实现
ä» PHP å° AI + Golangï¼ç¨åºåèªæè½¬åæè®°ï¼äºåå «ï¼ï¼åå°ç³»ç»é 置管çå®ç°
å¦ç çè± 2026-08-08 0 é 读4åéè¿æ¯ä¸ä¸ªç³»å Blogï¼ä½è å°ä»¥ä¸ä¸ª PHP å ¨æ å·¥ç¨å¸ç身份ï¼å©ç¨ AI å·¥å ·ï¼claude codeãcodexãdeepseekãè±å çï¼ï¼ä»é¶å¼å§å¦ä¹ golang è¯è¨ï¼å¹¶æç»å®æ ai-go-adminï¼github | giteeï¼å¼æºé¡¹ç®çå¶ä½ï¼æ¬¢è¿ star~ï¼ï¼å ¨ç¨è®°å½å享ã
å¨ä¸ä¸æï¼æä»¬è¿è¡äº âagInput ç»ä¸å ¥å£ï¼å¨æè¡¨åé¢å¤âï¼æ¬æå°å®æï¼åå°ç³»ç»é 置管çå®ç°
åå°ç³»ç»é 置管çå®ç°
让 AIï¼åè @../ba238/web/src/views/backend/routine/config/index.vue å®ç°æ¬é¡¹ç®ç @web/src/views/admin/routine/config/index.vue 页é¢ï¼ç³»ç»é
ç½®åè½ï¼
- æå¡ç«¯ç³»ç»é
置对åºç模åä½äº
@internal/model/common.goï¼è¿ç§»æä»¶ä½äº@cmd/migrate/migrations/000002_common.up.sql - æ ¹æ®æ¨¡å建ç«å¯¹åºç
@internal/handler/admin/routine/config.goæ§å¶å¨å对åºçä»å¨ãæå¡ - æå¡ç«¯å¯¹åºç
php代ç åèä½äº@../ba238/app/admin/controller/routine/Config.php
è¿æ¬¡ AI åç代ç ï¼review åï¼åºæ¬ä¸é½æ¾å¼äºðï¼ç¶å人工æ
¢æ
¢æäºä¸æ´å¤©ï¼ä¸»è¦è¿æ¯å 为系ç»é
置项å¯ä»¥å¨åå°å¨æå¢å ï¼ä¹å°±æ¯ å¨æè¡¨åï¼æä»¬ç agInput æ¯æå¤è¾¾ 25 ä¸è¡¨åç±»åï¼å个è¾å
¥ç»ä»¶çç»å®å¼ç±»åååæä¸åï¼go è¿å¯¹ç±»åéå¸¸ä¸¥æ ¼ï¼æ³ç´æ¥ä» PHP é£è¾¹è¿ç§»è¿æ¥é常ä¸ç¨³å¥ã
卿£å¼å¼å§å¶ä½ä¹åï¼äººå·¥éä¸å¯¹è¾å ¥ç»ä»¶çç»å®å¼ç±»åè¿è¡äºç¡®å®åæ´çï¼æ¯å¦ï¼
ä¸ä¼ çè¾å ¥ç»ä»¶ç»å®å¼ä¸æ¯æ null
è¿æ¶ç±äºç³»ç»é
置表ç value æ¯æ¯æ null çï¼*string æéææéç好å¤ï¼ä¸åèµè¿°ï¼ï¼è¿éçéæ©æ¯è®©è¿äºè¾å
¥ç»ä»¶ç»ä¸æ¯æå° nullã
为 null ååºå
¥åºåè½¬æ¢æ¯ä¸å¯è½çï¼é£æ ·ä¼å¤å¾å¤æ ·æ¿ä»£ç ã
è¾å ¥ç»ä»¶ç»å®å¼ç±»å为æ°ç»
selectsãå¾çå¤éãæä»¶å¤é çï¼é½æ¯è¿åæ°ç»ï¼ä½æ¯ç³»ç»é
置表ç value æ¯åºå®ç stringï¼ä¸è½æ¹æ¯ææ°ç»ç´æ¥åå¨çç±»åï¼é£æ ·åè¿æ¥ string ç±»ååæé®é¢äºï¼ï¼
å¯¹äº å¾çå¤é å æä»¶å¤éï¼å 强äºç»ä»¶å
é¨çç»å®å¼æºå¶ï¼å¤éæ¶å¦æä¼ å
¥çæ¯å符串æç©ºæ°ç»ï¼è¾åºçç»å®å¼å°±ç¨è¿æ ·çæ ¼å¼ a.png,b.pngï¼å³ä¼ å
¥å符串ï¼åªææ¯å¤éï¼ä¹è¾åºéå·åå²çå符串ã
å¯¹äº select å¤é å checkboxï¼å®ä»¬æ¯ç»ä»¶åºæä¾çåçç»ä»¶ï¼æ²¡æ³äºå¼ï¼é£å°±åªè½è½¬æ¢äºï¼ä¸è¿è¿é便§æ²¡éæ©æå¡ç«¯è½¬æ¢ï¼èæ¯å端å¨è·ååä¿åæ°æ®åè¿è¡è½¬æ¢ï¼remoteSelects å array åçï¼number ç±»åç转æ¢åçï¼ä¸ºäºæ¹ä¾¿è½¬æ¢ï¼è¿äº src\components\agInput\index.ts å®ä¹å¥½äº è¾å
¥æ¡çç»å®å¼ç±»å æ å°ï¼å¦ä¸ï¼
/**
* è¾å
¥æ¡çç»å®å¼ç±»å
* ç»ä»¶è¾åºå¼çç±»åï¼å¯è½æ¯æè¾åºå¤ç§ç±»åï¼æ¤å¤ååºçæ¯æ¨èç±»å
*/
export const inputModelValueTypes = {
number: ['number', 'switch', 'remoteSelect'],
array: ['checkbox', 'array', 'selects', 'remoteSelects'],
string: ['å
¶ä»é½æ¯æ stringï¼æ¤å¤ä¸åååº'],
}
è¾å ¥ç»ä»¶æ©å±å±æ§
卿·»å é
置项æ¶ï¼è¿å¯ä»¥å¡«å è¾å
¥ç»ä»¶çæ©å±å±æ§ï¼æ¯å¦èªå®ä¹ classãpkãremote-url çï¼
å¼ä¸éè¦å¸¦å¼å·ï¼ä¸è¡ä¸ä¸ªï¼æ¯æ åç¬¦ä¸²ãæ°åãå¸å°å¼ï¼
class=testpagination=falselevel=3
对è¿äºæ©å±å±æ§çè§£æï¼æ¯ç´æ¥å¨åç«¯å®æçï¼è®© AI å®ç°çå° å±æ§å符串解æä¸º JSON ç彿°å¦ä¸ï¼
/**
* å°åç¬¦ä¸²å±æ§å表转为对象
* ä¸è¡ä¸ä¸ª key=value é®å¼å¯¹ï¼æ¯æ boolãæ°åèªå¨è½¬æ¢ï¼æ¯æç¹å·åµå¥
*/
export const parseStrAttr = (attr: string): Record<string, any> => {
const result: Record<string, any> = {}
if (!attr) return result
const lines = attr.replace(/\r\n/g, '\n').trim().split('\n')
for (const line of lines) {
const trimmed = line.trim()
if (!trimmed) continue
const eqIdx = trimmed.indexOf('=')
if (eqIdx <= 0) continue
const key = trimmed.slice(0, eqIdx).trim()
let value: any = trimmed.slice(eqIdx + 1).trim()
if (value === 'false') value = false
else if (value === 'true') value = true
else if (value !== '' && !isNaN(Number(value))) value = parseFloat(value)
if (key.includes('.')) {
set(result, key, value)
continue
}
result[key] = value
}
return result
}
æ°æ®éªè¯
æ·»å æ°çç³»ç»é ç½®æ¶ï¼æ¯æéæ©é ç½®çæ°æ®éªè¯æ¹æ³ï¼åç«¯å¤§æ¦æ¯è¿æ ·åºç¨è¿äºéªè¯è§åçï¼
<template>
<div>
<!-- 表åä¸ç»å® rules å model -->
<el-form :rules="state.rules" :model="state.form"></el-form>
</div>
</template>
<script setup lang="ts">
import { buildValidatorRule, type BuildValidatorParams } from '/@/utils/validate'
// å¨ä»æå¡ç«¯è·åå°é
ç½®æ°æ®åç»è£
éªè¯è§åæ°æ®
let rules: Partial<Record<string, FormItemRule[]>> = {}
for (const key in state.config) {
for (const lKey in state.config[key].configs) {
if (state.config[key].configs[lKey].rule) {
let ruleStr = state.config[key].configs[lKey].rule.split(',')
let ruleArr: AnyObj = []
ruleStr.forEach((item: string) => {
ruleArr.push(
buildValidatorRule({ name: item as BuildValidatorParams['name'], title: state.config[key].configs[lKey].title })
)
})
rules = Object.assign(rules, {
[state.config[key].configs[lKey].name]: ruleArr,
})
}
}
}
</script>
åªæäº¤å½å tab çæ°æ®
å¦ä¸å¼å§çå¾çï¼ç³»ç»é
ç½®æå¤ä¸ª tabï¼å为 åºç¡é
ç½®ãé®ä»¶é
ç½® çï¼å¦ææ¯æ¬¡é½æäº¤å
¨é¨çè¯ï¼éªè¯ä¿¡æ¯ä¼é tab æ¾ç¤ºï¼ä¸åªæäº¤å½å tab 乿´å åçï¼å®ç°æ¹å¼å¦ä¸ï¼
å端æäº¤è¡¨åæ¶ï¼
// åªæäº¤å½åtabçè¡¨åæ°æ®
const formData: AnyObj = {}
for (const key in state.config) {
if (state.config[key].name != state.activeTab) {
continue
}
for (const lKey in state.config[key].configs) {
const { type, name } = state.config[key].configs[lKey]
// ç»è£
éè¦æäº¤çæ°æ®
formData[name] = state.form[name] ?? ''
}
}
ç»è£ åºæ¥ç请æ±ä½å¤§æ¦æ¯ï¼
{
name: 'AI GO ADMIN',
version: 'v1.0.0',
// ......
}
è½ç¶å·²ç»æ¯åªæå½å tab çé®å¼å¯¹äºï¼ä¸è¿é¢å¤è¿æ¯åéäºå½åç tab åç§°ï¼ å¯¹äºæå¡ç«¯æ¥è¯´ï¼tab åç§°å°±æ¯ groupï¼æ§è¡æ´æ°çæ¶åï¼å å
¥ group=tab name ç where å³å¯ã
Aitishiku.com