Просмотр исходного кода

feat: 增强穴位导出并补充开发设计文档

jiapu 4 месяцев назад
Родитель
Сommit
7b75c81568

+ 12 - 0
code/backend/src/main/java/com/aijiuyi/admin/controller/AcupointController.java

@@ -65,6 +65,18 @@ public class AcupointController {
         return Result.success(acupointService.pageList(queryDTO));
     }
 
+    /**
+     * 导出全部穴位(返回当前筛选条件下的全量数据供前端 Excel 导出使用)
+     *
+     * @param queryDTO 查询条件(不含分页参数)
+     * @return 穴位列表
+     */
+    @GetMapping("/export")
+    @Log(value = "导出穴位列表", module = "穴位管理", operationType = OperationType.OTHER)
+    public Result<List<Acupoint>> export(AcupointQueryDTO queryDTO) {
+        return Result.success(acupointService.listAll(queryDTO));
+    }
+
     /**
      * 查询穴位详情
      *

+ 8 - 0
code/backend/src/main/java/com/aijiuyi/admin/service/AcupointService.java

@@ -21,6 +21,14 @@ public interface AcupointService extends IService<Acupoint> {
      */
     IPage<Acupoint> pageList(AcupointQueryDTO queryDTO);
 
+    /**
+     * 查询全部穴位(用于 Excel 导出,不分页)
+     *
+     * @param queryDTO 查询条件
+     * @return 穴位列表
+     */
+    List<Acupoint> listAll(AcupointQueryDTO queryDTO);
+
     /**
      * 获取可用于穴位Y轴计算的医学定位术语选项
      *

+ 10 - 1
code/backend/src/main/java/com/aijiuyi/admin/service/impl/AcupointServiceImpl.java

@@ -43,6 +43,15 @@ public class AcupointServiceImpl extends ServiceImpl<AcupointMapper, Acupoint> i
     @Override
     public IPage<Acupoint> pageList(AcupointQueryDTO queryDTO) {
         Page<Acupoint> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
+        return page(page, buildQueryWrapper(queryDTO));
+    }
+
+    @Override
+    public List<Acupoint> listAll(AcupointQueryDTO queryDTO) {
+        return list(buildQueryWrapper(queryDTO));
+    }
+
+    private LambdaQueryWrapper<Acupoint> buildQueryWrapper(AcupointQueryDTO queryDTO) {
         LambdaQueryWrapper<Acupoint> wrapper = new LambdaQueryWrapper<>();
         wrapper.like(StringUtils.hasText(queryDTO.getName()), Acupoint::getName, queryDTO.getName())
                .eq(StringUtils.hasText(queryDTO.getMeridian()), Acupoint::getMeridian, queryDTO.getMeridian())
@@ -71,7 +80,7 @@ public class AcupointServiceImpl extends ServiceImpl<AcupointMapper, Acupoint> i
                    w.eq(Acupoint::getParseStatus, parseStatus);
                })
                .orderByDesc(Acupoint::getCreateTime);
-        return page(page, wrapper);
+        return wrapper;
     }
 
     @Override

+ 1 - 1
code/backend/src/main/resources/mapper/DeviceMapper.xml

@@ -23,7 +23,7 @@
             d.create_time,
             d.update_time,
             COUNT(DISTINCT ud.user_id) AS bound_user_count,
-            GROUP_CONCAT(DISTINCT up.name ORDER BY up.name SEPARATOR '、') AS bound_user_names
+            GROUP_CONCAT(DISTINCT up.name ORDER BY ud.bind_time DESC SEPARATOR '、') AS bound_user_names
         FROM device d
         LEFT JOIN user_device ud ON ud.device_code = d.device_code
             AND ud.deleted = 0

+ 8 - 0
code/frontend/src/api/acupoint.js

@@ -28,6 +28,14 @@ export function getAcupointPage(params) {
   return request.get('/acupoint/page', { params })
 }
 
+/**
+ * 导出穴位列表(全量,用于 Excel 导出)
+ * @param {Object} params 查询条件(不含分页参数)
+ */
+export function exportAcupoint(params) {
+  return request.get('/acupoint/export', { params })
+}
+
 /**
  * 查询穴位详情
  * @param {number} id 穴位ID

+ 15 - 3
code/frontend/src/components/ExcelExport.vue

@@ -12,7 +12,7 @@
 
 <script setup>
 import { ref } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ElMessage, ElMessageBox } from 'element-plus'
 import { Download } from '@element-plus/icons-vue'
 import { exportExcel } from '@/utils/excel'
 
@@ -48,7 +48,8 @@ const props = defineProps({
   fileName:   { type: String, default: 'export' },
   headers:    { type: Array, required: true },
   data:       { type: Array, default: null },
-  fetchData:  { type: Function, default: null }
+  fetchData:  { type: Function, default: null },
+  confirmMessage: { type: String, default: '' }
 })
 
 const loading = ref(false)
@@ -58,8 +59,16 @@ async function handleExport() {
     ElMessage.warning('请配置导出表头')
     return
   }
-  loading.value = true
   try {
+    if (props.confirmMessage) {
+      await ElMessageBox.confirm(props.confirmMessage, '提示', {
+        type: 'warning',
+        confirmButtonText: '是',
+        cancelButtonText: '否'
+      })
+    }
+
+    loading.value = true
     let exportData = props.data || []
     // 如果有 fetchData 函数,调用它获取全量数据
     if (props.fetchData) {
@@ -76,6 +85,9 @@ async function handleExport() {
     })
     ElMessage.success('导出成功')
   } catch (err) {
+    if (err === 'cancel' || err === 'close') {
+      return
+    }
     ElMessage.error(err?.message || '导出失败')
   } finally {
     loading.value = false

+ 240 - 9
code/frontend/src/utils/excel.js

@@ -26,15 +26,246 @@ export function exportExcel({ data = [], headers = [], fileName = 'export', shee
   const dataRows = data.map(row => headers.map(h => row[h.prop] ?? ''))
   // 合并表头和数据
   const sheetData = [headerRow, ...dataRows]
-  // 创建工作表
-  const worksheet = XLSX.utils.aoa_to_sheet(sheetData)
-  // 设置列宽(根据表头内容自动调整)
-  worksheet['!cols'] = headers.map(h => ({ wch: Math.max(h.label.length * 2, 12) }))
-  // 创建工作簿
-  const workbook = XLSX.utils.book_new()
-  XLSX.utils.book_append_sheet(workbook, worksheet, sheetName)
-  // 触发下载
-  XLSX.writeFile(workbook, `${fileName}.xlsx`)
+  const colWidths = headers.map(h => Math.max(h.label.length * 2, 12))
+  const workbookBlob = createStyledXlsx(sheetData, colWidths, sheetName)
+  downloadBlob(workbookBlob, `${fileName}.xlsx`)
+}
+
+function createStyledXlsx(sheetData, colWidths, sheetName) {
+  const files = [
+    ['[Content_Types].xml', contentTypesXml()],
+    ['_rels/.rels', rootRelsXml()],
+    ['docProps/app.xml', appXml()],
+    ['docProps/core.xml', coreXml()],
+    ['xl/workbook.xml', workbookXml(sheetName)],
+    ['xl/_rels/workbook.xml.rels', workbookRelsXml()],
+    ['xl/styles.xml', stylesXml()],
+    ['xl/worksheets/sheet1.xml', worksheetXml(sheetData, colWidths)]
+  ]
+  return new Blob([zipStore(files)], {
+    type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+  })
+}
+
+function worksheetXml(sheetData, colWidths) {
+  const rowCount = Math.max(sheetData.length, 1)
+  const colCount = Math.max(colWidths.length, 1)
+  const dimension = `A1:${columnName(colCount)}${rowCount}`
+  const cols = colWidths.map((width, index) => (
+    `<col min="${index + 1}" max="${index + 1}" width="${width}" customWidth="1"/>`
+  )).join('')
+  const rows = sheetData.map((row, rowIndex) => {
+    const rowNumber = rowIndex + 1
+    const cells = row.map((value, colIndex) => cellXml(value, rowNumber, colIndex + 1, rowIndex === 0)).join('')
+    return `<row r="${rowNumber}">${cells}</row>`
+  }).join('')
+
+  return xmlDeclaration() +
+    `<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ` +
+    `xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">` +
+    `<dimension ref="${dimension}"/><sheetViews><sheetView workbookViewId="0"/></sheetViews>` +
+    `<cols>${cols}</cols><sheetData>${rows}</sheetData></worksheet>`
+}
+
+function cellXml(value, rowNumber, colNumber, isHeader) {
+  const ref = `${columnName(colNumber)}${rowNumber}`
+  const style = isHeader ? ' s="1"' : ''
+  if (typeof value === 'number' && Number.isFinite(value)) {
+    return `<c r="${ref}"${style}><v>${value}</v></c>`
+  }
+  return `<c r="${ref}" t="inlineStr"${style}><is><t>${escapeXml(value)}</t></is></c>`
+}
+
+function stylesXml() {
+  return xmlDeclaration() +
+    '<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">' +
+    '<fonts count="2">' +
+    '<font><sz val="11"/><color rgb="FF000000"/><name val="Calibri"/><family val="2"/></font>' +
+    '<font><b/><sz val="14"/><color rgb="FF000000"/><name val="Calibri"/><family val="2"/></font>' +
+    '</fonts>' +
+    '<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>' +
+    '<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>' +
+    '<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>' +
+    '<cellXfs count="2">' +
+    '<xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/>' +
+    '<xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/>' +
+    '</cellXfs>' +
+    '<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>' +
+    '<dxfs count="0"/><tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4"/>' +
+    '</styleSheet>'
+}
+
+function workbookXml(sheetName) {
+  return xmlDeclaration() +
+    '<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" ' +
+    'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">' +
+    `<sheets><sheet name="${escapeXml(sheetName).slice(0, 31) || 'Sheet1'}" sheetId="1" r:id="rId1"/></sheets>` +
+    '</workbook>'
+}
+
+function workbookRelsXml() {
+  return xmlDeclaration() +
+    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
+    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet1.xml"/>' +
+    '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>' +
+    '</Relationships>'
+}
+
+function rootRelsXml() {
+  return xmlDeclaration() +
+    '<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">' +
+    '<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>' +
+    '<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/>' +
+    '<Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/>' +
+    '</Relationships>'
+}
+
+function contentTypesXml() {
+  return xmlDeclaration() +
+    '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">' +
+    '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>' +
+    '<Default Extension="xml" ContentType="application/xml"/>' +
+    '<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>' +
+    '<Override PartName="/xl/worksheets/sheet1.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>' +
+    '<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>' +
+    '<Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>' +
+    '<Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>' +
+    '</Types>'
+}
+
+function appXml() {
+  return xmlDeclaration() +
+    '<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties" ' +
+    'xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">' +
+    '<Application>aijiuyi</Application></Properties>'
+}
+
+function coreXml() {
+  const now = new Date().toISOString()
+  return xmlDeclaration() +
+    '<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" ' +
+    'xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" ' +
+    'xmlns:dcmitype="http://purl.org/dc/dcmitype/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' +
+    '<dc:creator>aijiuyi</dc:creator><cp:lastModifiedBy>aijiuyi</cp:lastModifiedBy>' +
+    `<dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created>` +
+    `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>` +
+    '</cp:coreProperties>'
+}
+
+function zipStore(files) {
+  const encoder = new TextEncoder()
+  const chunks = []
+  const centralDirectory = []
+  let offset = 0
+
+  files.forEach(([name, content]) => {
+    const nameBytes = encoder.encode(name)
+    const dataBytes = encoder.encode(content)
+    const crc = crc32(dataBytes)
+    const localHeader = new Uint8Array(30 + nameBytes.length)
+    const localView = new DataView(localHeader.buffer)
+    localView.setUint32(0, 0x04034b50, true)
+    localView.setUint16(4, 20, true)
+    localView.setUint16(8, 0, true)
+    localView.setUint32(14, crc, true)
+    localView.setUint32(18, dataBytes.length, true)
+    localView.setUint32(22, dataBytes.length, true)
+    localView.setUint16(26, nameBytes.length, true)
+    localHeader.set(nameBytes, 30)
+    chunks.push(localHeader, dataBytes)
+
+    const centralHeader = new Uint8Array(46 + nameBytes.length)
+    const centralView = new DataView(centralHeader.buffer)
+    centralView.setUint32(0, 0x02014b50, true)
+    centralView.setUint16(4, 20, true)
+    centralView.setUint16(6, 20, true)
+    centralView.setUint32(16, crc, true)
+    centralView.setUint32(20, dataBytes.length, true)
+    centralView.setUint32(24, dataBytes.length, true)
+    centralView.setUint16(28, nameBytes.length, true)
+    centralView.setUint32(42, offset, true)
+    centralHeader.set(nameBytes, 46)
+    centralDirectory.push(centralHeader)
+    offset += localHeader.length + dataBytes.length
+  })
+
+  const centralOffset = offset
+  const centralSize = centralDirectory.reduce((sum, item) => sum + item.length, 0)
+  const endRecord = new Uint8Array(22)
+  const endView = new DataView(endRecord.buffer)
+  endView.setUint32(0, 0x06054b50, true)
+  endView.setUint16(8, files.length, true)
+  endView.setUint16(10, files.length, true)
+  endView.setUint32(12, centralSize, true)
+  endView.setUint32(16, centralOffset, true)
+
+  return concatUint8Arrays([...chunks, ...centralDirectory, endRecord])
+}
+
+function crc32(data) {
+  let crc = -1
+  for (let i = 0; i < data.length; i++) {
+    crc = (crc >>> 8) ^ CRC_TABLE[(crc ^ data[i]) & 0xff]
+  }
+  return (crc ^ -1) >>> 0
+}
+
+const CRC_TABLE = (() => {
+  const table = new Uint32Array(256)
+  for (let i = 0; i < 256; i++) {
+    let value = i
+    for (let bit = 0; bit < 8; bit++) {
+      value = value & 1 ? 0xedb88320 ^ (value >>> 1) : value >>> 1
+    }
+    table[i] = value >>> 0
+  }
+  return table
+})()
+
+function concatUint8Arrays(arrays) {
+  const totalLength = arrays.reduce((sum, item) => sum + item.length, 0)
+  const result = new Uint8Array(totalLength)
+  let offset = 0
+  arrays.forEach((item) => {
+    result.set(item, offset)
+    offset += item.length
+  })
+  return result
+}
+
+function downloadBlob(blob, fileName) {
+  const url = URL.createObjectURL(blob)
+  const link = document.createElement('a')
+  link.href = url
+  link.download = fileName
+  document.body.appendChild(link)
+  link.click()
+  document.body.removeChild(link)
+  URL.revokeObjectURL(url)
+}
+
+function columnName(index) {
+  let name = ''
+  while (index > 0) {
+    const remainder = (index - 1) % 26
+    name = String.fromCharCode(65 + remainder) + name
+    index = Math.floor((index - 1) / 26)
+  }
+  return name
+}
+
+function escapeXml(value) {
+  return String(value ?? '')
+    .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f]/g, '')
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&apos;')
+}
+
+function xmlDeclaration() {
+  return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>'
 }
 
 /**

+ 31 - 10
code/frontend/src/views/acupoint/index.vue

@@ -45,7 +45,8 @@
             <ExcelImport :on-import="handleImport" button-text="导入" @success="loadData" />
             <ExcelExport
               :headers="exportHeaders"
-              :data="tableData"
+              :fetch-data="fetchExportData"
+              confirm-message="是否导出当前筛选条件下的所有数据?"
               file-name="穴位列表"
             />
             <el-button type="primary" :icon="Plus" @click="handleAdd">新增</el-button>
@@ -309,6 +310,7 @@ import ExcelImport from '@/components/ExcelImport.vue'
 import ExcelExport from '@/components/ExcelExport.vue'
 import {
   getAcupointPage,
+  exportAcupoint,
   addAcupoint,
   updateAcupoint,
   deleteAcupoint,
@@ -414,21 +416,40 @@ async function loadData() {
     const res = await getAcupointPage(queryForm)
     const records = res.data?.records || []
     // 添加辅助显示字段
-    tableData.value = records.map(item => ({
-      ...item,
-      parseStatusLabel: parseStatusLabel(item.parseStatus),
-      parseConfidenceLabel: parseConfidenceLabel(item.parseConfidence),
-      sideLabel: ['', '中心', '左侧', '右侧', '双侧'][item.side] || '-',
-      genderLabel: ['不限', '仅男', '仅女'][item.gender] || '-',
-      statusLabel: item.status === 1 ? '活跃' : '待完善',
-      productAcupointLabel: item.productAcupoint === 1 ? '是' : '否'
-    }))
+    tableData.value = records.map(formatAcupointRow)
     total.value = res.data?.total || 0
   } finally {
     loading.value = false
   }
 }
 
+function formatAcupointRow(item) {
+  return {
+    ...item,
+    parseStatusLabel: parseStatusLabel(item.parseStatus),
+    parseConfidenceLabel: parseConfidenceLabel(item.parseConfidence),
+    sideLabel: ['', '中心', '左侧', '右侧', '双侧'][item.side] || '-',
+    genderLabel: ['不限', '仅男', '仅女'][item.gender] || '-',
+    statusLabel: item.status === 1 ? '活跃' : '待完善',
+    productAcupointLabel: item.productAcupoint === 1 ? '是' : '否'
+  }
+}
+
+/**
+ * 获取导出数据(全量,传入当前筛选条件)
+ */
+async function fetchExportData() {
+  const params = {
+    name: queryForm.name,
+    meridian: queryForm.meridian,
+    status: queryForm.status,
+    parseStatus: queryForm.parseStatus
+  }
+  const res = await exportAcupoint(params)
+  const list = res.data || []
+  return list.map(formatAcupointRow)
+}
+
 function parseStatusLabel(status) {
   const map = {
     '已解析': '已解析',

+ 26 - 4
code/frontend/src/views/device/index.vue

@@ -66,7 +66,7 @@
         <el-table-column type="index" label="序号" width="60" align="center" />
         <el-table-column prop="deviceCode" label="设备编号" min-width="110" />
         <el-table-column prop="deviceName" label="设备名称" min-width="120" />
-        <el-table-column prop="deviceModel" label="设备型号" width="110">
+        <el-table-column prop="deviceModel" label="设备型号" width="90">
           <template #default="{ row }">{{ row.deviceModel || '-' }}</template>
         </el-table-column>
         <el-table-column label="绑定用户数" width="95" align="center">
@@ -75,7 +75,7 @@
           </template>
         </el-table-column>
         <el-table-column prop="boundUserNames" label="绑定用户" min-width="130">
-          <template #default="{ row }">{{ row.boundUserNames || '-' }}</template>
+          <template #default="{ row }">{{ formatBoundUserNames(row) }}</template>
         </el-table-column>
         <el-table-column label="在线状态" width="90" align="center">
           <template #default="{ row }">
@@ -87,10 +87,10 @@
         <el-table-column prop="firmwareVersion" label="固件版本" width="95">
           <template #default="{ row }">{{ row.firmwareVersion || '-' }}</template>
         </el-table-column>
-        <el-table-column prop="lastOnlineTime" label="最后在线时间" width="160">
+        <el-table-column prop="lastOnlineTime" label="最后在线时间" width="140">
           <template #default="{ row }">{{ row.lastOnlineTime || '-' }}</template>
         </el-table-column>
-        <el-table-column label="操作" width="195" align="center" fixed="right">
+        <el-table-column label="操作" width="170" align="center" fixed="right">
           <template #default="{ row }">
             <el-button size="small" type="primary" link @click="handleView(row)">查看</el-button>
             <el-button size="small" type="warning" link @click="handleEdit(row)">编辑</el-button>
@@ -241,6 +241,7 @@ const loading = ref(false)
 const selectedIds = ref([])
 const pageNum = ref(1)
 const pageSize = ref(10)
+const BOUND_USER_DISPLAY_LIMIT = 3
 
 /** 搜索条件 */
 const query = reactive({
@@ -251,6 +252,27 @@ const query = reactive({
   firmwareVersion: ''
 })
 
+/**
+ * 设备列表只展示最新绑定的前 3 个用户,更多用户用省略号提示。
+ * boundUserNames 由后端按绑定时间倒序拼接。
+ */
+function formatBoundUserNames(row) {
+  const names = String(row.boundUserNames || '')
+    .split('、')
+    .map(name => name.trim())
+    .filter(Boolean)
+
+  if (names.length === 0) {
+    return '-'
+  }
+
+  const total = Number(row.boundUserCount) || names.length
+  const displayText = names.slice(0, BOUND_USER_DISPLAY_LIMIT).join('、')
+  return total > BOUND_USER_DISPLAY_LIMIT || names.length > BOUND_USER_DISPLAY_LIMIT
+    ? `${displayText}...`
+    : displayText
+}
+
 /** 导出表头配置 */
 const exportHeaders = [
   { label: '设备编号', prop: 'deviceCode' },

+ 1292 - 0
code/开发设计文档.md

@@ -0,0 +1,1292 @@
+# 艾灸椅管理后台系统 开发设计文档
+
+## 目录
+
+1. [项目概述](#1-项目概述)
+2. [技术架构](#2-技术架构)
+3. [项目结构](#3-项目结构)
+4. [数据库设计](#4-数据库设计)
+5. [核心模块与接口设计](#5-核心模块与接口设计)
+6. [核心算法设计(穴位定位算法3.0)](#6-核心算法设计穴位定位算法30)
+7. [前端架构设计](#7-前端架构设计)
+8. [安全机制](#8-安全机制)
+9. [部署说明](#9-部署说明)
+
+---
+
+## 1. 项目概述
+
+### 1.1 项目简介
+
+艾灸椅管理后台系统(aijiuyi-admin)是一套用于管理艾灸椅设备的Web后台管理系统,提供用户管理、穴位管理、方案管理、设备管理、艾灸手法管理等功能,并通过智能穴位定位算法为用户提供个性化的艾灸方案。
+
+### 1.2 项目背景
+
+本项目针对艾灸椅产品设计,通过采集用户体型数据(肩宽、脊柱长度、身高体重等),结合作者体数据(手指宽度),运用智能穴位定位算法,为每个用户计算个性化的穴位坐标,实现精准艾灸理疗。
+
+### 1.3 核心功能模块
+
+| 模块 | 功能描述 |
+|------|----------|
+| 认证管理 | 用户登录、退出、Token管理 |
+| 账号管理 | 应用用户(管理员/专家/普通用户)的增删改查 |
+| 用户管理 | 子用户档案管理(含体型数据) |
+| 穴位管理 | 穴位信息CRUD、批量导入 |
+| 方案管理 | 艾灸方案的创建、编辑、发布、下架 |
+| 设备管理 | 艾灸椅设备管理及用户绑定 |
+| 手法管理 | 艾灸手法(温和灸、雀啄灸等)管理 |
+| 用户穴位 | 用户个性化穴位坐标生成与查询 |
+| 用户方案 | 用户订阅的方案管理 |
+| 模拟测试 | 方案模拟推送数据组装 |
+| 系统日志 | 操作日志记录与查询 |
+
+---
+
+## 2. 技术架构
+
+### 2.1 技术栈总览
+
+```
+┌─────────────────────────────────────────────────────────────┐
+│                        前端 (Frontend)                       │
+├─────────────────────────────────────────────────────────────┤
+│  Vue 3 + Composition API + Element Plus + Pinia + Vue Router │
+│                         Vite 4.0                            │
+└─────────────────────────────────────────────────────────────┘
+                              │
+                              │ HTTP/REST
+                              ▼
+┌─────────────────────────────────────────────────────────────┐
+│                        后端 (Backend)                       │
+├─────────────────────────────────────────────────────────────┤
+│                  Spring Boot 2.7.18                        │
+│  ┌──────────────┬──────────────┬──────────────┐            │
+│  │   Web MVC    │    Redis     │  MyBatis Plus│            │
+│  │   + AOP      │  + Lettuce   │   + MySQL    │            │
+│  └──────────────┴──────────────┴──────────────┘            │
+│                                                              │
+│  ┌──────────────┬──────────────┬──────────────┐            │
+│  │     JWT      │   Validation │    Log AOP   │            │
+│  │  Token鉴权   │   参数校验    │   操作日志   │            │
+│  └──────────────┴──────────────┴──────────────┘            │
+└─────────────────────────────────────────────────────────────┘
+```
+
+### 2.2 后端技术栈
+
+| 技术 | 版本 | 说明 |
+|------|------|------|
+| Spring Boot | 2.7.18 | 核心框架 |
+| Spring MVC | 5.3.31 | Web层 |
+| Spring AOP | 5.3.31 | 切面编程 |
+| Spring Data Redis | 2.7.18 | Redis集成 |
+| MyBatis Plus | 3.5.3.1 | ORM框架 |
+| MySQL Connector | 8.0.33 | 数据库驱动 |
+| JWT | 0.9.1 | Token生成与解析 |
+| FastJSON | 1.2.83 | JSON处理 |
+| Lombok | - | 简化代码 |
+| Apache Commons Lang3 | 3.12.0 | 工具类 |
+
+### 2.3 前端技术栈
+
+| 技术 | 版本 | 说明 |
+|------|------|------|
+| Vue | 3.5.32 | 核心框架 |
+| Vue Router | 4.6.4 | 路由管理 |
+| Pinia | 3.0.4 | 状态管理 |
+| Element Plus | 2.13.6 | UI组件库 |
+| Axios | 1.14.0 | HTTP客户端 |
+| Vite | 8.0.4 | 构建工具 |
+| @element-plus/icons-vue | 2.3.2 | 图标库 |
+| xlsx | 0.18.5 | Excel处理 |
+| sass | 1.99.0 | CSS预处理器 |
+
+### 2.4 中间件与基础设施
+
+| 组件 | 说明 |
+|------|------|
+| MySQL 8.0 | 关系型数据库 |
+| Redis | Token存储、缓存 |
+| JDK | 1.8+ |
+| Node.js | 16.0+ |
+
+---
+
+## 3. 项目结构
+
+### 3.1 后端项目结构
+
+```
+code/backend/
+├── src/main/
+│   ├── java/com/aijiuyi/admin/
+│   │   ├── AijiuyiAdminApplication.java    # 启动类
+│   │   │
+│   │   ├── common/                         # 公共模块
+│   │   │   ├── annotation/                  # 自定义注解
+│   │   │   │   ├── Log.java                 # 操作日志注解
+│   │   │   │   └── NoAuth.java              # 免鉴权注解
+│   │   │   ├── aspect/                      # 切面
+│   │   │   │   └── LogAspect.java           # 日志切面
+│   │   │   ├── config/                      # 配置类
+│   │   │   │   ├── AuthProperties.java     # 鉴权配置
+│   │   │   │   ├── MybatisPlusConfig.java  # MyBatis配置
+│   │   │   │   ├── RedisConfig.java        # Redis配置
+│   │   │   │   └── WebMvcConfig.java       # Web MVC配置
+│   │   │   ├── constant/                    # 常量定义
+│   │   │   │   └── ResultCode.java         # 响应码枚举
+│   │   │   ├── context/                     # 上下文
+│   │   │   │   └── RequestContext.java    # 请求上下文(ThreadLocal)
+│   │   │   ├── entity/                      # 公共实体
+│   │   │   │   ├── Result.java             # 统一响应
+│   │   │   │   └── SysLog.java             # 系统日志
+│   │   │   ├── enums/                       # 枚举类
+│   │   │   │   └── OperationType.java     # 操作类型枚举
+│   │   │   ├── exception/                   # 异常处理
+│   │   │   │   ├── BusinessException.java # 业务异常
+│   │   │   │   └── GlobalExceptionHandler.java # 全局异常处理
+│   │   │   ├── handler/                     # 处理器
+│   │   │   │   └── MyMetaObjectHandler.java # MyBatis自动填充
+│   │   │   ├── interceptor/                  # 拦截器
+│   │   │   │   └── TokenInterceptor.java   # Token拦截器
+│   │   │   ├── service/                      # 公共服务接口
+│   │   │   │   └── SysLogService.java
+│   │   │   └── util/                         # 工具类
+│   │   │       ├── AcupointLocationParser.java  # 穴位定位解析器
+│   │   │       ├── AcupointYReference.java     # Y轴医学定位
+│   │   │       ├── IpUtil.java                # IP工具
+│   │   │       ├── LogUtil.java                # 日志工具
+│   │   │       ├── RedisUtil.java              # Redis工具
+│   │   │       └── TokenUtil.java              # Token工具
+│   │   │
+│   │   ├── controller/                       # 控制层
+│   │   │   ├── AcupointController.java       # 穴位管理
+│   │   │   ├── AdminController.java          # 管理员管理
+│   │   │   ├── AppUserController.java        # 应用用户管理
+│   │   │   ├── AuthController.java           # 认证管理
+│   │   │   ├── DeviceController.java         # 设备管理
+│   │   │   ├── MoxibustionTechniqueController.java  # 手法管理
+│   │   │   ├── PlanController.java           # 方案管理
+│   │   │   ├── SimulationController.java     # 模拟测试
+│   │   │   ├── SysLogController.java         # 日志管理
+│   │   │   ├── UserAcupointController.java    # 用户穴位管理
+│   │   │   ├── UserDeviceController.java     # 用户设备管理
+│   │   │   ├── UserPlanController.java       # 用户方案管理
+│   │   │   └── UserProfileController.java     # 用户档案管理
+│   │   │
+│   │   ├── service/                          # 业务层接口
+│   │   │   ├── AcupointService.java
+│   │   │   ├── AdminService.java
+│   │   │   ├── AppUserService.java
+│   │   │   ├── DeviceService.java
+│   │   │   ├── MoxibustionTechniqueService.java
+│   │   │   ├── PlanService.java
+│   │   │   ├── SimulationService.java
+│   │   │   ├── SysUserService.java
+│   │   │   ├── UserAcupointService.java
+│   │   │   ├── UserDeviceService.java
+│   │   │   ├── UserPlanService.java
+│   │   │   └── UserProfileService.java
+│   │   │
+│   │   ├── service/impl/                     # 业务层实现
+│   │   │   ├── AcupointServiceImpl.java      # 穴位服务实现
+│   │   │   ├── AdminServiceImpl.java
+│   │   │   ├── AppUserServiceImpl.java
+│   │   │   ├── DeviceServiceImpl.java
+│   │   │   ├── MoxibustionTechniqueServiceImpl.java
+│   │   │   ├── PlanServiceImpl.java
+│   │   │   ├── SimulationServiceImpl.java     # 模拟测试实现
+│   │   │   ├── SysUserServiceImpl.java
+│   │   │   ├── UserAcupointServiceImpl.java   # 穴位算法实现
+│   │   │   ├── UserDeviceServiceImpl.java
+│   │   │   ├── UserPlanServiceImpl.java
+│   │   │   └── UserProfileServiceImpl.java
+│   │   │
+│   │   ├── mapper/                           # 数据访问层
+│   │   │   ├── AcupointMapper.java
+│   │   │   ├── AdminPermissionMapper.java
+│   │   │   ├── AppUserMapper.java
+│   │   │   ├── DeviceMapper.java
+│   │   │   ├── MoxibustionTechniqueMapper.java
+│   │   │   ├── PlanMapper.java
+│   │   │   ├── PlanStepMapper.java
+│   │   │   ├── SysLogMapper.java
+│   │   │   ├── SysUserMapper.java
+│   │   │   ├── UserAcupointMapper.java
+│   │   │   ├── UserDeviceMapper.java
+│   │   │   ├── UserPlanMapper.java
+│   │   │   └── UserProfileMapper.java
+│   │   │
+│   │   ├── entity/                           # 实体类
+│   │   │   ├── Acupoint.java
+│   │   │   ├── AdminPermission.java
+│   │   │   ├── AppUser.java
+│   │   │   ├── Device.java
+│   │   │   ├── DeviceUserVO.java
+│   │   │   ├── MoxibustionTechnique.java
+│   │   │   ├── Plan.java
+│   │   │   ├── PlanStep.java
+│   │   │   ├── SysUser.java
+│   │   │   ├── UserAcupoint.java
+│   │   │   ├── UserDevice.java
+│   │   │   ├── UserPlan.java
+│   │   │   └── UserProfile.java
+│   │   │
+│   │   └── controller/dto/                     # 数据传输对象
+│   │       ├── AcupointQueryDTO.java
+│   │       ├── AppUserQueryDTO.java
+│   │       ├── DeviceQueryDTO.java
+│   │       ├── DeviceSaveDTO.java
+│   │       ├── LoginDTO.java
+│   │       ├── MoxibustionTechniqueQueryDTO.java
+│   │       ├── PlanQueryDTO.java
+│   │       ├── PlanSaveDTO.java
+│   │       ├── SimulationResponse.java
+│   │       ├── UserDeviceQueryDTO.java
+│   │       ├── UserProfileDTO.java
+│   │       ├── UserProfileQueryDTO.java
+│   │       └── YReferenceOptionDTO.java
+│   │
+│   └── resources/
+│       ├── mapper/                           # MyBatis XML映射文件
+│       │   ├── AcupointMapper.xml
+│       │   ├── AppUserMapper.xml
+│       │   ├── DeviceMapper.xml
+│       │   ├── MoxibustionTechniqueMapper.xml
+│       │   ├── PlanMapper.xml
+│       │   ├── PlanStepMapper.xml
+│       │   ├── SysLogMapper.xml
+│       │   ├── SysUserMapper.xml
+│       │   ├── UserAcupointMapper.xml
+│       │   ├── UserDeviceMapper.xml
+│       │   ├── UserPlanMapper.xml
+│       │   └── UserProfileMapper.xml
+│       │
+│       ├── sql/                              # SQL脚本
+│       │   ├── acupoint_full_data_import.sql # 穴位数据导入
+│       │   ├── init.sql                     # 初始化脚本
+│       │   ├── migration_acupoint_auto_parse.sql
+│       │   └── migration_algorithm_v3.sql   # 算法v3迁移
+│       │
+│       ├── application.yml                  # 主配置
+│       ├── application-dev.yml              # 开发环境配置
+│       ├── application-prod.yml             # 生产环境配置
+│       ├── application-test.yml             # 测试环境配置
+│       └── rebel.xml                        # 热加载配置
+│
+└── pom.xml                                  # Maven配置
+```
+
+### 3.2 前端项目结构
+
+```
+code/frontend/
+├── public/                                  # 静态资源
+│   ├── favicon.svg
+│   ├── icons.svg
+│   └── logo.svg
+│
+├── src/
+│   ├── api/                                 # API接口模块化
+│   │   ├── acupoint.js                     # 穴位管理接口
+│   │   ├── admin.js                        # 管理员接口
+│   │   ├── auth.js                          # 认证接口
+│   │   ├── device.js                        # 设备接口
+│   │   ├── index.js                        # API统一导出
+│   │   ├── log.js                          # 日志接口
+│   │   ├── moxibustion.js                  # 手法接口
+│   │   ├── plan.js                         # 方案接口
+│   │   ├── simulation.js                   # 模拟测试接口
+│   │   └── user.js                         # 用户接口
+│   │
+│   ├── assets/                              # 资源文件
+│   │   ├── acupoint-back-realistic.jpg
+│   │   ├── hero.png
+│   │   ├── vite.svg
+│   │   └── vue.svg
+│   │
+│   ├── components/                          # 公共组件
+│   │   ├── ExcelExport.vue                # Excel导出组件
+│   │   ├── ExcelImport.vue                # Excel导入组件
+│   │   └── HelloWorld.vue
+│   │
+│   ├── layout/                             # 布局组件
+│   │   └── index.vue                       # 后台管理布局
+│   │
+│   ├── router/                             # 路由配置
+│   │   └── index.js                        # 路由定义与守卫
+│   │
+│   ├── store/                              # 状态管理
+│   │   └── user.js                         # 用户状态
+│   │
+│   ├── utils/                              # 工具函数
+│   │   ├── excel.js                        # Excel处理工具
+│   │   ├── request.js                      # Axios封装
+│   │   └── storage.js                      # 本地存储工具
+│   │
+│   ├── views/                              # 页面视图
+│   │   ├── acupoint/                       # 穴位管理页面
+│   │   │   └── index.vue
+│   │   ├── admin/                          # 管理员页面
+│   │   │   └── index.vue
+│   │   ├── dashboard/                      # 仪表盘
+│   │   │   └── index.vue
+│   │   ├── device/                         # 设备管理页面
+│   │   │   ├── index.vue                   # 设备列表
+│   │   │   └── users.vue                   # 设备用户管理
+│   │   ├── log/                            # 日志页面
+│   │   │   └── index.vue
+│   │   ├── login/                          # 登录页面
+│   │   │   └── index.vue
+│   │   ├── moxibustion/                    # 手法管理页面
+│   │   │   └── index.vue
+│   │   ├── plan/                           # 方案管理页面
+│   │   │   └── index.vue
+│   │   ├── simulation/                     # 模拟测试页面
+│   │   │   └── index.vue
+│   │   ├── user/                           # 用户管理模块
+│   │   │   ├── app-user/                   # 应用用户
+│   │   │   │   └── index.vue
+│   │   │   ├── device/                     # 用户设备
+│   │   │   │   └── index.vue
+│   │   │   ├── plan/                       # 用户方案
+│   │   │   │   └── index.vue
+│   │   │   └── profile/                    # 用户档案
+│   │   │       └── index.vue
+│   │   └── 404.vue                         # 404页面
+│   │
+│   ├── App.vue                             # 根组件
+│   ├── main.js                             # 入口文件
+│   └── style.css                           # 全局样式
+│
+├── index.html                              # HTML模板
+├── package.json                            # NPM配置
+├── vite.config.js                          # Vite配置
+└── README.md
+```
+
+---
+
+## 4. 数据库设计
+
+### 4.1 ER图概述
+
+```
+┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
+│    app_user     │      │  user_profile   │      │  user_acupoint  │
+│  应用用户表      │──────│  子用户档案表   │──────│  用户穴位坐标表  │
+└─────────────────┘      └─────────────────┘      └─────────────────┘
+        │                        │
+        │                        │
+        ▼                        ▼
+┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
+│ admin_permission│      │   user_device   │      │     acupoint    │
+│  管理员权限表    │      │  用户设备绑定表  │◄─────│     穴位表      │
+└─────────────────┘      └─────────────────┘      └─────────────────┘
+                                 │
+                                 ▼
+┌─────────────────┐      ┌─────────────────┐      ┌─────────────────┐
+│     device      │      │    user_plan    │      │   moxibustion   │
+│   艾灸椅设备表   │      │   用户方案表     │◄─────│   艾灸手法表     │
+└─────────────────┘      └─────────────────┘      └─────────────────┘
+                                 ▲
+                                 │
+                        ┌─────────────────┐
+                        │    plan_step    │
+                        │   方案步骤表    │◄───── plan (方案主表)
+                        └─────────────────┘
+```
+
+### 4.2 数据表详细设计
+
+#### 4.2.1 应用用户表 (app_user)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| phone | VARCHAR(20) | 手机号(唯一) |
+| username | VARCHAR(64) | 用户名(管理员登录用,唯一) |
+| password | VARCHAR(64) | 密码(MD5加密) |
+| nickname | VARCHAR(100) | 昵称 |
+| avatar | VARCHAR(500) | 头像URL |
+| user_type | TINYINT | 用户类型:1=普通用户,2=专家,3=管理员 |
+| admin_role | TINYINT | 管理员角色:1=超级管理员,2=运营人员,3=只读用户 |
+| status | TINYINT | 状态:1=正常,0=禁用 |
+| last_login_time | DATETIME | 最后登录时间 |
+| deleted | TINYINT | 逻辑删除:0=未删除,1=已删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.2 子用户信息表 (user_profile)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| user_id | BIGINT | 所属应用用户ID |
+| name | VARCHAR(50) | 姓名 |
+| gender | TINYINT | 性别:1=男,2=女 |
+| age | INT | 年龄 |
+| phone | VARCHAR(20) | 手机号 |
+| shoulder_width | DECIMAL(5,1) | 肩宽(cm) |
+| body_height | DECIMAL(5,1) | 身长/坐高(cm) |
+| spine_length | DECIMAL(5,1) | 脊柱长度C7-S4(cm,范围30-60) |
+| height | DECIMAL(5,1) | 身高(cm) |
+| weight | DECIMAL(5,1) | 体重(kg) |
+| finger_width_1 | DECIMAL(4,2) | 1寸指宽(cm) |
+| finger_width_15 | DECIMAL(4,2) | 1.5寸指宽(cm) |
+| finger_width_3 | DECIMAL(4,2) | 3寸指宽(cm) |
+| acupoint_table_id | BIGINT | 用户穴位表ID |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.3 穴位信息表 (acupoint)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| acupoint_code | VARCHAR(100) | 穴位编码(唯一标识) |
+| excel_serial_no | VARCHAR(20) | Excel原始序号 |
+| name | VARCHAR(50) | 穴位名称 |
+| single_double | VARCHAR(20) | 单/双穴 |
+| meridian | VARCHAR(100) | 所属经络 |
+| body_part | VARCHAR(50) | 身体部位 |
+| body_region | VARCHAR(50) | 部位分区 |
+| location_description | VARCHAR(500) | 穴位定位原文 |
+| relative_position | VARCHAR(200) | 相对位置原文 |
+| indications | VARCHAR(1000) | 主管疾病/主治 |
+| product_symptoms | VARCHAR(500) | 产品症状覆盖 |
+| acupoint_method | VARCHAR(500) | 取穴说明 |
+| product_acupoint | TINYINT | 是否艾灸椅产品穴位:1=是,0=否 |
+| effects | VARCHAR(500) | 功效描述 |
+| location_type | TINYINT | 定位方式:1=相对定位 |
+| longitude_offset | DECIMAL(6,2) | 相对大椎穴纵向偏移(寸) |
+| lateral_offset | DECIMAL(6,2) | 相对大椎穴横向偏移(寸) |
+| normalized_y | DECIMAL(6,4) | Panjabi归一化Y位置系数(0~1) |
+| y_reference_term | VARCHAR(100) | Y轴医学定位术语 |
+| parse_status | VARCHAR(20) | 解析状态:已解析/待复核/暂不支持 |
+| parse_confidence | VARCHAR(10) | 置信度:高/中/低 |
+| parse_message | VARCHAR(500) | 解析说明 |
+| side | TINYINT | 侧别:1=中心,2=左侧,3=右侧,4=双侧 |
+| finger_width_type | TINYINT | 指寸类型:1=一寸,2=1.5寸,3=三寸 |
+| gender | TINYINT | 适用性别:0=不限,1=仅男,2=仅女 |
+| status | TINYINT | 状态:1=活跃,0=待完善 |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.4 用户穴位坐标表 (user_acupoint)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| user_id | BIGINT | 子用户ID |
+| acupoint_id | BIGINT | 穴位ID |
+| acupoint_name | VARCHAR(50) | 穴位名称(冗余) |
+| acupoint_side | VARCHAR(10) | 侧别:center/left/right |
+| cun_value | DECIMAL(6,2) | 相对大椎穴纵向偏移(寸) |
+| cun_type | VARCHAR(10) | 指寸类型 |
+| coordinate_x | DECIMAL(8,2) | X坐标(mm,向右为正) |
+| coordinate_y | DECIMAL(8,2) | Y坐标(mm,向下为正) |
+| coordinate_z | DECIMAL(8,2) | Z坐标(mm,体表=0) |
+| confidence | VARCHAR(10) | 置信度:high/medium/low |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.5 方案表 (plan)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| plan_code | VARCHAR(50) | 方案编码(唯一) |
+| name | VARCHAR(100) | 方案名称 |
+| mode_type | TINYINT | 模式类型:1=一键艾灸,2=专业模式,3=自定义模式,4=延年圣手 |
+| effect_type | VARCHAR(20) | 功效类型:驱寒/祛湿/祛风/化瘀/活血/化痰/养颜/扶阳 |
+| symptoms | VARCHAR(500) | 适用症状 |
+| applicable_gender | TINYINT | 适用人群:0=不限,1=仅男,2=仅女 |
+| age_min | INT | 适用年龄最小值 |
+| age_max | INT | 适用年龄最大值 |
+| description | VARCHAR(200) | 方案描述 |
+| author_type | TINYINT | 创作人类型:1=管理员,2=普通用户,3=专家 |
+| author_id | BIGINT | 创作人ID |
+| author_name | VARCHAR(100) | 创作人名称 |
+| status | TINYINT | 状态:0=草稿,1=已发布,2=已下架 |
+| use_count | INT | 使用人数 |
+| avg_rating | DECIMAL(3,1) | 平均评分 |
+| last_use_time | DATETIME | 最后使用时间 |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.6 方案步骤表 (plan_step)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| plan_id | BIGINT | 方案ID |
+| step_order | INT | 步骤序号 |
+| acupoint_id | BIGINT | 穴位ID |
+| acupoint_name | VARCHAR(50) | 穴位名称 |
+| side | TINYINT | 侧别:1=中心,2=左侧,3=右侧,4=双侧 |
+| temperature | DECIMAL(4,1) | 温度(°C,35-50) |
+| duration | INT | 时间(分钟,1-30) |
+| technique_id | BIGINT | 艾灸手法ID |
+| technique_name | VARCHAR(50) | 手法名称 |
+| remark | VARCHAR(100) | 备注 |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.7 艾灸手法表 (moxibustion_technique)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| technique_code | VARCHAR(50) | 手法编码(唯一) |
+| name | VARCHAR(50) | 手法名称 |
+| motion_mode | VARCHAR(20) | 运动模式:default/fixed/updown/circular/dotted |
+| temp_min | DECIMAL(4,1) | 温度范围最小值 |
+| temp_max | DECIMAL(4,1) | 温度范围最大值 |
+| time_min | INT | 时间范围最小值 |
+| time_max | INT | 时间范围最大值 |
+| motion_amplitude | DECIMAL(4,1) | 运动幅度(mm) |
+| motion_speed | DECIMAL(4,1) | 运动速度(mm/s) |
+| status | TINYINT | 状态:1=启用,0=禁用 |
+| remark | VARCHAR(200) | 备注 |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.8 设备表 (device)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| device_code | VARCHAR(50) | 设备编号(唯一) |
+| device_name | VARCHAR(100) | 设备名称 |
+| device_model | VARCHAR(50) | 设备型号 |
+| serial_no | VARCHAR(50) | 设备序列号(唯一) |
+| firmware_version | VARCHAR(20) | 固件版本 |
+| online_status | TINYINT | 在线状态:1=在线,0=离线 |
+| last_online_time | DATETIME | 最后在线时间 |
+| remark | VARCHAR(200) | 备注 |
+| deleted | TINYINT | 逻辑删除 |
+| create_time | DATETIME | 创建时间 |
+| update_time | DATETIME | 更新时间 |
+
+#### 4.2.9 系统操作日志表 (sys_log)
+
+| 字段名 | 类型 | 说明 |
+|--------|------|------|
+| id | BIGINT | 主键ID |
+| user_id | BIGINT | 请求用户ID |
+| user_name | VARCHAR(100) | 请求用户名 |
+| user_type | TINYINT | 操作者类型 |
+| request_url | VARCHAR(500) | 请求接口路径 |
+| request_params | TEXT | 请求参数(JSON) |
+| request_result | TEXT | 请求结果(JSON) |
+| api_desc | VARCHAR(200) | 接口说明 |
+| operation_type | VARCHAR(20) | 操作方式 |
+| request_ip | VARCHAR(50) | 请求客户端IP |
+| operation_module | VARCHAR(100) | 操作模块 |
+| request_id | VARCHAR(50) | 请求唯一ID |
+| create_time | DATETIME | 创建时间 |
+
+---
+
+## 5. 核心模块与接口设计
+
+### 5.1 认证模块 (AuthController)
+
+**接口基础路径**: `/api/auth`
+
+| 接口 | 方法 | 路径 | 说明 | 鉴权 |
+|------|------|------|------|------|
+| 登录 | POST | /auth/login | 用户登录,返回Token | 否 |
+| 退出 | POST | /auth/logout | 删除Redis中的Token | 是 |
+| 当前用户信息 | GET | /auth/info | 获取当前登录用户信息 | 是 |
+
+**登录请求示例**:
+```json
+POST /api/auth/login
+{
+  "username": "admin",
+  "password": "admin123"
+}
+```
+
+**登录响应示例**:
+```json
+{
+  "code": 200,
+  "message": "操作成功",
+  "data": {
+    "token": "eyJhbGciOiJIUzI1NiJ9...",
+    "userId": 1,
+    "username": "admin",
+    "nickname": "超级管理员"
+  }
+}
+```
+
+### 5.2 穴位管理模块 (AcupointController)
+
+**接口基础路径**: `/api/acupoint`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 穴位选项列表 | GET | /acupoint/list | 获取所有启用穴位(选择框用) |
+| Y轴定位术语选项 | GET | /acupoint/y-reference-options | 获取Y轴定位术语选项 |
+| 穴位分页列表 | GET | /acupoint/page | 分页查询穴位 |
+| 穴位详情 | GET | /acupoint/{id} | 查询单个穴位详情 |
+| 新增穴位 | POST | /acupoint | 新增穴位 |
+| 修改穴位 | PUT | /acupoint | 修改穴位 |
+| 删除穴位 | DELETE | /acupoint/{id} | 删除穴位(逻辑删除) |
+| 批量删除穴位 | DELETE | /acupoint/batch | 批量删除穴位 |
+| 批量导入穴位 | POST | /acupoint/import | 批量导入穴位 |
+
+**查询参数 (AcupointQueryDTO)**:
+| 参数 | 类型 | 说明 |
+|------|------|------|
+| name | String | 穴位名称(模糊查询) |
+| meridian | String | 经络 |
+| status | Integer | 状态 |
+| parseStatus | String | 解析状态 |
+| pageNum | Integer | 页码(默认1) |
+| pageSize | Integer | 每页条数(默认10) |
+
+### 5.3 方案管理模块 (PlanController)
+
+**接口基础路径**: `/api/plan`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 方案分页列表 | GET | /plan/page | 分页查询方案 |
+| 方案详情 | GET | /plan/{id} | 查询方案详情(含步骤) |
+| 新增方案 | POST | /plan | 新增方案(草稿) |
+| 修改方案 | PUT | /plan | 修改方案 |
+| 删除方案 | DELETE | /plan/{id} | 删除方案(仅草稿) |
+| 批量删除方案 | DELETE | /plan/batch | 批量删除(仅草稿) |
+| 发布方案 | PUT | /plan/{id}/publish | 发布方案 |
+| 批量发布方案 | PUT | /plan/batch/publish | 批量发布 |
+| 下架方案 | PUT | /plan/{id}/offshelf | 下架方案 |
+| 批量下架方案 | PUT | /plan/batch/offshelf | 批量下架 |
+
+### 5.4 设备管理模块 (DeviceController)
+
+**接口基础路径**: `/api/device`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 设备分页列表 | GET | /device/page | 分页查询设备 |
+| 设备详情 | GET | /device/{id} | 查询设备详情 |
+| 新增设备 | POST | /device | 新增设备 |
+| 修改设备 | PUT | /device | 修改设备 |
+| 删除设备 | DELETE | /device/{id} | 删除设备 |
+| 批量删除设备 | DELETE | /device/batch | 批量删除 |
+| 批量导入设备 | POST | /device/import | 批量导入 |
+| 设备绑定用户列表 | GET | /device/{deviceId}/users | 查询设备绑定的用户 |
+| 添加绑定用户 | POST | /device/{deviceId}/users/{profileId} | 为设备添加用户 |
+| 解绑用户 | DELETE | /device/{deviceId}/users/{profileId} | 解绑设备用户 |
+| 设为主用户 | PUT | /device/{deviceId}/users/{profileId}/primary | 设为主用户 |
+| 批量解绑 | DELETE | /device/{deviceId}/users/batch | 批量解绑 |
+
+### 5.5 用户管理模块 (UserProfileController)
+
+**接口基础路径**: `/api/user/profile`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 用户分页列表 | GET | /user/profile/page | 分页查询用户 |
+| 用户详情 | GET | /user/profile/{id} | 查询用户详情 |
+| 新增用户 | POST | /user/profile | 新增用户 |
+| 修改用户 | PUT | /user/profile | 修改用户 |
+| 删除用户 | DELETE | /user/profile/{id} | 删除用户 |
+| 批量删除用户 | DELETE | /user/profile/batch | 批量删除 |
+| 批量导入用户 | POST | /user/profile/import | 批量导入 |
+| 用户选项列表 | GET | /user/profile/options | 设备绑定用户下拉 |
+| 所有用户列表 | GET | /user/profile/list-all | 获取所有用户 |
+
+### 5.6 艾灸手法管理模块 (MoxibustionTechniqueController)
+
+**接口基础路径**: `/api/moxibustion-technique`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 手法选项列表 | GET | /moxibustion-technique/list | 获取启用手法(选择框) |
+| 手法分页列表 | GET | /moxibustion-technique/page | 分页查询手法 |
+| 手法详情 | GET | /moxibustion-technique/{id} | 查询手法详情 |
+| 新增手法 | POST | /moxibustion-technique | 新增手法 |
+| 修改手法 | PUT | /moxibustion-technique | 修改手法 |
+| 删除手法 | DELETE | /moxibustion-technique/{id} | 删除手法 |
+| 批量删除手法 | DELETE | /moxibustion-technique/batch | 批量删除 |
+
+### 5.7 用户穴位模块 (UserAcupointController)
+
+**接口基础路径**: `/api/user/acupoint`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 生成穴位坐标 | POST | /user/acupoint/{profileId}/generate | 触发生成用户穴位坐标 |
+| 穴位坐标列表 | GET | /user/acupoint/{profileId}/list | 查询用户穴位坐标列表 |
+
+### 5.8 方案模拟测试模块 (SimulationController)
+
+**接口基础路径**: `/api/simulation`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 组装模拟推送数据 | GET | /simulation/push-data/{profileId}/{planId} | 方案模拟推送数据 |
+
+### 5.9 用户方案模块 (UserPlanController)
+
+**接口基础路径**: `/api/user-plan`
+
+| 接口 | 方法 | 路径 | 说明 |
+|------|------|------|------|
+| 用户方案分页列表 | GET | /user-plan/page | 查询用户方案分页列表 |
+| 用户方案详情 | GET | /user-plan/{id} | 查询用户方案详情 |
+
+---
+
+## 6. 核心算法设计(穴位定位算法3.0)
+
+### 6.1 算法概述
+
+穴位定位算法3.0是一套基于用户体型数据的智能穴位坐标计算系统,通过多源数据融合和解剖分区策略,实现高精度的个性化穴位定位。
+
+### 6.2 核心输入参数
+
+| 参数 | 是否必填 | 说明 | 校验规则 |
+|------|---------|------|----------|
+| 脊柱长度(C7-S4) | **必填** | C7棘突至S4棘突实测距离 | 范围:30-60cm |
+| 肩宽 | **必填** | 两侧肩峰间距 | 范围:30-50cm |
+| 实测1寸值 | 可选 | 用户中指中节长度 | 范围:1.5-3.5cm |
+| 实测1.5寸值 | 可选 | 1.5倍中指长度 | 范围:2.3-5.3cm |
+| 实测3寸值 | 可选 | 3倍中指长度 | 范围:4.5-10.5cm |
+| 身高 | 条件必填 | 未填指寸时必填 | 用于BMI推算 |
+| 体重 | 条件必填 | 未填指寸时必填 | 用于BMI推算 |
+
+### 6.3 算法流程
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│                    穴位坐标计算流程                               │
+├─────────────────────────────────────────────────────────────────┤
+│                                                                  │
+│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
+│  │  第一步      │    │  第二步      │    │  第三步      │       │
+│  │  三路指寸融合 │───▶│  背部一寸宽度 │───▶│  置信度确定  │       │
+│  │  A路+B路     │    │  肩宽+指寸   │    │  高/中/低   │       │
+│  └──────────────┘    └──────────────┘    └──────────────┘       │
+│         │                   │                   │              │
+│         ▼                   ▼                   ▼              │
+│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
+│  │  第四步      │    │  第五步      │    │  第六步      │       │
+│  │  Y轴坐标计算 │    │  X轴坐标计算 │    │  Z轴坐标=0   │       │
+│  │  Panjabi归一化│   │  分区域策略  │    │  体表定位   │       │
+│  └──────────────┘    └──────────────┘    └──────────────┘       │
+│                                                                  │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+### 6.4 三路指寸融合策略
+
+#### 6.4.1 A路:实测融合值
+
+当用户填写任意指寸值时:
+- 1寸值:权重50%
+- 1.5寸值(折算为1寸):权重25%
+- 3寸值(折算为1寸):权重25%
+
+**计算公式**:
+```
+A路值 = Σ(折算值 × 权重) / Σ权重
+```
+
+#### 6.4.2 B路:BMI换算法
+
+当提供身高体重时:
+```python
+标准人BMI = 65 / (1.70)^2 ≈ 22.49
+用户BMI = 体重(kg) / (身高(m))^2
+B路指寸(mm) = 24.0 × (用户BMI / 标准人BMI)
+```
+
+#### 6.4.3 最终指寸确定
+
+| 条件 | 最终指寸 | 置信度 |
+|------|----------|--------|
+| A路存在且B路存在 | (A路 + B路) / 2 | 高 |
+| 仅A路存在 | A路 | 中 |
+| 仅B路存在 | B路 | 中 |
+| 两者都不存在 | 24.0mm(标准人) | 低 |
+
+### 6.5 背部一寸宽度计算
+
+#### 6.5.1 双源计算
+
+**来源一:肩宽推算法**
+```
+肩胛骨内缘间距 = 肩宽(mm) × 0.420
+背部一寸宽度_肩宽 = 肩胛骨内缘间距 / 6
+```
+
+**来源二:指寸验证法**
+```
+背部一寸宽度_指寸 = 最终指寸(mm)
+```
+
+#### 6.5.2 融合策略
+
+```python
+差异率 = |肩宽推算值 - 指寸验证值| / 肩宽推算值
+
+if 差异率 <= 0.15:
+    背部一寸宽度 = (肩宽推算值 + 指寸验证值) / 2
+elif 有实测指寸值(A路存在):
+    背部一寸宽度 = 指寸验证值
+else:
+    背部一寸宽度 = 肩宽推算值
+```
+
+### 6.6 Y轴坐标计算
+
+#### 6.6.1 Panjabi归一化位置系数
+
+基于Panjabi椎骨归一化位置系数,以C7为原点(0),S4为终点(1):
+
+| 椎骨 | 归一化位置 | 对应穴位 |
+|------|-----------|---------|
+| C7 | 0.000 | 大椎、肩中俞 |
+| T2 | 0.078 | 风门 |
+| T3 | 0.120 | 肺俞、身柱 |
+| T5 | 0.203 | 心俞 |
+| T7 | 0.283 | 膈俞 |
+| T9 | 0.361 | 肝俞 |
+| T11 | 0.438 | 脾俞 |
+| T12 | 0.476 | 胃俞 |
+| L2 | 0.552 | 肾俞、命门 |
+| L4 | 0.628 | 大肠俞、腰阳关 |
+| S1 | 0.750 | 上髎 |
+| S2 | 0.834 | 次髎 |
+| S3 | 0.917 | 中髎 |
+| S4 | 1.000 | 下髎 |
+
+#### 6.6.2 Y轴计算公式
+
+```
+Y坐标(mm) = 脊柱长度(mm) × 归一化位置系数
+```
+
+**回退策略**:若无脊柱长度,则使用寸制计算
+```
+Y坐标(mm) = 纵向偏移(寸) × 最终指寸(mm)
+```
+
+### 6.7 X轴坐标计算
+
+#### 6.7.1 分策略计算
+
+| 穴位类型 | 计算策略 | 公式 |
+|----------|----------|------|
+| 督脉穴位 | X = 0 | 后正中线 |
+| 膀胱经第一侧线(1.5寸) | 区域系数 | X = 1.5 × 背部一寸宽度 × 区域系数 |
+| 膀胱经第二侧线(3寸) | 无区域系数 | X = 3.0 × 背部一寸宽度 |
+| 八髎穴 | 骶后孔解剖基准 | 基准偏移 × 身高缩放比例 |
+
+#### 6.7.2 区域系数
+
+骶段(normalizedY ≥ 0.750)应用收窄系数:
+```
+区域系数 = 0.85
+```
+
+#### 6.7.3 八髎穴特殊处理
+
+| 穴位 | 基准偏移(mm) |
+|------|-------------|
+| 上髎 | 15 |
+| 次髎 | 17 |
+| 中髎 | 16 |
+| 下髎 | 11 |
+
+**计算公式**:
+```
+X坐标 = 基准偏移(mm) × (用户身高(cm) / 160cm标准身高)
+```
+
+### 6.8 Z轴坐标
+
+所有背部/颈部穴位均位于体表:
+```
+Z坐标 = 0 mm
+```
+
+### 6.9 置信度等级
+
+| 等级 | 条件 | 说明 |
+|------|------|------|
+| high | A路存在且B路存在 | 双源数据,高可信度 |
+| medium | 仅A路存在或仅B路存在 | 单源数据,中可信度 |
+| low | 两者都不存在 | 使用标准参数,低可信度 |
+
+### 6.10 算法核心代码路径
+
+算法实现位于:`com.aijiuyi.admin.service.impl.UserAcupointServiceImpl`
+
+关键方法:
+- `calculateRouteA()` - A路实测融合
+- `calculateRouteB()` - B路BMI换算
+- `calculateFinalCunMm()` - 最终指寸
+- `calculateBackOneCunWidth()` - 背部一寸宽度
+- `calculateY()` - Y轴坐标
+- `calculateLateralMm()` - X轴坐标
+- `calculateBaliaoX()` - 八髎穴X坐标
+
+---
+
+## 7. 前端架构设计
+
+### 7.1 技术架构
+
+前端采用Vue 3 + Composition API + Vite构建后台管理系统。
+
+```
+┌────────────────────────────────────────────────────────────┐
+│                        App.vue (根组件)                     │
+└────────────────────────────────────────────────────────────┘
+                              │
+                              ▼
+┌────────────────────────────────────────────────────────────┐
+│                    layout/index.vue                        │
+│                    (后台管理布局)                           │
+│  ┌─────────────┬─────────────────────────┬─────────────┐  │
+│  │             │                         │             │  │
+│  │  侧边导航    │      内容区域            │  顶部导航   │  │
+│  │  Sidebar    │      <router-view />     │  Header     │  │
+│  │             │                         │             │  │
+│  └─────────────┴─────────────────────────┴─────────────┘  │
+└────────────────────────────────────────────────────────────┘
+                              │
+                              ▼
+┌────────────────────────────────────────────────────────────┐
+│                    Views (页面视图)                          │
+│  ├── acupoint/index.vue        (穴位管理)                   │
+│  ├── admin/index.vue           (管理员管理)                 │
+│  ├── device/index.vue          (设备管理)                   │
+│  ├── moxibustion/index.vue     (手法管理)                  │
+│  ├── plan/index.vue            (方案管理)                  │
+│  ├── simulation/index.vue      (模拟测试)                   │
+│  ├── user/app-user/index.vue   (应用用户)                   │
+│  ├── user/device/index.vue     (用户设备)                   │
+│  ├── user/plan/index.vue       (用户方案)                   │
+│  ├── user/profile/index.vue   (用户档案)                   │
+│  ├── log/index.vue             (系统日志)                   │
+│  └── login/index.vue           (登录页)                     │
+└────────────────────────────────────────────────────────────┘
+```
+
+### 7.2 路由设计
+
+**路由配置**: `src/router/index.js`
+
+| 路径 | 组件 | 说明 | 鉴权 |
+|------|------|------|------|
+| /login | login/index.vue | 登录页 | 否 |
+| / | layout/index.vue | 主布局 | 是 |
+| /user/app | user/app-user/index.vue | 账号管理 | 是 |
+| /user/profile | user/profile/index.vue | 用户列表 | 是 |
+| /user/plan | user/plan/index.vue | 用户方案管理 | 是 |
+| /user/device | user/device/index.vue | 用户设备管理 | 是 |
+| /acupoint | acupoint/index.vue | 穴位管理 | 是 |
+| /device | device/index.vue | 设备管理 | 是 |
+| /device/users | device/users.vue | 设备用户管理 | 是 |
+| /moxibustion | moxibustion/index.vue | 手法管理 | 是 |
+| /plan | plan/index.vue | 方案管理 | 是 |
+| /simulation | simulation/index.vue | 模拟测试 | 是 |
+| /system/admin | admin/index.vue | 管理员 | 是 |
+| /system/log | log/index.vue | 系统日志 | 是 |
+
+### 7.3 状态管理
+
+**用户状态**: `src/store/user.js` (Pinia)
+
+```javascript
+// 状态结构
+{
+  token: string,      // JWT Token
+  userId: number,     // 用户ID
+  username: string,   // 用户名
+  nickname: string,   // 昵称
+  userType: number,   // 用户类型
+  adminRole: number,  // 管理员角色
+  permissions: array  // 权限路由列表
+}
+
+// Actions
+- setToken(token)
+- setUserInfo(userInfo)
+- logout()
+```
+
+### 7.4 HTTP请求封装
+
+**封装文件**: `src/utils/request.js`
+
+特性:
+- 基于Axios封装
+- 自动携带Token (`Authorization: Bearer {token}`)
+- 统一错误处理(401跳转登录、500服务器错误等)
+- 请求超时30秒
+- 业务状态码200视为成功
+
+### 7.5 API模块化
+
+**统一导出**: `src/api/index.js`
+
+每个业务模块对应一个API文件:
+- `acupoint.js` - 穴位管理
+- `admin.js` - 管理员管理
+- `auth.js` - 认证管理
+- `device.js` - 设备管理
+- `log.js` - 日志管理
+- `moxibustion.js` - 手法管理
+- `plan.js` - 方案管理
+- `simulation.js` - 模拟测试
+- `user.js` - 用户管理
+
+---
+
+## 8. 安全机制
+
+### 8.1 认证机制
+
+#### 8.1.1 JWT Token认证
+
+- 采用JWT RS256算法
+- Token存储于Redis,设置过期时间
+- 请求头格式:`Authorization: Bearer {token}`
+
+#### 8.1.2 Token拦截器
+
+位置:`com.aijiuyi.admin.common.interceptor.TokenInterceptor`
+
+功能:
+- 验证Token有效性
+- 解析用户ID存入ThreadLocal
+- 白名单路径跳过验证
+
+#### 8.1.3 白名单配置
+
+```yaml
+auth:
+  white-list:
+    - /api/auth/login
+    - /api/auth/register
+    - /api/actuator/**
+    - /api/doc.html
+    - /api/swagger-ui.html
+    - /api/v2/api-docs
+    - /api/swagger-resources/**
+    - /api/webjars/**
+```
+
+### 8.2 权限控制
+
+#### 8.2.1 管理员角色
+
+| 角色值 | 角色名称 | 说明 |
+|--------|----------|------|
+| 1 | 超级管理员 | 全部权限 |
+| 2 | 运营人员 | 部分管理权限 |
+| 3 | 只读用户 | 仅查看权限 |
+
+#### 8.2.2 路由权限
+
+管理员权限通过`admin_permission`表配置,可访问的路由路径以JSON数组形式存储。
+
+### 8.3 操作日志
+
+#### 8.3.1 日志记录
+
+使用`@Log`注解标记需要记录日志的接口:
+```java
+@Log(value = "新增穴位", module = "穴位管理", operationType = OperationType.INSERT)
+public Result<Void> add(@RequestBody Acupoint acupoint) {
+    // ...
+}
+```
+
+#### 8.3.2 日志切面
+
+位置:`com.aijiuyi.admin.common.aspect.LogAspect`
+
+记录内容:
+- 请求用户ID、用户名
+- 请求URL、参数、结果
+- 请求IP、操作模块、操作类型
+- 请求唯一ID(串联同一请求的所有日志)
+
+### 8.4 数据安全
+
+#### 8.4.1 逻辑删除
+
+所有数据表使用`deleted`字段实现逻辑删除:
+- 0 = 未删除
+- 1 = 已删除
+
+#### 8.4.2 敏感数据处理
+
+- 用户密码使用MD5加密存储
+- 密码字段在返回给前端前被置空
+
+---
+
+## 9. 部署说明
+
+### 9.1 环境要求
+
+| 组件 | 版本要求 |
+|------|----------|
+| JDK | 1.8+ |
+| MySQL | 8.0+ |
+| Redis | 6.0+ |
+| Node.js | 16.0+ |
+| Maven | 3.6+ |
+
+### 9.2 后端部署
+
+#### 9.2.1 数据库初始化
+
+```bash
+# 登录MySQL
+mysql -u root -p
+
+# 创建数据库
+CREATE DATABASE aijiuyi_admin DEFAULT CHARACTER SET utf8mb4;
+
+# 执行初始化脚本
+USE aijiuyi_admin;
+SOURCE /path/to/init.sql;
+```
+
+#### 9.2.2 配置文件
+
+修改`application-dev.yml`中的数据库和Redis连接信息:
+
+```yaml
+spring:
+  datasource:
+    url: jdbc:mysql://localhost:3306/aijiuyi_admin?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
+    username: root
+    password: your_password
+  redis:
+    host: localhost
+    port: 6379
+    password: your_redis_password
+```
+
+#### 9.2.3 构建部署
+
+```bash
+# 进入后端目录
+cd code/backend
+
+# 打包
+mvn clean package -DskipTests
+
+# 运行
+java -jar target/aijiuyi-admin-1.0.0.jar
+```
+
+### 9.3 前端部署
+
+#### 9.3.1 安装依赖
+
+```bash
+cd code/frontend
+npm install
+```
+
+#### 9.3.2 开发环境运行
+
+```bash
+npm run dev
+```
+
+#### 9.3.3 生产环境构建
+
+```bash
+npm run build
+```
+
+构建产物位于`dist`目录,可部署至Nginx等Web服务器。
+
+### 9.4 Nginx配置示例
+
+```nginx
+server {
+    listen 80;
+    server_name your-domain.com;
+
+    # 前端静态资源
+    location / {
+        root /path/to/frontend/dist;
+        try_files $uri $uri/ /index.html;
+    }
+
+    # 后端API代理
+    location /api {
+        proxy_pass http://localhost:18888;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+    }
+}
+```
+
+### 9.5 初始化账号
+
+系统初始化管理员账号:
+- 用户名:`admin`
+- 密码:`admin123`
+
+---
+
+## 附录
+
+### A. 常用端口
+
+| 服务 | 端口 |
+|------|------|
+| MySQL | 3306 |
+| Redis | 6379 |
+| 后端API | 18888 |
+| 前端开发服务器 | 5173 |
+
+### B. 默认数据
+
+系统初始化包含:
+- 1个超级管理员账号
+- 12个常用穴位
+- 5种艾灸手法
+- 9个默认艾灸方案
+
+### C. 相关文档
+
+- [智能穴位定位系统设计文档](./智能穴位定位系统设计文档.md)
+- [算法3.0说明](./算法3.0.md)
+- [穴位管理说明](./穴位管理.md)
+- [方案管理和用户方案管理](./方案管理和用户方案管理.md)
+- [用户管理说明](./用户管理.md)
+- [设备管理和用户设备管理](./设备管理和用户设备管理.md)
+- [用户穴位管理](./用户穴位管理.md)
+- [艾灸手法管理](./艾灸手法管理.md)
+- [管理员管理](./管理员管理.md)
+- [日志管理](./日志管理.md)
+- [规范说明](./规范说明.md)
+
+---
+
+*文档版本:v1.0*
+*最后更新:2026-04-28*