Справочник участников M2M: выгрузка в JSON и XML
На странице /admin/handbook добавлены кнопки «JSON» и «XML» — скачивание сохранённого справочника участников (readHandbook: от НРД #M2MTH, копия с сервера лицензий или встроенный снимок) файлом. JSON отдаётся как есть с отступами, XML формируется из плоских записей. Маршруты /admin/handbook/ export.json и export.xml под админ-авторизацией. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -566,6 +566,10 @@ func RegisterAdmin(mux *http.ServeMux, svc *Service, rc *RuntimeConfig, getOpts
|
|||||||
a.renderClaim(w, r, svc, id)
|
a.renderClaim(w, r, svc, id)
|
||||||
case p == "status":
|
case p == "status":
|
||||||
a.renderStatus(w, r, getOpts())
|
a.renderStatus(w, r, getOpts())
|
||||||
|
case p == "handbook/export.json":
|
||||||
|
a.exportHandbook(w, "json")
|
||||||
|
case p == "handbook/export.xml":
|
||||||
|
a.exportHandbook(w, "xml")
|
||||||
case p == "handbook":
|
case p == "handbook":
|
||||||
a.renderHandbook(w, r)
|
a.renderHandbook(w, r)
|
||||||
case p == "help":
|
case p == "help":
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package lkgateway
|
||||||
|
|
||||||
|
// handbook_export.go — выгрузка справочника участников M2M со страницы
|
||||||
|
// /admin/handbook в файлах JSON и XML. Данные — самый свежий справочник
|
||||||
|
// (readHandbook): полученный от НРД #M2MTH, копия с сервера лицензий или
|
||||||
|
// встроенный снимок. Только чтение, доступ — под админ-авторизацией /admin/*.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"encoding/xml"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hbEntryXML — запись справочника в XML (атрибутами, как плоский JSON).
|
||||||
|
type hbEntryXML struct {
|
||||||
|
INN string `xml:"inn,attr,omitempty"`
|
||||||
|
Code string `xml:"code,attr,omitempty"`
|
||||||
|
Kind string `xml:"kind,attr,omitempty"`
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
FullName string `xml:"fullName,attr,omitempty"`
|
||||||
|
DisplayName string `xml:"displayName,attr,omitempty"`
|
||||||
|
EngName string `xml:"engName,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// hbExportXML — корень XML-выгрузки справочника.
|
||||||
|
type hbExportXML struct {
|
||||||
|
XMLName xml.Name `xml:"M2MHandbook"`
|
||||||
|
At string `xml:"at,attr,omitempty"`
|
||||||
|
Participants []hbEntryXML `xml:"Participants>Participant"`
|
||||||
|
Places []hbEntryXML `xml:"SettlementPlaces>Place"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func hbEntryToXML(e HandbookEntry) hbEntryXML {
|
||||||
|
return hbEntryXML{INN: e.INN, Code: e.Code, Kind: e.Kind, Name: e.Name,
|
||||||
|
FullName: e.FullName, DisplayName: e.DisplayName, EngName: e.EngName}
|
||||||
|
}
|
||||||
|
|
||||||
|
// exportHandbook отдаёт сохранённый справочник участников M2M файлом JSON или XML.
|
||||||
|
func (a *admin) exportHandbook(w http.ResponseWriter, format string) {
|
||||||
|
raw, err := readHandbook()
|
||||||
|
if err != nil || len(raw) == 0 {
|
||||||
|
http.Error(w, "Справочник ещё не загружен — запросите его у НРД.", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var hb struct {
|
||||||
|
At string `json:"at"`
|
||||||
|
Participants []HandbookEntry `json:"participants"`
|
||||||
|
Places []HandbookEntry `json:"settlement_places"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(raw, &hb); err != nil {
|
||||||
|
http.Error(w, "Справочник повреждён: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
day := time.Now().Format("2006-01-02")
|
||||||
|
if format == "xml" {
|
||||||
|
doc := hbExportXML{At: hb.At}
|
||||||
|
for _, p := range hb.Participants {
|
||||||
|
doc.Participants = append(doc.Participants, hbEntryToXML(p))
|
||||||
|
}
|
||||||
|
for _, p := range hb.Places {
|
||||||
|
doc.Places = append(doc.Places, hbEntryToXML(p))
|
||||||
|
}
|
||||||
|
out, err := xml.MarshalIndent(doc, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Не удалось сформировать XML: "+err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Disposition", `attachment; filename="m2m-handbook-`+day+`.xml"`)
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
_, _ = w.Write([]byte(xml.Header))
|
||||||
|
_, _ = w.Write(out)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// JSON — отдаём сохранённый справочник как есть, с отступами для читаемости.
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if json.Indent(&buf, raw, "", " ") != nil {
|
||||||
|
buf.Reset()
|
||||||
|
buf.Write(raw)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.Header().Set("Content-Disposition", `attachment; filename="m2m-handbook-`+day+`.json"`)
|
||||||
|
w.Header().Set("Cache-Control", "no-store")
|
||||||
|
_, _ = w.Write(buf.Bytes())
|
||||||
|
}
|
||||||
@@ -45,6 +45,8 @@
|
|||||||
<option value="place">Места расчётов</option>
|
<option value="place">Места расчётов</option>
|
||||||
</select>
|
</select>
|
||||||
<span class="muted" id="hb-count" style="font-size:13px;white-space:nowrap"></span>
|
<span class="muted" id="hb-count" style="font-size:13px;white-space:nowrap"></span>
|
||||||
|
<a class="btn" href="/admin/handbook/export.json" download title="Скачать справочник в формате JSON">JSON</a>
|
||||||
|
<a class="btn" href="/admin/handbook/export.xml" download title="Скачать справочник в формате XML">XML</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user