Update admin
This commit is contained in:
@@ -0,0 +1,44 @@
|
|||||||
|
# Copilot Instructions for Jiscuss
|
||||||
|
|
||||||
|
## Build, test, and run commands
|
||||||
|
|
||||||
|
This repository is a Maven Spring Boot project (Java 17+).
|
||||||
|
|
||||||
|
- Full compile: `mvn -DskipTests compile`
|
||||||
|
- Full test suite: `mvn test`
|
||||||
|
- Run one test class: `mvn -Dtest=ClassName test`
|
||||||
|
- Run one test method: `mvn -Dtest=ClassName#methodName test`
|
||||||
|
- Run app (default H2 profile): `mvn spring-boot:run`
|
||||||
|
- Run app (MySQL profile): `mvn spring-boot:run -Dspring-boot.run.profiles=mysql`
|
||||||
|
- Run app on a non-80 port: `mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8080"`
|
||||||
|
|
||||||
|
There is currently no dedicated lint plugin configured in `pom.xml` (no Checkstyle/SpotBugs/PMD task).
|
||||||
|
|
||||||
|
## High-level architecture
|
||||||
|
|
||||||
|
Jiscuss is a Spring Boot monolith with a server-rendered web UI plus REST APIs:
|
||||||
|
|
||||||
|
- **Web MVC + templates**: `controller/` classes return FreeMarker views from `src/main/resources/templates/`, with frontend assets under `src/main/resources/static/` (Semantic UI + custom JS).
|
||||||
|
- **REST APIs**: `controller/api/` exposes JSON endpoints (`/user_api/**`, `/other_api/**`) and is documented by SpringDoc OpenAPI (`/swagger-ui.html`, `/v3/api-docs`).
|
||||||
|
- **Domain and persistence**: `entity/` models map to relational tables; `repository/` uses Spring Data JPA (plus a few native queries); `service/impl/` contains business logic and transaction boundaries.
|
||||||
|
- **Security and RBAC**: `WebSecurityConfig` wires Spring Security 6; auth uses `UserDetailServiceImpl`; path checks delegate to `RbacPermission`; roles come from `rbac_role` / `rbac_user_role` (Flyway migration `V4__rbac_admin_console.sql`).
|
||||||
|
- **Admin console and auditing**: `/admin/**` endpoints in `AdminSystemController` manage users/roles/content and write operation history via `AuditLogService`.
|
||||||
|
- **Database lifecycle**: Flyway owns schema/data evolution (`src/main/resources/db/migration/*`), with profile-specific datasource config in `application-h2.yml` and `application-mysql.yml`.
|
||||||
|
|
||||||
|
## Key repository conventions
|
||||||
|
|
||||||
|
- **Flyway is the source of truth for DDL**: keep schema changes in new migration files; do not reintroduce `schema.sql`/`data.sql` initialization paths.
|
||||||
|
- **Boot 3 / Jakarta imports only**: use `jakarta.*` APIs (not legacy `javax.*` servlet/validation/persistence types).
|
||||||
|
- **Password handling is BCrypt-only**: DB passwords are expected to be BCrypt hashes (`UserDetailServiceImpl`, migration notes in `V2__security_updates.sql`); do not add plaintext comparisons in SQL/repositories.
|
||||||
|
- **Role naming convention**: security authorities are `ROLE_*`; database role codes are plain (`ADMIN`, `USER`) and are prefixed in the auth layer.
|
||||||
|
- **Controller split matters**:
|
||||||
|
- `controller/` for page flows and model population
|
||||||
|
- `controller/api/` for REST endpoints
|
||||||
|
- `AdminSystemController` for admin workflows under `/admin/**`
|
||||||
|
- **Current user lookup pattern**: controllers extending `BaseController` use `getUserInfo(HttpServletRequest)` (reads `SPRING_SECURITY_CONTEXT` from session).
|
||||||
|
- **Response wrappers are mixed**: newer global exception flow returns `ApiResponse`; legacy endpoints still return entities or `ResponseResult`. Keep changes consistent with the style already used in the touched controller package.
|
||||||
|
- **Caching is selective**: user read paths in `UsersServiceImpl` use cache names (`user`, `userList`) backed by Ehcache JCache (`ehcache3.xml`).
|
||||||
|
- **Profile behavior**:
|
||||||
|
- default/dev flow is H2 profile
|
||||||
|
- MySQL requires explicit profile activation and datasource credentials
|
||||||
|
- admin-only tooling endpoints include `/admin/**`, `/actuator/**`, `/druid/**` (and H2 console when enabled)
|
||||||
@@ -10,7 +10,7 @@ import java.util.Date;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.common
|
* @Package com.yaoyuan.jiscuss.common
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title: 工具类
|
* @Title: 工具类
|
||||||
* @Package com.yaoyuan.jiscuss.common
|
* @Package com.yaoyuan.jiscuss.common
|
||||||
* @Description: 通用工具类
|
* @Description: 通用工具类
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* 后台系统控制器
|
* 后台系统控制器
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
@@ -98,9 +98,9 @@ public class AdminSystemController extends BaseController {
|
|||||||
|
|
||||||
List<User> users = usersRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
|
List<User> users = usersRepository.findAll(Sort.by(Sort.Direction.ASC, "id"));
|
||||||
List<Role> roles = roleRepository.findAllByOrderByIdAsc();
|
List<Role> roles = roleRepository.findAllByOrderByIdAsc();
|
||||||
Map<Integer, Set<Integer>> userRoleIds = new HashMap<>();
|
Map<String, Set<Integer>> userRoleIds = new HashMap<>();
|
||||||
for (UserRole ur : userRoleRepository.findAll()) {
|
for (UserRole ur : userRoleRepository.findAll()) {
|
||||||
userRoleIds.computeIfAbsent(ur.getUserId(), key -> new HashSet<>()).add(ur.getRoleId());
|
userRoleIds.computeIfAbsent(String.valueOf(ur.getUserId()), key -> new HashSet<>()).add(ur.getRoleId());
|
||||||
}
|
}
|
||||||
|
|
||||||
map.put("users", users);
|
map.put("users", users);
|
||||||
@@ -222,10 +222,10 @@ public class AdminSystemController extends BaseController {
|
|||||||
Set<Integer> userIds = posts.stream().map(Post::getCreateId).filter(v -> v != null).collect(Collectors.toSet());
|
Set<Integer> userIds = posts.stream().map(Post::getCreateId).filter(v -> v != null).collect(Collectors.toSet());
|
||||||
Set<Integer> discussionIds = posts.stream().map(Post::getDiscussionId).filter(v -> v != null).collect(Collectors.toSet());
|
Set<Integer> discussionIds = posts.stream().map(Post::getDiscussionId).filter(v -> v != null).collect(Collectors.toSet());
|
||||||
|
|
||||||
Map<Integer, String> userNames = usersRepository.findAllById(userIds).stream()
|
Map<String, String> userNames = usersRepository.findAllById(userIds).stream()
|
||||||
.collect(Collectors.toMap(User::getId, User::getUsername));
|
.collect(Collectors.toMap(user -> String.valueOf(user.getId()), User::getUsername));
|
||||||
Map<Integer, String> discussionNames = discussionsRepository.findAllById(discussionIds).stream()
|
Map<String, String> discussionNames = discussionsRepository.findAllById(discussionIds).stream()
|
||||||
.collect(Collectors.toMap(Discussion::getId, Discussion::getTitle));
|
.collect(Collectors.toMap(discussion -> String.valueOf(discussion.getId()), Discussion::getTitle));
|
||||||
|
|
||||||
map.put("userNames", userNames);
|
map.put("userNames", userNames);
|
||||||
map.put("discussionNames", discussionNames);
|
map.put("discussionNames", discussionNames);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import org.springframework.security.core.context.SecurityContext;
|
|||||||
/**
|
/**
|
||||||
* Base controller exposing helpers shared by web controllers.
|
* Base controller exposing helpers shared by web controllers.
|
||||||
*
|
*
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
*/
|
*/
|
||||||
public class BaseController {
|
public class BaseController {
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.yaoyuan.jiscuss.controller;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* 用户消息控制器
|
* 用户消息控制器
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.yaoyuan.jiscuss.controller;
|
|||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* 其他控制器——积分/权限等
|
* 其他控制器——积分/权限等
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.controller
|
* @Package com.yaoyuan.jiscuss.controller
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* 主题帖子评论控制器
|
* 主题帖子评论控制器
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ import java.util.Set;
|
|||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* 首页页面系统控制器
|
* 首页页面系统控制器
|
||||||
*/
|
*/
|
||||||
@Controller
|
@Controller
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.io.Serializable;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Entity
|
@Entity
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.springframework.security.core.userdetails.UserDetails;
|
|||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.entity
|
* @Package com.yaoyuan.jiscuss.entity
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import lombok.Setter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.entity.custom
|
* @Package com.yaoyuan.jiscuss.entity.custom
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import lombok.Setter;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.entity.custom
|
* @Package com.yaoyuan.jiscuss.entity.custom
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import lombok.Setter;
|
|||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title:
|
* @Title:
|
||||||
* @Package com.yaoyuan.jiscuss.entity.custom
|
* @Package com.yaoyuan.jiscuss.entity.custom
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.yaoyuan.jiscuss.util;
|
package com.yaoyuan.jiscuss.util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author yaoyuan2.chu
|
* @author Chuyaoyuan
|
||||||
* @Title: 去除内容页代码里的HTML标签
|
* @Title: 去除内容页代码里的HTML标签
|
||||||
* @Package com.yaoyuan.jiscuss.util
|
* @Package com.yaoyuan.jiscuss.util
|
||||||
* @Description:
|
* @Description:
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
-- V2.0.2: RBAC foundation + admin upgrade logs (Spring Boot 3 optimization)
|
-- V4: RBAC foundation + admin upgrade logs (Spring Boot 3 optimization)
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS rbac_role
|
CREATE TABLE IF NOT EXISTS rbac_role
|
||||||
(
|
(
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
-- V2.0.3: Audit log for admin operations (Spring Boot 3 optimization)
|
-- V5: Audit log for admin operations (Spring Boot 3 optimization)
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS audit_log
|
CREATE TABLE IF NOT EXISTS audit_log
|
||||||
(
|
(
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<meta name="_csrf" content="${_csrf.token}"/>
|
<meta name="_csrf" content="${_csrf.token}"/>
|
||||||
<meta name="_csrf_header" content="${_csrf.headerName}"/>
|
<meta name="_csrf_header" content="${_csrf.headerName}"/>
|
||||||
<title>${title}</title>
|
<title>${title}</title>
|
||||||
<#include "admin-commjs.ftl"/>
|
<#include "admin/admin-commjs.ftl"/>
|
||||||
<style>
|
<style>
|
||||||
body { background: #f6f7fb; }
|
body { background: #f6f7fb; }
|
||||||
.admin-header { background: #1f2937; color: #fff; padding: 12px 18px; }
|
.admin-header { background: #1f2937; color: #fff; padding: 12px 18px; }
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 操作审计日志" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 操作审计日志" active=active adminName=adminName>
|
||||||
<h2 class="ui header">操作审计日志</h2>
|
<h2 class="ui header">操作审计日志</h2>
|
||||||
<p class="small-muted">记录所有管理员的后台操作,包括角色管理、用户管理、内容管理等。</p>
|
<p class="small-muted">记录所有管理员的后台操作,包括角色管理、用户管理、内容管理等。</p>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 文章管理" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 文章管理" active=active adminName=adminName>
|
||||||
<h2 class="ui header">文章管理</h2>
|
<h2 class="ui header">文章管理</h2>
|
||||||
<table class="ui striped celled table">
|
<table class="ui striped celled table">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 总览" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 总览" active=active adminName=adminName>
|
||||||
<h2 class="ui header">系统总览</h2>
|
<h2 class="ui header">系统总览</h2>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 插件管理(预留)" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 插件管理(预留)" active=active adminName=adminName>
|
||||||
<h2 class="ui header">插件管理(预留)</h2>
|
<h2 class="ui header">插件管理(预留)</h2>
|
||||||
<div class="ui info message">
|
<div class="ui info message">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 评论管理" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 评论管理" active=active adminName=adminName>
|
||||||
<h2 class="ui header">评论管理</h2>
|
<h2 class="ui header">评论管理</h2>
|
||||||
<table class="ui compact celled table">
|
<table class="ui compact celled table">
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
<#list posts as p>
|
<#list posts as p>
|
||||||
<tr>
|
<tr>
|
||||||
<td>${p.id}</td>
|
<td>${p.id}</td>
|
||||||
<td>${discussionNames[p.discussionId]!('#'+p.discussionId?c)}</td>
|
<td>${discussionNames[p.discussionId?c]!('#'+p.discussionId?c)}</td>
|
||||||
<td>${userNames[p.createId]!('#'+p.createId?c)}</td>
|
<td>${userNames[p.createId?c]!('#'+p.createId?c)}</td>
|
||||||
<td><div style="max-width:420px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${p.content!""}</div></td>
|
<td><div style="max-width:420px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${p.content!""}</div></td>
|
||||||
<td>${p.createTime?string('yyyy-MM-dd HH:mm:ss')!""}</td>
|
<td>${p.createTime?string('yyyy-MM-dd HH:mm:ss')!""}</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 主题管理(预留)" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 主题管理(预留)" active=active adminName=adminName>
|
||||||
<h2 class="ui header">主题管理(预留)</h2>
|
<h2 class="ui header">主题管理(预留)</h2>
|
||||||
<div class="ui info message">
|
<div class="ui info message">
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 版本升级日志" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 版本升级日志" active=active adminName=adminName>
|
||||||
<h2 class="ui header">版本功能升级日志</h2>
|
<h2 class="ui header">版本功能升级日志</h2>
|
||||||
<p class="small-muted">记录每次升级新增与修复内容,便于发布追踪与回滚评估。</p>
|
<p class="small-muted">记录每次升级新增与修复内容,便于发布追踪与回滚评估。</p>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<#import "admin-shell.ftl" as shell>
|
<#import "admin/admin-shell.ftl" as shell>
|
||||||
<@shell.page title="后台管理 - 用户与角色" active=active adminName=adminName>
|
<@shell.page title="后台管理 - 用户与角色" active=active adminName=adminName>
|
||||||
<h2 class="ui header">用户与角色管理</h2>
|
<h2 class="ui header">用户与角色管理</h2>
|
||||||
<p class="small-muted">简单 RBAC:用户可绑定多个角色,拥有 ADMIN 角色的用户可以进入后台管理。</p>
|
<p class="small-muted">简单 RBAC:用户可绑定多个角色,拥有 ADMIN 角色的用户可以进入后台管理。</p>
|
||||||
@@ -59,7 +59,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="ui checkbox">
|
<div class="ui checkbox">
|
||||||
<input type="checkbox" name="roleIds" value="${r.id}"
|
<input type="checkbox" name="roleIds" value="${r.id}"
|
||||||
<#if userRoleIds[u.id]?? && userRoleIds[u.id]?seq_contains(r.id)>checked</#if>>
|
<#if userRoleIds[u.id?c]?? && userRoleIds[u.id?c]?seq_contains(r.id)>checked</#if>>
|
||||||
<label>${r.code}</label>
|
<label>${r.code}</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user