liujie
5 天以前 3b64bcc08e7103006599278f7fe3d8cdeebf30b5
init
17个文件已添加
13个文件已修改
1627 ■■■■■ 已修改文件
README.md 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/login.js 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/menu.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/monitor/job.js 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/monitor/jobLog.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/monitor/logininfor.js 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/monitor/online.js 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/monitor/operlog.js 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/tool/gen.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/api/user/account.js 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/store/modules/user.js 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/src/views/user/account/index.vue 359 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-admin/vite.config.js 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/bin/start-all.bat 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/bin/stop-all.bat 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-gateway/src/main/resources/bootstrap.yml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/pom.xml 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/pom.xml 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/RuoYiUserApplication.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountUserController.java 131 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/banner.txt 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/bootstrap.yml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/logback.xml 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/sql/hugray_account_20261009.sql 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
hugray-server/sql/hugray_menu_account_20261009.sql 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md
@@ -47,11 +47,12 @@
| 模块 | 目录 | 说明 | 端口 |
| --- | --- | --- | --- |
| 网关 | `ruoyi-gateway` | 统一入口、路由转发、鉴权与限流 | 8080 |
| 网关 | `ruoyi-gateway` | 统一入口、路由转发、鉴权与限流 | 8085 |
| 认证服务 | `ruoyi-auth` | 登录、注册、令牌签发 | 9200 |
| 系统服务 | `ruoyi-modules/ruoyi-system` | 用户 / 角色 / 菜单 / 部门 / 字典 / 日志等 | 9201 |
| 代码生成 | `ruoyi-modules/ruoyi-gen` | 前后端代码一键生成 | 9202 |
| 定时任务 | `ruoyi-modules/ruoyi-job` | Quartz 任务调度 | 9203 |
| 用户服务 | `ruoyi-modules/ruoyi-user` | C 端会员用户(`account_user`,网关前缀 `/account`) | 9210 |
| 文件服务 | `ruoyi-modules/ruoyi-file` | 本地 / MinIO 文件上传下载 | 9300 |
| 监控服务 | `ruoyi-visual/ruoyi-monitor` | Spring Boot Admin 服务监控 | 9100 |
@@ -72,10 +73,14 @@
- `docker/`:Nacos、MySQL、Redis、Nginx 及各服务的容器化配置,编排文件为 `docker/docker-compose.yml`
- `docker/deploy.sh`、`docker/copy.sh`:一键部署与拷贝脚本
- `bin/`:各服务的本地启动脚本(`run-gateway.bat`、`run-auth.bat`、`run-modules-*.bat`、`run-monitor.bat`)
- `bin/`:本地启动脚本
    - `start-all.bat`、`stop-all.bat`:一键启动/停止 Nacos、Redis 与全部服务(Windows)
    - `run-gateway.bat`、`run-auth.bat`、`run-modules-*.bat`、`run-monitor.bat`:单服务前台启动
- `sql/`:初始化脚本
    - `ry_20260417.sql`:业务库(`ry-cloud`)
    - `ry_config_20260918.sql`:Nacos 配置库
    - `hugray_account_20261009.sql`:会员库 `hugray-account` 的 `account_user` 表
    - `hugray_menu_account_20261009.sql`:用户服务菜单(`hugray` 库 `sys_menu`)
    - `quartz.sql`:定时任务表
    - `ry_seata_20210128.sql`:Seata 事务表
@@ -118,6 +123,8 @@
ruoyi-gateway → ruoyi-auth → ruoyi-modules/ruoyi-system → ... → ruoyi-visual/ruoyi-monitor
```
Windows 下可直接双击 `hugray-server/bin/start-all.bat`:脚本会自动拉起 Nacos、Redis,再依次启动全部 8 个服务并等待网关就绪;端口已被占用的服务自动跳过,因此可重复执行。停止用 `stop-all.bat`(Redis 作为常驻中间件不在此列)。两者的路径配置都在文件顶部的「配置区」,换机器时改那里即可。
### 4. 启动管理后台前端
```bash
@@ -136,11 +143,12 @@
| 服务 | 端口 |
| --- | --- |
| 管理后台前端(开发) | 80 |
| 网关 ruoyi-gateway | 8080 |
| 网关 ruoyi-gateway | 8085 |
| 认证 ruoyi-auth | 9200 |
| 系统 ruoyi-system | 9201 |
| 代码生成 ruoyi-gen | 9202 |
| 定时任务 ruoyi-job | 9203 |
| 用户服务 ruoyi-user | 9210 |
| 文件服务 ruoyi-file | 9300 |
| 监控 ruoyi-monitor | 9100 |
| Nacos | 8848 / 9848 / 9849 |
hugray-admin/src/api/login.js
@@ -1,5 +1,9 @@
import request from '@/utils/request'
// 注意:本前端对接的是 hugray-server 微服务网关,路径必须带云网关前缀
// (校验码由网关自身提供 /code,认证走 /auth/**,用户与菜单走 /system/**),
// 不能使用 RuoYi 单体版的 /login、/getInfo、/getRouters 等写法。
// 登录方法
export function login(username, password, code, uuid) {
  const data = {
@@ -9,7 +13,7 @@
    uuid
  }
  return request({
    url: '/login',
    url: '/auth/login',
    headers: {
      isToken: false,
      repeatSubmit: false
@@ -22,7 +26,7 @@
// 注册方法
export function register(data) {
  return request({
    url: '/register',
    url: '/auth/register',
    headers: {
      isToken: false
    },
@@ -34,7 +38,7 @@
// 获取用户详细信息
export function getInfo() {
  return request({
    url: '/getInfo',
    url: '/system/user/getInfo',
    method: 'get'
  })
}
@@ -42,7 +46,7 @@
// 解锁屏幕
export function unlockScreen(password) {
  return request({
    url: '/unlockscreen',
    url: '/auth/unlockscreen',
    method: 'post',
    data: { password }
  })
@@ -51,15 +55,15 @@
// 退出方法
export function logout() {
  return request({
    url: '/logout',
    method: 'post'
    url: '/auth/logout',
    method: 'delete'
  })
}
// 获取验证码
export function getCodeImg() {
  return request({
    url: '/captchaImage',
    url: '/code',
    headers: {
      isToken: false
    },
hugray-admin/src/api/menu.js
@@ -3,7 +3,7 @@
// 获取路由
export const getRouters = () => {
  return request({
    url: '/getRouters',
    url: '/system/menu/getRouters',
    method: 'get'
  })
}
hugray-admin/src/api/monitor/job.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询定时任务调度列表
export function listJob(query) {
  return request({
    url: '/monitor/job/list',
    url: '/schedule/job/list',
    method: 'get',
    params: query
  })
@@ -12,7 +13,7 @@
// 查询定时任务调度详细
export function getJob(jobId) {
  return request({
    url: '/monitor/job/' + jobId,
    url: '/schedule/job/' + jobId,
    method: 'get'
  })
}
@@ -20,7 +21,7 @@
// 新增定时任务调度
export function addJob(data) {
  return request({
    url: '/monitor/job',
    url: '/schedule/job',
    method: 'post',
    data: data
  })
@@ -29,7 +30,7 @@
// 修改定时任务调度
export function updateJob(data) {
  return request({
    url: '/monitor/job',
    url: '/schedule/job',
    method: 'put',
    data: data
  })
@@ -38,7 +39,7 @@
// 删除定时任务调度
export function delJob(jobId) {
  return request({
    url: '/monitor/job/' + jobId,
    url: '/schedule/job/' + jobId,
    method: 'delete'
  })
}
@@ -50,7 +51,7 @@
    status
  }
  return request({
    url: '/monitor/job/changeStatus',
    url: '/schedule/job/changeStatus',
    method: 'put',
    data: data
  })
@@ -64,7 +65,7 @@
    jobGroup
  }
  return request({
    url: '/monitor/job/run',
    url: '/schedule/job/run',
    method: 'put',
    data: data
  })
hugray-admin/src/api/monitor/jobLog.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询调度日志列表
export function listJobLog(query) {
  return request({
    url: '/monitor/jobLog/list',
    url: '/schedule/job/log/list',
    method: 'get',
    params: query
  })
@@ -12,7 +13,7 @@
// 删除调度日志
export function delJobLog(jobLogId) {
  return request({
    url: '/monitor/jobLog/' + jobLogId,
    url: '/schedule/job/log/' + jobLogId,
    method: 'delete'
  })
}
@@ -20,7 +21,7 @@
// 清空调度日志
export function cleanJobLog() {
  return request({
    url: '/monitor/jobLog/clean',
    url: '/schedule/job/log/clean',
    method: 'delete'
  })
}
hugray-admin/src/api/monitor/logininfor.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询登录日志列表
export function list(query) {
  return request({
    url: '/monitor/logininfor/list',
    url: '/system/logininfor/list',
    method: 'get',
    params: query
  })
@@ -12,7 +13,7 @@
// 删除登录日志
export function delLogininfor(infoId) {
  return request({
    url: '/monitor/logininfor/' + infoId,
    url: '/system/logininfor/' + infoId,
    method: 'delete'
  })
}
@@ -20,7 +21,7 @@
// 解锁用户登录状态
export function unlockLogininfor(userName) {
  return request({
    url: '/monitor/logininfor/unlock/' + userName,
    url: '/system/logininfor/unlock/' + userName,
    method: 'get'
  })
}
@@ -28,7 +29,7 @@
// 清空登录日志
export function cleanLogininfor() {
  return request({
    url: '/monitor/logininfor/clean',
    url: '/system/logininfor/clean',
    method: 'delete'
  })
}
hugray-admin/src/api/monitor/online.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询在线用户列表
export function list(query) {
  return request({
    url: '/monitor/online/list',
    url: '/system/online/list',
    method: 'get',
    params: query
  })
@@ -12,7 +13,7 @@
// 强退用户
export function forceLogout(tokenId) {
  return request({
    url: '/monitor/online/' + tokenId,
    url: '/system/online/' + tokenId,
    method: 'delete'
  })
}
hugray-admin/src/api/monitor/operlog.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询操作日志列表
export function list(query) {
  return request({
    url: '/monitor/operlog/list',
    url: '/system/operlog/list',
    method: 'get',
    params: query
  })
@@ -12,7 +13,7 @@
// 删除操作日志
export function delOperlog(operId) {
  return request({
    url: '/monitor/operlog/' + operId,
    url: '/system/operlog/' + operId,
    method: 'delete'
  })
}
@@ -20,7 +21,7 @@
// 清空操作日志
export function cleanOperlog() {
  return request({
    url: '/monitor/operlog/clean',
    url: '/system/operlog/clean',
    method: 'delete'
  })
}
hugray-admin/src/api/tool/gen.js
@@ -1,9 +1,10 @@
import request from '@/utils/request'
// 注意:本前端对接 hugray-server 云网关,路径与 RuoYi 单体版不同。
// 查询生成表数据
export function listTable(query) {
  return request({
    url: '/tool/gen/list',
    url: '/code/gen/list',
    method: 'get',
    params: query
  })
@@ -11,7 +12,7 @@
// 查询db数据库列表
export function listDbTable(query) {
  return request({
    url: '/tool/gen/db/list',
    url: '/code/gen/db/list',
    method: 'get',
    params: query
  })
@@ -20,7 +21,7 @@
// 查询表详细信息
export function getGenTable(tableId) {
  return request({
    url: '/tool/gen/' + tableId,
    url: '/code/gen/' + tableId,
    method: 'get'
  })
}
@@ -28,7 +29,7 @@
// 修改代码生成信息
export function updateGenTable(data) {
  return request({
    url: '/tool/gen',
    url: '/code/gen',
    method: 'put',
    data: data
  })
@@ -37,7 +38,7 @@
// 导入表
export function importTable(data) {
  return request({
    url: '/tool/gen/importTable',
    url: '/code/gen/importTable',
    method: 'post',
    params: data
  })
@@ -46,7 +47,7 @@
// 创建表
export function createTable(data) {
  return request({
    url: '/tool/gen/createTable',
    url: '/code/gen/createTable',
    method: 'post',
    params: data
  })
@@ -55,7 +56,7 @@
// 预览生成代码
export function previewTable(tableId) {
  return request({
    url: '/tool/gen/preview/' + tableId,
    url: '/code/gen/preview/' + tableId,
    method: 'get'
  })
}
@@ -63,7 +64,7 @@
// 删除表数据
export function delTable(tableId) {
  return request({
    url: '/tool/gen/' + tableId,
    url: '/code/gen/' + tableId,
    method: 'delete'
  })
}
@@ -71,7 +72,7 @@
// 生成代码(自定义路径)
export function genCode(tableName) {
  return request({
    url: '/tool/gen/genCode/' + tableName,
    url: '/code/gen/genCode/' + tableName,
    method: 'get'
  })
}
@@ -79,7 +80,7 @@
// 同步数据库
export function synchDb(tableName) {
  return request({
    url: '/tool/gen/synchDb/' + tableName,
    url: '/code/gen/synchDb/' + tableName,
    method: 'get'
  })
}
hugray-admin/src/api/user/account.js
New file
@@ -0,0 +1,48 @@
import request from '@/utils/request'
// 注意:本前端对接的是 hugray-server 云网关,会员服务挂在 /account 前缀下
// (网关 StripPrefix=1 剥掉 /account 后落到 ruoyi-user 服务的 /user/**),
// 不能照抄 RuoYi 单体版的写法。
// 查询会员列表
export function listUser(query) {
  return request({
    url: '/account/user/list',
    method: 'get',
    params: query
  })
}
// 查询会员详细
export function getUser(userId) {
  return request({
    url: '/account/user/' + userId,
    method: 'get'
  })
}
// 新增会员
export function addUser(data) {
  return request({
    url: '/account/user',
    method: 'post',
    data: data
  })
}
// 修改会员
export function updateUser(data) {
  return request({
    url: '/account/user',
    method: 'put',
    data: data
  })
}
// 删除会员
export function delUser(userId) {
  return request({
    url: '/account/user/' + userId,
    method: 'delete'
  })
}
hugray-admin/src/store/modules/user.js
@@ -28,8 +28,8 @@
        const uuid = userInfo.uuid
        return new Promise((resolve, reject) => {
          login(username, password, code, uuid).then(res => {
            setToken(res.token)
            this.token = res.token
            setToken(res.data.access_token)
            this.token = res.data.access_token
            useLockStore().unlockScreen()
            resolve()
          }).catch(error => {
hugray-admin/src/views/user/account/index.vue
New file
@@ -0,0 +1,359 @@
<template>
   <div class="app-container">
      <el-form :model="queryParams" ref="queryRef" :inline="true" v-show="showSearch">
         <el-form-item label="用户名" prop="userName">
            <el-input
               v-model="queryParams.userName"
               placeholder="请输入用户名"
               clearable
               style="width: 200px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="手机号" prop="phone">
            <el-input
               v-model="queryParams.phone"
               placeholder="请输入手机号"
               clearable
               style="width: 200px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="昵称" prop="nickname">
            <el-input
               v-model="queryParams.nickname"
               placeholder="请输入昵称"
               clearable
               style="width: 200px"
               @keyup.enter="handleQuery"
            />
         </el-form-item>
         <el-form-item label="状态" prop="status">
            <el-select v-model="queryParams.status" placeholder="会员状态" clearable style="width: 200px">
               <el-option
                  v-for="dict in sys_normal_disable"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
               />
            </el-select>
         </el-form-item>
         <el-form-item label="创建时间" style="width: 308px">
            <el-date-picker
               v-model="dateRange"
               value-format="YYYY-MM-DD"
               type="daterange"
               range-separator="-"
               start-placeholder="开始日期"
               end-placeholder="结束日期"
            ></el-date-picker>
         </el-form-item>
         <el-form-item>
            <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
            <el-button icon="Refresh" @click="resetQuery">重置</el-button>
         </el-form-item>
      </el-form>
      <el-row :gutter="10" class="mb8">
         <el-col :span="1.5">
            <el-button
               type="primary"
               plain
               icon="Plus"
               @click="handleAdd"
               v-hasPermi="['account:user:add']"
            >新增</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="success"
               plain
               icon="Edit"
               :disabled="single"
               @click="handleUpdate"
               v-hasPermi="['account:user:edit']"
            >修改</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="danger"
               plain
               icon="Delete"
               :disabled="multiple"
               @click="handleDelete"
               v-hasPermi="['account:user:remove']"
            >删除</el-button>
         </el-col>
         <el-col :span="1.5">
            <el-button
               type="warning"
               plain
               icon="Download"
               @click="handleExport"
               v-hasPermi="['account:user:export']"
            >导出</el-button>
         </el-col>
         <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
      </el-row>
      <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange">
         <el-table-column type="selection" width="55" align="center" />
         <el-table-column label="会员ID" align="center" prop="userId" width="90" />
         <el-table-column label="用户名" align="center" prop="userName" :show-overflow-tooltip="true" />
         <el-table-column label="手机号" align="center" prop="phone" width="130" />
         <el-table-column label="昵称" align="center" prop="nickname" :show-overflow-tooltip="true" />
         <el-table-column label="性别" align="center" prop="gender" width="80">
            <template #default="scope">
               <dict-tag :options="sys_user_sex" :value="scope.row.gender" />
            </template>
         </el-table-column>
         <el-table-column label="状态" align="center" prop="status" width="80">
            <template #default="scope">
               <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
            </template>
         </el-table-column>
         <el-table-column label="创建时间" align="center" prop="createTime" width="180">
            <template #default="scope">
               <span>{{ parseTime(scope.row.createTime) }}</span>
            </template>
         </el-table-column>
         <el-table-column label="操作" width="180" align="center" class-name="small-padding fixed-width">
            <template #default="scope">
               <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['account:user:edit']">修改</el-button>
               <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['account:user:remove']">删除</el-button>
            </template>
         </el-table-column>
      </el-table>
      <pagination
         v-show="total > 0"
         :total="total"
         v-model:page="queryParams.pageNum"
         v-model:limit="queryParams.pageSize"
         @pagination="getList"
      />
      <!-- 添加或修改会员对话框 -->
      <el-dialog :title="title" v-model="open" width="600px" append-to-body>
         <el-form ref="userRef" :model="form" :rules="rules" label-width="80px">
            <el-form-item label="用户名" prop="userName">
               <el-input v-model="form.userName" placeholder="请输入用户名" maxlength="64" />
            </el-form-item>
            <el-form-item label="手机号" prop="phone">
               <el-input v-model="form.phone" placeholder="请输入手机号" maxlength="20" />
            </el-form-item>
            <el-form-item label="密码" prop="password">
               <el-input
                  v-model="form.password"
                  type="password"
                  show-password
                  maxlength="20"
                  :placeholder="form.userId ? '留空则不修改密码' : '请输入密码'"
               />
            </el-form-item>
            <el-form-item label="昵称" prop="nickname">
               <el-input v-model="form.nickname" placeholder="请输入昵称" maxlength="64" />
            </el-form-item>
            <el-form-item label="头像地址" prop="avatar">
               <el-input v-model="form.avatar" placeholder="请输入头像图片地址" maxlength="255" />
            </el-form-item>
            <el-form-item label="性别" prop="gender">
               <el-radio-group v-model="form.gender">
                  <el-radio
                     v-for="dict in sys_user_sex"
                     :key="dict.value"
                     :value="dict.value"
                  >{{ dict.label }}</el-radio>
               </el-radio-group>
            </el-form-item>
            <el-form-item label="生日" prop="birthday">
               <el-date-picker
                  v-model="form.birthday"
                  type="date"
                  value-format="YYYY-MM-DD"
                  placeholder="选择日期"
                  style="width: 100%"
               />
            </el-form-item>
            <el-form-item label="状态" prop="status">
               <el-radio-group v-model="form.status">
                  <el-radio
                     v-for="dict in sys_normal_disable"
                     :key="dict.value"
                     :value="dict.value"
                  >{{ dict.label }}</el-radio>
               </el-radio-group>
            </el-form-item>
            <el-form-item label="备注" prop="remark">
               <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" maxlength="500" />
            </el-form-item>
         </el-form>
         <template #footer>
            <div class="dialog-footer">
               <el-button type="primary" @click="submitForm">确 定</el-button>
               <el-button @click="cancel">取 消</el-button>
            </div>
         </template>
      </el-dialog>
   </div>
</template>
<script setup name="AccountUser">
import { listUser, addUser, delUser, getUser, updateUser } from "@/api/user/account"
const { proxy } = getCurrentInstance()
const { sys_user_sex, sys_normal_disable } = useDict("sys_user_sex", "sys_normal_disable")
const userList = ref([])
const open = ref(false)
const loading = ref(true)
const showSearch = ref(true)
const ids = ref([])
const single = ref(true)
const multiple = ref(true)
const total = ref(0)
const title = ref("")
const dateRange = ref([])
const data = reactive({
  form: {},
  queryParams: {
    pageNum: 1,
    pageSize: 10,
    userName: undefined,
    phone: undefined,
    nickname: undefined,
    status: undefined
  },
  rules: {
    userName: [{ required: true, message: "用户名不能为空", trigger: "blur" }],
    // 新增时密码必填;修改时留空表示不改,故用自定义校验
    password: [{
      validator: (rule, value, callback) => {
        if (!form.value.userId && !value) {
          callback(new Error("密码不能为空"))
        } else {
          callback()
        }
      },
      trigger: "blur"
    }]
  }
})
const { queryParams, form, rules } = toRefs(data)
/** 查询会员列表 */
function getList() {
  loading.value = true
  listUser(proxy.addDateRange(queryParams.value, dateRange.value)).then(response => {
    userList.value = response.rows
    total.value = response.total
    loading.value = false
  })
}
/** 取消按钮 */
function cancel() {
  open.value = false
  reset()
}
/** 表单重置 */
function reset() {
  form.value = {
    userId: undefined,
    userName: undefined,
    phone: undefined,
    password: undefined,
    nickname: undefined,
    avatar: undefined,
    gender: "2",
    birthday: undefined,
    status: "0",
    remark: undefined
  }
  proxy.resetForm("userRef")
}
/** 搜索按钮操作 */
function handleQuery() {
  queryParams.value.pageNum = 1
  getList()
}
/** 重置按钮操作 */
function resetQuery() {
  dateRange.value = []
  proxy.resetForm("queryRef")
  handleQuery()
}
/** 多选框选中数据 */
function handleSelectionChange(selection) {
  ids.value = selection.map(item => item.userId)
  single.value = selection.length != 1
  multiple.value = !selection.length
}
/** 新增按钮操作 */
function handleAdd() {
  reset()
  open.value = true
  title.value = "添加会员"
}
/** 修改按钮操作 */
function handleUpdate(row) {
  reset()
  const userId = row.userId || ids.value
  getUser(userId).then(response => {
    form.value = response.data
    // 接口不回传密码,避免把 hash 回填到输入框后又被当成新密码提交
    form.value.password = undefined
    open.value = true
    title.value = "修改会员"
  })
}
/** 提交按钮 */
function submitForm() {
  proxy.$refs["userRef"].validate(valid => {
    if (valid) {
      if (form.value.userId != undefined) {
        updateUser(form.value).then(() => {
          proxy.$modal.msgSuccess("修改成功")
          open.value = false
          getList()
        })
      } else {
        addUser(form.value).then(() => {
          proxy.$modal.msgSuccess("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}
/** 删除按钮操作 */
function handleDelete(row) {
  const userIds = row.userId || ids.value
  proxy.$modal.confirm('是否确认删除会员编号为"' + userIds + '"的数据项?').then(function() {
    return delUser(userIds)
  }).then(() => {
    getList()
    proxy.$modal.msgSuccess("删除成功")
  }).catch(() => {})
}
/** 导出按钮操作 */
function handleExport() {
  proxy.download("account/user/export", {
    ...queryParams.value
  }, `account_user_${new Date().getTime()}.xlsx`)
}
getList()
</script>
hugray-admin/vite.config.js
@@ -2,7 +2,7 @@
import path from 'path'
import createVitePlugins from './vite/plugins'
const baseUrl = 'http://localhost:8080' // 后端接口
const baseUrl = 'http://localhost:8085' // 后端接口
// https://vitejs.dev/config/
export default defineConfig(({ mode, command }) => {
hugray-server/bin/start-all.bat
New file
@@ -0,0 +1,148 @@
@echo off
setlocal enabledelayedexpansion
rem ============================================================
rem  Hugray ±¾µØÒ»¼üÆô¶¯£ºNacos -> Redis -> 8 ¸ö΢·þÎñ
rem  ÒÑÕ¼ÓõĶ˿ڻáÌø¹ý£¬¿ÉÖØ¸´Ö´ÐУ¨Ãݵȣ©
rem ============================================================
rem ---------------------- ÅäÖÃÇø ----------------------
set "JAVA_HOME=D:\jdk-17.0.9"
set "NACOS_HOME=F:\nacos-3.0.2\nacos"
set "REDIS_HOME=C:\Users\Admin\Desktop\redis"
rem ----------------------------------------------------
set "SERVER_DIR=%~dp0.."
set "LOG_DIR=%SERVER_DIR%\logs"
set "JAVA=%JAVA_HOME%\bin\java.exe"
if not exist "%JAVA%" (
    echo [´íÎó] Î´ÕÒµ½ JDK£º%JAVA%
    echo        ÇëÐ޸ı¾½Å±¾¡¸ÅäÖÃÇø¡¹ÖеĠJAVA_HOME
    pause
    exit /b 1
)
cd /d "%SERVER_DIR%"
if not exist "%LOG_DIR%" md "%LOG_DIR%"
echo.
echo ============ Hugray ±¾µØ¿ª·¢»·¾³Æô¶¯ ============
echo  JDK  : %JAVA_HOME%
echo  ÈÕÖ¾ : %LOG_DIR%
echo ================================================
echo.
rem ---------- 1. Nacos ----------
call :port_in_use 8848
if not errorlevel 1 (
    echo [Ìø¹ý] Nacos ÒÑÔÚÔËÐÐ
) else (
    if not exist "%NACOS_HOME%\bin\startup.cmd" (
        echo [´íÎó] Î´ÕÒµ½ Nacos£º%NACOS_HOME%\bin\startup.cmd
        pause
        exit /b 1
    )
    echo [Æô¶¯] Nacos -^> %NACOS_HOME%
    rem ²»ÒªÓàstart + startup.cmd£ºstart »áÈÃ×Ó½ø³Ì¼Ì³Ð±¾½Å±¾±»Öض¨ÏòµÄ stdin£¨³£Îª nul£©£¬
    rem startup.cmd Ä©Î²µÄ pause Òò¶øÁ¢¼´·µ»Ø¡¢¿ØÖÆÌ¨ËæÖ®¹Ø±Õ£¬Nacos »á±»Á¬´øÉ±µô¡£
    rem ¸ÄÓàStart-Process£¬×Ó½ø³ÌÄõ½¶ÀÁ¢¿ØÖÆÌ¨£¬Nacos ÉúÃüÖÜÆÚÓë±¾½Å±¾½âñî¡£
    powershell -NoProfile -Command "Start-Process -FilePath '%NACOS_HOME%\bin\startup.cmd' -ArgumentList '-m','standalone' -WindowStyle Hidden"
)
echo [µÈ´ý] Nacos 8848 ¾ÍÐ÷ ...
call :wait_port 8848 120
if errorlevel 1 (
    echo [´íÎó] Nacos 120 ÃëÄÚδ¾ÍÐ÷£¬Çë²é¿´ %NACOS_HOME%\logs
    pause
    exit /b 1
)
echo [¾ÍÐ÷] Nacos
rem ---------- 2. Redis ----------
call :port_in_use 6379
if not errorlevel 1 (
    echo [Ìø¹ý] Redis ÒÑÔÚÔËÐÐ
) else (
    if not exist "%REDIS_HOME%\redis-server.exe" (
        echo [¾¯¸æ] Î´ÕÒµ½ Redis£º%REDIS_HOME%\redis-server.exe£¬Ìø¹ý
    ) else (
        echo [Æô¶¯] Redis -^> %REDIS_HOME%
        powershell -NoProfile -Command "Start-Process -FilePath '%REDIS_HOME%\redis-server.exe' -ArgumentList '%REDIS_HOME%\redis.windows.conf' -WorkingDirectory '%REDIS_HOME%' -WindowStyle Hidden"
        call :wait_port 6379 20
        if errorlevel 1 echo [¾¯¸æ] Redis Æô¶¯½ÏÂý£¬ÇëÉÔºóÈ·ÈÏ
    )
)
rem ---------- 3. Î¢·þÎñ£¨gateway ·Å×îºó£¬µÈËüÆðÀ´¼´¿ÉÅж¨ÕûÌå¾ÍÐ÷£© ----------
echo.
echo ---------- Æô¶¯Î¢·þÎñ ----------
call :start_service "ruoyi-auth"    "ruoyi-auth"                 "ruoyi-auth.jar"            9200
call :start_service "ruoyi-system"  "ruoyi-modules\ruoyi-system" "ruoyi-modules-system.jar"  9201
call :start_service "ruoyi-gen"     "ruoyi-modules\ruoyi-gen"    "ruoyi-modules-gen.jar"     9202
call :start_service "ruoyi-job"     "ruoyi-modules\ruoyi-job"    "ruoyi-modules-job.jar"     9203
call :start_service "ruoyi-file"    "ruoyi-modules\ruoyi-file"   "ruoyi-modules-file.jar"    9300
call :start_service "ruoyi-user"    "ruoyi-modules\ruoyi-user"  "ruoyi-modules-user.jar"    9210
call :start_service "ruoyi-monitor" "ruoyi-visual\ruoyi-monitor" "ruoyi-visual-monitor.jar"  9100
call :start_service "ruoyi-gateway" "ruoyi-gateway"              "ruoyi-gateway.jar"         8085
echo.
echo ---------- µÈ´ýÍø¹Ø¾ÍÐ÷ ----------
call :wait_port 8085 120
if errorlevel 1 (
    echo [¾¯¸æ] Íø¹Ø 8085 Î´ÔÚ 120 ÃëÄÚ¾ÍÐ÷£¬Çë²é¿´ %LOG_DIR%\ruoyi-gateway.err.log
) else (
    echo [¾ÍÐ÷] Íø¹Ø http://localhost:8085
)
echo.
echo ============ Æô¶¯Íê³É ============
echo  Nacos ¿ØÖÆÌ¨ : http://localhost:8082
echo  Íø¹Ø         : http://localhost:8085
echo  ¹ÜÀíºǫ́     : ¼û hugray-admin Ä¿Â¼£¨npm run dev£©
echo  Í£Ö¹È«²¿     : bin\stop-all.bat
echo ================================
echo.
pause
exit /b 0
rem ============================================================
rem  ×Ó¹ý³Ì
rem ============================================================
rem :port_in_use <¶Ë¿Ú>   -- errorlevel 0 ±íʾ¶Ë¿ÚÒѱ»¼àÌý
:port_in_use
netstat -an | findstr /C:":%~1 " | findstr /C:"LISTENING" >nul 2>&1
exit /b %errorlevel%
rem :wait_port <¶Ë¿Ú> <³¬Ê±ÃëÊý>  -- errorlevel 0 ±íʾÒѾÍÐ÷
:wait_port
set /a _waited=0
:wait_port_loop
call :port_in_use %~1
if not errorlevel 1 exit /b 0
set /a _waited+=1
if !_waited! GEQ %~2 exit /b 1
rem ÑÓʱÓàping£ºtimeout ÔÚ stdin ±»Öض¨Ïòʱ»áÖ±½ÓÍ˳ö£¬¿¿²»×¡£»
rem ¼Ó¾ø¶Ô·¾¶ÊÇΪÁ˱ÜÃâ Git Bash µÈ»·¾³°ÑͬÃû¹¤¾ß»ì½ø PATH
"%SystemRoot%\System32\ping.exe" -n 2 -w 1000 127.0.0.1 >nul
goto :wait_port_loop
rem :start_service <Ãû³Æ> <Ä£¿éÏà¶ÔĿ¼> <jar ÎļþÃû> <¶Ë¿Ú>
:start_service
call :port_in_use %~4
if not errorlevel 1 (
    echo   [Ìø¹ý] %~1 - ¶Ë¿Ú %~4 Òѱ»Õ¼ÓÃ
    exit /b 0
)
set "_dir=%SERVER_DIR%\%~2\target"
set "_jar=%_dir%\%~3"
if not exist "%_jar%" (
    echo   [´íÎó] %~1 - Î´ÕÒµ½ %_jar%
    echo          ÇëÏÈÖ´ÐРbin\package.bat ¹¹½¨
    exit /b 1
)
echo   [Æô¶¯] %~1 - ¶Ë¿Ú %~4
powershell -NoProfile -Command "Start-Process -FilePath '%JAVA%' -ArgumentList '-Dfile.encoding=UTF-8','-Xms512m','-Xmx1024m','-XX:MetaspaceSize=128m','-XX:MaxMetaspaceSize=512m','-jar','%_jar%' -WorkingDirectory '%_dir%' -RedirectStandardOutput '%LOG_DIR%\%~1.out.log' -RedirectStandardError '%LOG_DIR%\%~1.err.log' -WindowStyle Hidden"
exit /b 0
hugray-server/bin/stop-all.bat
New file
@@ -0,0 +1,33 @@
@echo off
setlocal enabledelayedexpansion
rem ============================================================
rem  Hugray ±¾µØÒ»¼üÍ£Ö¹£ºÎ¢·þÎñ + Nacos
rem  Redis Îª³£×¤Öмä¼þ£¬²»ÔÚ±¾½Å±¾Í£Ö¹·¶Î§ÄÚ
rem ============================================================
echo.
echo ============ Hugray ±¾µØ¿ª·¢»·¾³Í£Ö¹ ============
echo.
rem ÏÈÍ£Íø¹Ø£¬ÔÙÍ£ÒµÎñ·þÎñ£¬×îºóÍ£ Nacos
for %%p in (8085 9200 9201 9202 9203 9210 9300 9100 8848) do (
    call :kill_port %%p
)
echo.
echo ============ Í£Ö¹Íê³É ============
echo  Redis ÈÔÔÚÔËÐУ¨ÈçÐèÍ£Ö¹ÇëÊÖ¶¯½áÊø½ø³Ì£©
echo ================================
echo.
pause
exit /b 0
rem :kill_port <¶Ë¿Ú>  -- ½áÊø¼àÌý¸Ã¶Ë¿ÚµÄ½ø³Ì
:kill_port
for /f "tokens=5" %%a in ('netstat -ano ^| findstr /C:":%~1 " ^| findstr /C:"LISTENING"') do (
    echo [Í£Ö¹] ¶Ë¿Ú %~1 - PID %%a
    taskkill /F /PID %%a >nul 2>&1
)
exit /b 0
hugray-server/ruoyi-gateway/src/main/resources/bootstrap.yml
@@ -1,6 +1,6 @@
# Tomcat
server:
  port: 8080
  port: 8085
# Spring
spring: 
hugray-server/ruoyi-modules/pom.xml
@@ -13,6 +13,7 @@
        <module>ruoyi-gen</module>
        <module>ruoyi-job</module>
        <module>ruoyi-file</module>
        <module>ruoyi-user</module>
    </modules>
    <artifactId>ruoyi-modules</artifactId>
hugray-server/ruoyi-modules/ruoyi-user/pom.xml
New file
@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.ruoyi</groupId>
        <artifactId>ruoyi-modules</artifactId>
        <version>3.6.8</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>ruoyi-modules-user</artifactId>
    <description>
        ruoyi-modules-user用户服务(C端会员)
    </description>
    <dependencies>
        <!-- SpringCloud Alibaba Nacos -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <!-- SpringCloud Alibaba Nacos Config -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>
        <!-- SpringCloud Alibaba Sentinel -->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
        </dependency>
        <!-- SpringBoot Actuator -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <!-- Mysql Connector -->
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
        </dependency>
        <!-- RuoYi Common DataSource -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-datasource</artifactId>
        </dependency>
        <!-- RuoYi Common DataScope -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-datascope</artifactId>
        </dependency>
        <!-- RuoYi Common Log -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-log</artifactId>
        </dependency>
        <!-- RuoYi Common Swagger -->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common-swagger</artifactId>
        </dependency>
    </dependencies>
    <!-- finalName 固定为 artifactId,jar 名必须与 bin/start-all.bat 里的写法一致 -->
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/RuoYiUserApplication.java
New file
@@ -0,0 +1,32 @@
package com.ruoyi.user;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import com.ruoyi.common.security.annotation.EnableCustomConfig;
import com.ruoyi.common.security.annotation.EnableRyFeignClients;
/**
 * 用户服务(C端会员)
 *
 * @author ruoyi
 */
@EnableCustomConfig
@EnableRyFeignClients
@SpringBootApplication
public class RuoYiUserApplication
{
    public static void main(String[] args)
    {
        SpringApplication.run(RuoYiUserApplication.class, args);
        System.out.println("(♥◠‿◠)ノ゙  用户服务启动成功   ლ(´ڡ`ლ)゙  \n" +
                " .-------.       ____     __        \n" +
                " |  _ _   \\      \\   \\   /  /    \n" +
                " | ( ' )  |       \\  _. /  '       \n" +
                " |(_ o _) /        _( )_ .'         \n" +
                " | (_,_).' __  ___(_ o _)'          \n" +
                " |  |\\ \\  |  ||   |(_,_)'         \n" +
                " |  | \\ `'   /|   `-'  /           \n" +
                " |  |  \\    /  \\      /           \n" +
                " ''-'   `'-'    `-..-'              ");
    }
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/controller/AccountUserController.java
New file
@@ -0,0 +1,131 @@
package com.ruoyi.user.controller;
import java.util.List;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.poi.ExcelUtil;
import com.ruoyi.common.core.web.controller.BaseController;
import com.ruoyi.common.core.web.domain.AjaxResult;
import com.ruoyi.common.core.web.page.TableDataInfo;
import com.ruoyi.common.log.annotation.Log;
import com.ruoyi.common.log.enums.BusinessType;
import com.ruoyi.common.security.annotation.RequiresPermissions;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.user.domain.AccountUser;
import com.ruoyi.user.service.IAccountUserService;
/**
 * 会员用户 信息操作处理
 *
 * 网关前缀 /account 由 StripPrefix=1 剥掉,故此处映射为 /user,
 * 外部实际路径是 /account/user/**。
 *
 * @author ruoyi
 */
@RestController
@RequestMapping("/user")
public class AccountUserController extends BaseController
{
    @Autowired
    private IAccountUserService accountUserService;
    /**
     * 查询会员列表
     */
    @RequiresPermissions("account:user:list")
    @GetMapping("/list")
    public TableDataInfo list(AccountUser accountUser)
    {
        startPage();
        List<AccountUser> list = accountUserService.selectAccountUserList(accountUser);
        return getDataTable(list);
    }
    /**
     * 导出会员列表
     */
    @RequiresPermissions("account:user:export")
    @Log(title = "会员管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, AccountUser accountUser)
    {
        List<AccountUser> list = accountUserService.selectAccountUserList(accountUser);
        ExcelUtil<AccountUser> util = new ExcelUtil<AccountUser>(AccountUser.class);
        util.exportExcel(response, list, "会员数据");
    }
    /**
     * 获取会员详细信息
     */
    @RequiresPermissions("account:user:query")
    @GetMapping(value = "/{userId}")
    public AjaxResult getInfo(@PathVariable Long userId)
    {
        return success(accountUserService.selectAccountUserById(userId));
    }
    /**
     * 新增会员
     */
    @RequiresPermissions("account:user:add")
    @Log(title = "会员管理", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@Validated @RequestBody AccountUser accountUser)
    {
        if (!accountUserService.checkUsernameUnique(accountUser))
        {
            return error("新增会员'" + accountUser.getUserName() + "'失败,用户名已存在");
        }
        if (!accountUserService.checkPhoneUnique(accountUser))
        {
            return error("新增会员'" + accountUser.getUserName() + "'失败,手机号已存在");
        }
        if (StringUtils.isEmpty(accountUser.getPassword()))
        {
            return error("密码不能为空");
        }
        accountUser.setCreateBy(SecurityUtils.getUsername());
        return toAjax(accountUserService.insertAccountUser(accountUser));
    }
    /**
     * 修改会员
     */
    @RequiresPermissions("account:user:edit")
    @Log(title = "会员管理", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@Validated @RequestBody AccountUser accountUser)
    {
        if (!accountUserService.checkUsernameUnique(accountUser))
        {
            return error("修改会员'" + accountUser.getUserName() + "'失败,用户名已存在");
        }
        if (!accountUserService.checkPhoneUnique(accountUser))
        {
            return error("修改会员'" + accountUser.getUserName() + "'失败,手机号已存在");
        }
        accountUser.setUpdateBy(SecurityUtils.getUsername());
        return toAjax(accountUserService.updateAccountUser(accountUser));
    }
    /**
     * 删除会员
     */
    @RequiresPermissions("account:user:remove")
    @Log(title = "会员管理", businessType = BusinessType.DELETE)
    @DeleteMapping("/{userIds}")
    public AjaxResult remove(@PathVariable Long[] userIds)
    {
        return toAjax(accountUserService.deleteAccountUserByIds(userIds));
    }
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/domain/AccountUser.java
New file
@@ -0,0 +1,193 @@
package com.ruoyi.user.domain;
import java.util.Date;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.ruoyi.common.core.annotation.Excel;
import com.ruoyi.common.core.annotation.Excel.ColumnType;
import com.ruoyi.common.core.web.domain.BaseEntity;
/**
 * C端会员用户表 account_user
 *
 * @author ruoyi
 */
public class AccountUser extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** 会员ID */
    @Excel(name = "会员ID", cellType = ColumnType.NUMERIC)
    private Long userId;
    /**
     * 用户名
     *
     * 注意:属性名刻意用驼峰 userName,与上游 SysUser.userName 一致。网关的 AuthFilter 会
     * 给每个转发请求加上请求头 username=<登录名>(SecurityConstants.DETAILS_USERNAME),
     * 而 Spring 6+ 的 @ModelAttribute 会把请求头一并绑定到命令对象——若此处叫 username,
     * 列表/导出接口在未显式传参时会被注入当前登录名,导致查询结果被静默过滤(实测列表恒为空)。
     */
    @Excel(name = "用户名")
    private String userName;
    /** 手机号 */
    @Excel(name = "手机号")
    private String phone;
    /** 密码(BCrypt 存储)。只入不出,避免密码随响应回传 */
    @JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
    private String password;
    /** 昵称 */
    @Excel(name = "昵称")
    private String nickname;
    /** 头像地址 */
    private String avatar;
    /** 性别(0男 1女 2未知,对齐 sys_user_sex 字典) */
    @Excel(name = "性别", readConverterExp = "0=男,1=女,2=未知")
    private String gender;
    /** 生日 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "生日", width = 30, dateFormat = "yyyy-MM-dd")
    private Date birthday;
    /** 状态(0正常 1停用) */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
    /** 删除标志(0存在 2删除) */
    private String delFlag;
    public Long getUserId()
    {
        return userId;
    }
    public void setUserId(Long userId)
    {
        this.userId = userId;
    }
    @NotBlank(message = "用户名不能为空")
    @Size(min = 0, max = 64, message = "用户名长度不能超过64个字符")
    public String getUserName()
    {
        return userName;
    }
    public void setUserName(String userName)
    {
        this.userName = userName;
    }
    @Size(min = 0, max = 20, message = "手机号长度不能超过20个字符")
    public String getPhone()
    {
        return phone;
    }
    public void setPhone(String phone)
    {
        this.phone = phone;
    }
    @Size(min = 0, max = 20, message = "密码长度必须在5到20个字符之间")
    public String getPassword()
    {
        return password;
    }
    public void setPassword(String password)
    {
        this.password = password;
    }
    @Size(min = 0, max = 64, message = "昵称长度不能超过64个字符")
    public String getNickname()
    {
        return nickname;
    }
    public void setNickname(String nickname)
    {
        this.nickname = nickname;
    }
    public String getAvatar()
    {
        return avatar;
    }
    public void setAvatar(String avatar)
    {
        this.avatar = avatar;
    }
    public String getGender()
    {
        return gender;
    }
    public void setGender(String gender)
    {
        this.gender = gender;
    }
    public Date getBirthday()
    {
        return birthday;
    }
    public void setBirthday(Date birthday)
    {
        this.birthday = birthday;
    }
    public String getStatus()
    {
        return status;
    }
    public void setStatus(String status)
    {
        this.status = status;
    }
    public String getDelFlag()
    {
        return delFlag;
    }
    public void setDelFlag(String delFlag)
    {
        this.delFlag = delFlag;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
            .append("userId", getUserId())
            .append("userName", getUserName())
            .append("phone", getPhone())
            .append("nickname", getNickname())
            .append("avatar", getAvatar())
            .append("gender", getGender())
            .append("birthday", getBirthday())
            .append("status", getStatus())
            .append("delFlag", getDelFlag())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .append("remark", getRemark())
            .toString();
    }
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/mapper/AccountUserMapper.java
New file
@@ -0,0 +1,52 @@
package com.ruoyi.user.mapper;
import java.util.List;
import com.ruoyi.user.domain.AccountUser;
/**
 * 会员用户 数据层
 *
 * @author ruoyi
 */
public interface AccountUserMapper
{
    /**
     * 根据会员ID查询
     */
    public AccountUser selectAccountUserById(Long userId);
    /**
     * 根据用户名查询(查重用)
     */
    public AccountUser selectAccountUserByUsername(String userName);
    /**
     * 根据手机号查询(查重用)
     */
    public AccountUser selectAccountUserByPhone(String phone);
    /**
     * 查询会员列表
     */
    public List<AccountUser> selectAccountUserList(AccountUser accountUser);
    /**
     * 新增会员
     */
    public int insertAccountUser(AccountUser accountUser);
    /**
     * 修改会员
     */
    public int updateAccountUser(AccountUser accountUser);
    /**
     * 逻辑删除会员
     */
    public int deleteAccountUserById(Long userId);
    /**
     * 批量逻辑删除会员
     */
    public int deleteAccountUserByIds(Long[] userIds);
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/IAccountUserService.java
New file
@@ -0,0 +1,49 @@
package com.ruoyi.user.service;
import java.util.List;
import com.ruoyi.user.domain.AccountUser;
/**
 * 会员用户 业务层
 *
 * @author ruoyi
 */
public interface IAccountUserService
{
    /**
     * 根据会员ID查询
     */
    public AccountUser selectAccountUserById(Long userId);
    /**
     * 查询会员列表
     */
    public List<AccountUser> selectAccountUserList(AccountUser accountUser);
    /**
     * 校验用户名是否唯一
     *
     * @return true 唯一,false 已存在
     */
    public boolean checkUsernameUnique(AccountUser accountUser);
    /**
     * 校验手机号是否唯一
     */
    public boolean checkPhoneUnique(AccountUser accountUser);
    /**
     * 新增会员(密码加密后落库)
     */
    public int insertAccountUser(AccountUser accountUser);
    /**
     * 修改会员(密码为空则不改密码)
     */
    public int updateAccountUser(AccountUser accountUser);
    /**
     * 批量逻辑删除会员
     */
    public int deleteAccountUserByIds(Long[] userIds);
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/java/com/ruoyi/user/service/impl/AccountUserServiceImpl.java
New file
@@ -0,0 +1,88 @@
package com.ruoyi.user.service.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.common.core.constant.UserConstants;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.security.utils.SecurityUtils;
import com.ruoyi.user.domain.AccountUser;
import com.ruoyi.user.mapper.AccountUserMapper;
import com.ruoyi.user.service.IAccountUserService;
/**
 * 会员用户 业务层实现
 *
 * @author ruoyi
 */
@Service
public class AccountUserServiceImpl implements IAccountUserService
{
    @Autowired
    private AccountUserMapper accountUserMapper;
    @Override
    public AccountUser selectAccountUserById(Long userId)
    {
        return accountUserMapper.selectAccountUserById(userId);
    }
    @Override
    public List<AccountUser> selectAccountUserList(AccountUser accountUser)
    {
        return accountUserMapper.selectAccountUserList(accountUser);
    }
    @Override
    public boolean checkUsernameUnique(AccountUser accountUser)
    {
        Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
        AccountUser info = accountUserMapper.selectAccountUserByUsername(accountUser.getUserName());
        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
        {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }
    @Override
    public boolean checkPhoneUnique(AccountUser accountUser)
    {
        // 手机号非必填,为空时不做唯一性校验,否则会与库里其它空手机号记录相撞
        if (StringUtils.isEmpty(accountUser.getPhone()))
        {
            return UserConstants.UNIQUE;
        }
        Long userId = StringUtils.isNull(accountUser.getUserId()) ? -1L : accountUser.getUserId();
        AccountUser info = accountUserMapper.selectAccountUserByPhone(accountUser.getPhone());
        if (StringUtils.isNotNull(info) && info.getUserId().longValue() != userId.longValue())
        {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }
    @Override
    public int insertAccountUser(AccountUser accountUser)
    {
        accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
        return accountUserMapper.insertAccountUser(accountUser);
    }
    @Override
    public int updateAccountUser(AccountUser accountUser)
    {
        // 不为空才加密;为空时保持 null,mapper 的 <if> 会跳过该列,原密码不受影响
        if (StringUtils.isNotEmpty(accountUser.getPassword()))
        {
            accountUser.setPassword(SecurityUtils.encryptPassword(accountUser.getPassword()));
        }
        return accountUserMapper.updateAccountUser(accountUser);
    }
    @Override
    public int deleteAccountUserByIds(Long[] userIds)
    {
        return accountUserMapper.deleteAccountUserByIds(userIds);
    }
}
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/banner.txt
New file
@@ -0,0 +1,10 @@
Spring Boot Version: ${spring-boot.version}
Spring Application Name: ${spring.application.name}
                            _
                           (_)
 _ __  _   _   ___   _   _  _  ______   __ _   ___  _ __
| '__|| | | | / _ \ | | | || ||______| / _` | / _ \| '_ \
| |   | |_| || (_) || |_| || |        | (_| ||  __/| | | |
|_|    \__,_| \___/  \__, ||_|         \__, | \___||_| |_|
                      __/ |             __/ |
                     |___/             |___/
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/bootstrap.yml
New file
@@ -0,0 +1,27 @@
# Tomcat
server:
  port: 9210
# Spring
spring:
  application:
    # 应用名称
    name: ruoyi-user
  profiles:
    # 环境配置
    active: dev
  cloud:
    nacos:
      discovery:
        # 服务注册地址
        server-addr: 127.0.0.1:8848
      config:
        # 配置中心地址
        server-addr: 127.0.0.1:8848
  config:
    # 配置文件格式
    file-extension: yml
    # 数据源、mybatis、springdoc 全在 Nacos,此处只声明要加载哪两份配置
    import:
      - nacos:application-${spring.profiles.active}.${spring.config.file-extension}
      - nacos:${spring.application.name}-${spring.profiles.active}.${spring.config.file-extension}
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/logback.xml
New file
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- 日志存放路径 -->
    <property name="log.path" value="logs/ruoyi-user" />
   <!-- 日志输出格式 -->
    <property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
    <!-- 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
    </appender>
    <!-- 系统日志输出 -->
    <appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/info.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>INFO</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${log.path}/error.log</file>
        <!-- 循环政策:基于时间创建日志文件 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 日志文件名格式 -->
            <fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
            <!-- 日志最大的历史 60天 -->
            <maxHistory>60</maxHistory>
        </rollingPolicy>
        <encoder>
            <pattern>${log.pattern}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <!-- 过滤的级别 -->
            <level>ERROR</level>
            <!-- 匹配时的操作:接收(记录) -->
            <onMatch>ACCEPT</onMatch>
            <!-- 不匹配时的操作:拒绝(不记录) -->
            <onMismatch>DENY</onMismatch>
        </filter>
    </appender>
    <!-- 系统模块日志级别控制  -->
    <logger name="com.ruoyi" level="info" />
    <!-- Spring日志级别控制  -->
    <logger name="org.springframework" level="warn" />
    <root level="info">
        <appender-ref ref="console" />
    </root>
    <!--系统操作日志-->
    <root level="info">
        <appender-ref ref="file_info" />
        <appender-ref ref="file_error" />
    </root>
</configuration>
hugray-server/ruoyi-modules/ruoyi-user/src/main/resources/mapper/user/AccountUserMapper.xml
New file
@@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.user.mapper.AccountUserMapper">
    <resultMap type="AccountUser" id="AccountUserResult">
        <id     property="userId"     column="user_id"     />
        <result property="userName"   column="username"    />
        <result property="phone"      column="phone"       />
        <result property="password"   column="password"    />
        <result property="nickname"   column="nickname"    />
        <result property="avatar"     column="avatar"      />
        <result property="gender"     column="gender"      />
        <result property="birthday"   column="birthday"    />
        <result property="status"     column="status"      />
        <result property="delFlag"    column="del_flag"    />
        <result property="createBy"   column="create_by"   />
        <result property="createTime" column="create_time" />
        <result property="updateBy"   column="update_by"   />
        <result property="updateTime" column="update_time" />
        <result property="remark"     column="remark"      />
    </resultMap>
    <sql id="selectAccountUserVo">
        select user_id, username, phone, password, nickname, avatar, gender, birthday, status, del_flag, create_by, create_time, update_by, update_time, remark
        from account_user
    </sql>
    <select id="selectAccountUserList" parameterType="AccountUser" resultMap="AccountUserResult">
        <include refid="selectAccountUserVo"/>
        <where>
            del_flag = '0'
            <if test="userName != null and userName != ''"> and username like concat('%', #{userName}, '%')</if>
            <if test="phone != null and phone != ''"> and phone like concat('%', #{phone}, '%')</if>
            <if test="nickname != null and nickname != ''"> and nickname like concat('%', #{nickname}, '%')</if>
            <if test="gender != null and gender != ''"> and gender = #{gender}</if>
            <if test="status != null and status != ''"> and status = #{status}</if>
            <if test="params.beginTime != null and params.beginTime != ''">
                and date_format(create_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
            </if>
            <if test="params.endTime != null and params.endTime != ''">
                and date_format(create_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
            </if>
        </where>
        order by user_id desc
    </select>
    <select id="selectAccountUserById" parameterType="Long" resultMap="AccountUserResult">
        <include refid="selectAccountUserVo"/>
        where del_flag = '0' and user_id = #{userId}
    </select>
    <select id="selectAccountUserByUsername" parameterType="String" resultMap="AccountUserResult">
        <include refid="selectAccountUserVo"/>
        where del_flag = '0' and username = #{userName}
    </select>
    <select id="selectAccountUserByPhone" parameterType="String" resultMap="AccountUserResult">
        <include refid="selectAccountUserVo"/>
        where del_flag = '0' and phone = #{phone}
    </select>
    <insert id="insertAccountUser" parameterType="AccountUser" useGeneratedKeys="true" keyProperty="userId">
        insert into account_user(
            <if test="userName != null and userName != ''">username,</if>
            <if test="phone != null and phone != ''">phone,</if>
            <if test="password != null and password != ''">password,</if>
            <if test="nickname != null and nickname != ''">nickname,</if>
            <if test="avatar != null and avatar != ''">avatar,</if>
            <if test="gender != null and gender != ''">gender,</if>
            <if test="birthday != null">birthday,</if>
            <if test="status != null and status != ''">status,</if>
            <if test="remark != null">remark,</if>
            <if test="createBy != null and createBy != ''">create_by,</if>
            create_time
        )values(
            <if test="userName != null and userName != ''">#{userName},</if>
            <if test="phone != null and phone != ''">#{phone},</if>
            <if test="password != null and password != ''">#{password},</if>
            <if test="nickname != null and nickname != ''">#{nickname},</if>
            <if test="avatar != null and avatar != ''">#{avatar},</if>
            <if test="gender != null and gender != ''">#{gender},</if>
            <if test="birthday != null">#{birthday},</if>
            <if test="status != null and status != ''">#{status},</if>
            <if test="remark != null">#{remark},</if>
            <if test="createBy != null and createBy != ''">#{createBy},</if>
            sysdate()
        )
    </insert>
    <update id="updateAccountUser" parameterType="AccountUser">
        update account_user
        <set>
            <if test="userName != null and userName != ''">username = #{userName},</if>
            <if test="phone != null and phone != ''">phone = #{phone},</if>
            <if test="password != null and password != ''">password = #{password},</if>
            <if test="nickname != null">nickname = #{nickname},</if>
            <if test="avatar != null">avatar = #{avatar},</if>
            <if test="gender != null and gender != ''">gender = #{gender},</if>
            <if test="birthday != null">birthday = #{birthday},</if>
            <if test="status != null and status != ''">status = #{status},</if>
            <if test="remark != null">remark = #{remark},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            update_time = sysdate()
        </set>
        where user_id = #{userId} and del_flag = '0'
    </update>
    <!-- 逻辑删除:保留行,仅置删除标志 -->
    <update id="deleteAccountUserById" parameterType="Long">
        update account_user set del_flag = '2' where user_id = #{userId}
    </update>
    <update id="deleteAccountUserByIds" parameterType="Long">
        update account_user set del_flag = '2' where user_id in
        <foreach item="userId" collection="array" open="(" separator="," close=")">
            #{userId}
        </foreach>
    </update>
</mapper>
hugray-server/sql/hugray_account_20261009.sql
New file
@@ -0,0 +1,34 @@
-- ----------------------------
-- C 端会员用户表(ruoyi-user 用户服务)
--
-- 目标库 hugray-account 由使用方预先创建(utf8mb4)。
-- 注意:库名含连字符,任何引用都必须用反引号。
-- ----------------------------
USE `hugray-account`;
DROP TABLE IF EXISTS `account_user`;
CREATE TABLE `account_user` (
  `user_id`     bigint(20)   NOT NULL AUTO_INCREMENT COMMENT '会员ID',
  `username`    varchar(64)  NOT NULL                COMMENT '用户名',
  `phone`       varchar(20)  DEFAULT NULL            COMMENT '手机号',
  `password`    varchar(100) DEFAULT NULL            COMMENT '密码(BCrypt)',
  `nickname`    varchar(64)  DEFAULT NULL            COMMENT '昵称',
  `avatar`      varchar(255) DEFAULT NULL            COMMENT '头像地址',
  `gender`      char(1)      DEFAULT '2'             COMMENT '性别(0男 1女 2未知,对齐 sys_user_sex 字典)',
  `birthday`    date         DEFAULT NULL            COMMENT '生日',
  `status`      char(1)      DEFAULT '0'             COMMENT '状态(0正常 1停用)',
  `del_flag`    char(1)      DEFAULT '0'             COMMENT '删除标志(0存在 2删除)',
  `create_by`   varchar(64)  DEFAULT ''              COMMENT '创建者',
  `create_time` datetime     DEFAULT NULL            COMMENT '创建时间',
  `update_by`   varchar(64)  DEFAULT ''              COMMENT '更新者',
  `update_time` datetime     DEFAULT NULL            COMMENT '更新时间',
  `remark`      varchar(500) DEFAULT NULL            COMMENT '备注',
  PRIMARY KEY (`user_id`),
  -- 刻意不加 username 唯一索引:删除是逻辑删除(del_flag=2),残留行会永久占用用户名,
  -- 与「服务层只校验 del_flag='0' 的记录」冲突,导致新增时报 500。此处对齐上游
  -- sys_user 的做法(ry_20260417.sql 中 sys_user 仅有主键),唯一性由
  -- AccountUserServiceImpl.checkUsernameUnique 单一权威保证。
  KEY `idx_account_user_username` (`username`),
  KEY `idx_account_user_phone` (`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8mb4 COMMENT='C端会员用户表';
hugray-server/sql/hugray_menu_account_20261009.sql
New file
@@ -0,0 +1,26 @@
-- ----------------------------
-- 用户服务(C 端会员)菜单
--
-- 目标库为业务库 hugray(与 sys_menu 同库)。
-- 号段取 2100~2106:现有最大 menu_id 为 1060,此段与之不冲突。
--
-- 不需要写 sys_role_menu:超级管理员在 SysMenuServiceImpl 里走
-- selectMenuTreeAll() / selectMenuList() 全量返回,与角色菜单表无关。
-- 若要授权给非 admin 角色,再自行插 sys_role_menu。
-- ----------------------------
USE `hugray`;
-- 可重复执行:先清掉本号段,再插入
DELETE FROM `sys_menu` WHERE `menu_id` BETWEEN 2100 AND 2106;
INSERT INTO `sys_menu`
(`menu_id`, `menu_name`, `parent_id`, `order_num`, `path`, `component`, `query`, `route_name`, `is_frame`, `is_cache`, `menu_type`, `visible`, `status`, `perms`, `icon`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`)
VALUES
(2100, '用户服务', 0,    5, 'user',    NULL,                   NULL, '', 1, 0, 'M', '0', '0', '',                   'user',    'admin', sysdate(), '', NULL, '用户服务目录'),
(2101, '会员管理', 2100, 1, 'account', 'user/account/index',   NULL, '', 1, 0, 'C', '0', '0', 'account:user:list',  'peoples', 'admin', sysdate(), '', NULL, '会员管理菜单'),
(2102, '会员查询', 2101, 1, '',        NULL,                   NULL, '', 1, 0, 'F', '0', '0', 'account:user:query', '#',       'admin', sysdate(), '', NULL, ''),
(2103, '会员新增', 2101, 2, '',        NULL,                   NULL, '', 1, 0, 'F', '0', '0', 'account:user:add',   '#',       'admin', sysdate(), '', NULL, ''),
(2104, '会员修改', 2101, 3, '',        NULL,                   NULL, '', 1, 0, 'F', '0', '0', 'account:user:edit',  '#',       'admin', sysdate(), '', NULL, ''),
(2105, '会员删除', 2101, 4, '',        NULL,                   NULL, '', 1, 0, 'F', '0', '0', 'account:user:remove','#',       'admin', sysdate(), '', NULL, ''),
(2106, '会员导出', 2101, 5, '',        NULL,                   NULL, '', 1, 0, 'F', '0', '0', 'account:user:export','#',       'admin', sysdate(), '', NULL, '');