update
This commit is contained in:
@@ -57,6 +57,178 @@
|
||||
http://localhost
|
||||
```
|
||||
|
||||
> 说明:以上为原始快速启动说明。当前升级后的 v2.0 分支已迁移到 Spring Boot 3,推荐使用 JDK 17 运行;旧版本中的 JDK 1.8 说明仅适用于早期分支。
|
||||
|
||||
|
||||
## 当前升级版本说明
|
||||
|
||||
当前代码已完成 Spring Boot 3 兼容升级,主要技术栈如下:
|
||||
|
||||
- Java 17
|
||||
- Spring Boot 3.3.5
|
||||
- Spring Security 6
|
||||
- Spring Data JPA / Hibernate 6
|
||||
- FreeMarker
|
||||
- H2 Database(开发环境默认)
|
||||
- MySQL 8(生产/外部数据库环境)
|
||||
- Flyway(数据库版本迁移)
|
||||
- SpringDoc OpenAPI(替代 Springfox Swagger)
|
||||
- Ehcache 3 + JCache
|
||||
- Druid Spring Boot 3 Starter
|
||||
- MapStruct
|
||||
|
||||
|
||||
## 环境要求
|
||||
|
||||
- JDK:17+
|
||||
- Maven:3.8+
|
||||
- MySQL:8.x(仅 mysql profile 需要)
|
||||
|
||||
检查本地环境:
|
||||
|
||||
```
|
||||
java -version
|
||||
javac -version
|
||||
mvn -version
|
||||
```
|
||||
|
||||
|
||||
## 构建与启动
|
||||
|
||||
### 使用默认 H2 开发环境
|
||||
|
||||
项目默认激活 `h2` profile:
|
||||
|
||||
```
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
访问:
|
||||
|
||||
```
|
||||
http://localhost
|
||||
```
|
||||
|
||||
如果本机 Linux 普通用户无法绑定 80 端口,可以临时指定其他端口:
|
||||
|
||||
```
|
||||
mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8080"
|
||||
```
|
||||
|
||||
然后访问:
|
||||
|
||||
```
|
||||
http://localhost:8080
|
||||
```
|
||||
|
||||
|
||||
### 使用 MySQL 环境
|
||||
|
||||
1. 创建数据库:
|
||||
|
||||
```
|
||||
CREATE DATABASE jiscuss DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
2. 修改配置文件:
|
||||
|
||||
```
|
||||
src/main/resources/application-mysql.yml
|
||||
```
|
||||
|
||||
根据实际环境修改:
|
||||
|
||||
- `spring.datasource.url`
|
||||
- `spring.datasource.username`
|
||||
- `spring.datasource.password`
|
||||
|
||||
3. 启动 mysql profile:
|
||||
|
||||
```
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=mysql
|
||||
```
|
||||
|
||||
|
||||
## 数据库迁移说明
|
||||
|
||||
项目已使用 Flyway 接管数据库初始化和后续变更,迁移脚本位于:
|
||||
|
||||
```
|
||||
src/main/resources/db/migration/
|
||||
```
|
||||
|
||||
当前迁移文件:
|
||||
|
||||
- `V1__init_schema.sql`:初始化表结构和基础数据
|
||||
- `V2__security_updates.sql`:安全相关字段/数据升级
|
||||
|
||||
注意:旧的 `schema.sql` 和 `data.sql` 已不再使用,避免与 Flyway 重复初始化。
|
||||
|
||||
|
||||
## 安全与管理端点
|
||||
|
||||
- 登录认证已迁移到 Spring Security 6 配置方式。
|
||||
- `/admin/**`、`/actuator/**`、`/druid/**` 需要管理员角色访问。
|
||||
- H2 Console 默认关闭;如需开发调试,可显式开启:
|
||||
|
||||
```
|
||||
mvn spring-boot:run -Dspring-boot.run.arguments="--spring.h2.console.enabled=true"
|
||||
```
|
||||
|
||||
- OpenAPI 文档地址:
|
||||
|
||||
```
|
||||
http://localhost/swagger-ui.html
|
||||
http://localhost/v3/api-docs
|
||||
```
|
||||
|
||||
|
||||
## 网络不佳时的构建建议
|
||||
|
||||
如果依赖已经下载到本地 Maven 仓库,可以使用离线模式:
|
||||
|
||||
```
|
||||
mvn -o -DskipTests compile
|
||||
```
|
||||
|
||||
如果首次构建尚未下载 Spring Boot 3.3.5 等依赖,离线模式会失败,需要在网络可用时先执行一次:
|
||||
|
||||
```
|
||||
mvn -DskipTests compile
|
||||
```
|
||||
|
||||
国内网络环境可配置 Maven 镜像源后再构建。依赖下载完成后,后续即可使用 `mvn -o` 离线构建。
|
||||
|
||||
|
||||
## 常用检查命令
|
||||
|
||||
编译检查:
|
||||
|
||||
```
|
||||
mvn -DskipTests compile
|
||||
```
|
||||
|
||||
打包:
|
||||
|
||||
```
|
||||
mvn -DskipTests package
|
||||
```
|
||||
|
||||
运行 jar:
|
||||
|
||||
```
|
||||
java -jar target/jiscuss-0.0.1-SNAPSHOT.jar
|
||||
```
|
||||
|
||||
|
||||
## 升级注意事项
|
||||
|
||||
- Spring Boot 3 使用 `jakarta.*` 命名空间,旧的 `javax.servlet`、`javax.persistence`、`javax.validation` 不再使用。
|
||||
- Hibernate 6 可自动识别数据库方言,配置中无需手动指定 `database-platform`。
|
||||
- MySQL 连接已移除不推荐的 `autoReconnect=true`。
|
||||
- JSON 响应优先使用 Spring MVC + Jackson 自动序列化,不再依赖 `org.json`。
|
||||
- WebSocket starter 已移除;如后续确实需要实时通信,再按功能重新引入。
|
||||
|
||||
|
||||
## [四]、开源协议
|
||||
|
||||
|
||||
@@ -39,10 +39,6 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
</dependency>
|
||||
<!-- Bean Validation (input validation) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@@ -73,13 +69,6 @@
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JSON (upgraded from 20180813) -->
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20240303</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SpringDoc OpenAPI 2 — replaces discontinued springfox-swagger2, compatible with Spring Boot 3 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.yaoyuan.jiscuss.common;
|
||||
|
||||
import com.yaoyuan.jiscuss.entity.custom.PostCustom;
|
||||
import lombok.Data;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
@@ -17,6 +19,8 @@ import java.util.List;
|
||||
@Data
|
||||
public class Node {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Node.class);
|
||||
|
||||
/**
|
||||
* Node
|
||||
* 空方法
|
||||
@@ -85,7 +89,7 @@ public class Node {
|
||||
for (Node node1 : list) { //循环添加
|
||||
if (node1.getId().equals(node.getParentId())) { //判断留言的上一段是都是这条留言
|
||||
node1.getNextNodes().add(node); //是,添加,返回true;
|
||||
System.out.println("添加了一个");
|
||||
logger.debug("添加了一个");
|
||||
return true;
|
||||
} else { //否则递归继续判断
|
||||
if (node1.getNextNodes().size() != 0) {
|
||||
@@ -127,7 +131,7 @@ public class Node {
|
||||
**/
|
||||
public static void show(List<Node> list) {
|
||||
for (Node node : list) {
|
||||
System.out.println(node.getUserId() + " 用户回复了你:" + node.getContent());
|
||||
logger.debug("{} 用户回复了你:{}", node.getUserId(), node.getContent());
|
||||
if (node.getNextNodes().size() != 0) {
|
||||
Node.show(node.getNextNodes());
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.security.web.access.expression.DefaultHttpSecurityExpressionHandler;
|
||||
import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
|
||||
|
||||
/**
|
||||
@@ -69,7 +69,7 @@ public class WebSecurityConfig {
|
||||
* <p>Security decisions:
|
||||
* <ul>
|
||||
* <li>H2 console: only accessible when enabled (dev profile) and requires ROLE_ADMIN</li>
|
||||
* <li>Druid monitoring: requires ROLE_ADMIN (Druid also has its own login page)</li>
|
||||
* <li>Druid monitoring: requires ROLE_ADMIN through Spring Security</li>
|
||||
* <li>Admin/Actuator paths: requires ROLE_ADMIN</li>
|
||||
* <li>SpringDoc UI paths: public (API docs are for developers)</li>
|
||||
* <li>All other authenticated requests: evaluated by {@code RbacPermission.hasPermission}</li>
|
||||
@@ -89,7 +89,7 @@ public class WebSecurityConfig {
|
||||
}
|
||||
|
||||
// Build the RBAC expression manager with ApplicationContext so @bean references work
|
||||
DefaultWebSecurityExpressionHandler expressionHandler = new DefaultWebSecurityExpressionHandler();
|
||||
DefaultHttpSecurityExpressionHandler expressionHandler = new DefaultHttpSecurityExpressionHandler();
|
||||
expressionHandler.setApplicationContext(applicationContext);
|
||||
WebExpressionAuthorizationManager rbacManager = new WebExpressionAuthorizationManager(
|
||||
"@rbacPermission.hasPermission(request, authentication)");
|
||||
|
||||
@@ -1,48 +1,34 @@
|
||||
package com.yaoyuan.jiscuss.controller;
|
||||
|
||||
import com.yaoyuan.jiscuss.entity.UserInfo;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
* Base controller exposing helpers shared by web controllers.
|
||||
*
|
||||
* @author yaoyuan2.chu
|
||||
* @Title: BaseController
|
||||
* @Package com.yaoyuan.jiscuss.controller
|
||||
* @Description: BaseController
|
||||
* @date 2020/7/16 14:36
|
||||
*/
|
||||
public class BaseController {
|
||||
|
||||
/**
|
||||
* 获取当前用户
|
||||
*
|
||||
* @return
|
||||
* Returns the current authenticated user, or {@code null} when not logged in.
|
||||
*/
|
||||
protected UserInfo getUserInfo(HttpServletRequest request) {
|
||||
UserInfo user = null;
|
||||
//获得session对象
|
||||
HttpSession session = request.getSession();
|
||||
//取出session域中所有属性名
|
||||
Enumeration attributeNames = session.getAttributeNames();
|
||||
while (attributeNames.hasMoreElements()) {
|
||||
System.out.println(attributeNames.nextElement());
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session == null) {
|
||||
return null;
|
||||
}
|
||||
Object ctx = session.getAttribute("SPRING_SECURITY_CONTEXT");
|
||||
if (!(ctx instanceof SecurityContext securityContext)) {
|
||||
return null;
|
||||
}
|
||||
//SPRING_SECURITY_CONTEXT
|
||||
Object spring_security_context = session.getAttribute("SPRING_SECURITY_CONTEXT");
|
||||
System.out.println(spring_security_context);
|
||||
SecurityContext securityContext = (SecurityContext) spring_security_context;
|
||||
if (securityContext != null) {
|
||||
//获得认证信息
|
||||
Authentication authentication = securityContext.getAuthentication();
|
||||
//获得用户详情
|
||||
Object principal = authentication.getPrincipal();
|
||||
user = (UserInfo) principal;
|
||||
if (authentication == null || !(authentication.getPrincipal() instanceof UserInfo userInfo)) {
|
||||
return null;
|
||||
}
|
||||
return user;
|
||||
return userInfo;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import com.yaoyuan.jiscuss.service.IDiscussionsTagsService;
|
||||
import com.yaoyuan.jiscuss.service.IPostsService;
|
||||
import com.yaoyuan.jiscuss.service.ITagsService;
|
||||
import com.yaoyuan.jiscuss.service.IUsersService;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -29,7 +28,9 @@ import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author yaoyuan2.chu
|
||||
@@ -108,9 +109,9 @@ public class UserPostController extends BaseController {
|
||||
* @param discussion
|
||||
* @return
|
||||
*/
|
||||
@PostMapping(value = "/newDiscussions")
|
||||
@PostMapping(value = {"/newDiscussions", "/newdiscussions"})
|
||||
@ResponseBody
|
||||
public String newDiscussions(@RequestBody DiscussionCustom discussion) {
|
||||
public Map<String, Object> newDiscussions(@RequestBody DiscussionCustom discussion) {
|
||||
logger.info(">>> newPost" + discussion);
|
||||
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
@@ -141,13 +142,11 @@ public class UserPostController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject resultobj = new JSONObject();
|
||||
|
||||
Map<String, Object> resultobj = new HashMap<>();
|
||||
logger.info(">>>{}", saveDiscussion);
|
||||
resultobj.put("msg", "添加主题成功");
|
||||
resultobj.put("flag", true);
|
||||
return resultobj.toString(); //
|
||||
|
||||
return resultobj;
|
||||
}
|
||||
|
||||
|
||||
@@ -170,7 +169,7 @@ public class UserPostController extends BaseController {
|
||||
*/
|
||||
@PostMapping(value = "/newPost")
|
||||
@ResponseBody
|
||||
public String newPosts(@RequestBody Post post) {
|
||||
public Map<String, Object> newPosts(@RequestBody Post post) {
|
||||
logger.info(">>> newpost" + post);
|
||||
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
@@ -187,12 +186,11 @@ public class UserPostController extends BaseController {
|
||||
}
|
||||
|
||||
Post savePost = postsService.insert(post);
|
||||
JSONObject resultobj = new JSONObject();
|
||||
|
||||
Map<String, Object> resultobj = new HashMap<>();
|
||||
logger.info(">>>{}", savePost);
|
||||
resultobj.put("msg", "添加评论成功");
|
||||
resultobj.put("flag", true);
|
||||
return resultobj.toString(); //
|
||||
return resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -208,7 +206,7 @@ public class UserPostController extends BaseController {
|
||||
*/
|
||||
@PostMapping(value = "/newtags")
|
||||
@ResponseBody
|
||||
public String newTags(@RequestBody Tag tag) {
|
||||
public Map<String, Object> newTags(@RequestBody Tag tag) {
|
||||
logger.info(">>> newTags" + tag);
|
||||
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
HttpServletRequest request = servletRequestAttributes.getRequest();
|
||||
@@ -220,12 +218,11 @@ public class UserPostController extends BaseController {
|
||||
tag.setCreateTime(new Date());
|
||||
|
||||
Tag saveTag = tagsService.insert(tag);
|
||||
JSONObject resultobj = new JSONObject();
|
||||
|
||||
Map<String, Object> resultobj = new HashMap<>();
|
||||
logger.info(">>>{}", saveTag);
|
||||
resultobj.put("msg", "添加标签成功");
|
||||
resultobj.put("flag", true);
|
||||
return resultobj.toString();
|
||||
return resultobj;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,20 +1,28 @@
|
||||
package com.yaoyuan.jiscuss.dto;
|
||||
|
||||
import com.yaoyuan.jiscuss.entity.User;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.MappingConstants;
|
||||
import org.mapstruct.ReportingPolicy;
|
||||
|
||||
/**
|
||||
* Manual mapper between {@link User} entity and DTO objects.
|
||||
* MapStruct mapper between {@link User} entity and its DTOs.
|
||||
*
|
||||
* <p>Intentionally excludes the {@code password} field from all output DTOs.
|
||||
* Replace with a generated MapStruct mapper if mapping complexity grows.
|
||||
* <p>The {@code password} field is intentionally never copied into a response and
|
||||
* is left {@code null} when constructing a {@link User} from a create request —
|
||||
* the caller (service layer) is responsible for BCrypt-hashing and setting it.
|
||||
*/
|
||||
@Component
|
||||
public class UserMapper {
|
||||
@Mapper(
|
||||
componentModel = MappingConstants.ComponentModel.SPRING,
|
||||
unmappedTargetPolicy = ReportingPolicy.IGNORE
|
||||
)
|
||||
public interface UserMapper {
|
||||
|
||||
/** Map a {@link User} entity to a safe {@link UserResponse} (no password). */
|
||||
public UserResponse toResponse(User user) {
|
||||
if (user == null) return null;
|
||||
default UserResponse toResponse(User user) {
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
return new UserResponse(
|
||||
user.getId(),
|
||||
user.getUsername(),
|
||||
@@ -32,13 +40,15 @@ public class UserMapper {
|
||||
);
|
||||
}
|
||||
|
||||
/** Map a {@link UserCreateRequest} to a new {@link User} entity (password left unset — caller must hash it). */
|
||||
public User fromCreateRequest(UserCreateRequest request) {
|
||||
/** Map a create request to a new {@link User} entity. Password must be set by the caller. */
|
||||
default User fromCreateRequest(UserCreateRequest request) {
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
User user = new User();
|
||||
user.setUsername(request.username());
|
||||
user.setEmail(request.email());
|
||||
user.setRealname(request.realname());
|
||||
// Caller is responsible for BCrypt-hashing the password before calling usersService.insert()
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,63 +1,62 @@
|
||||
package com.yaoyuan.jiscuss.handler;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.WebAttributes;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import jakarta.servlet.RequestDispatcher;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.web.access.AccessDeniedHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
/**
|
||||
* 处理无权请求
|
||||
* Handles 403 access-denied responses.
|
||||
*
|
||||
* @author charlie
|
||||
* <p>Behavior:
|
||||
* <ul>
|
||||
* <li>AJAX requests → JSON body with {@code code/msg}</li>
|
||||
* <li>Regular page requests → simple HTML 403 response (no forward to /403,
|
||||
* which previously caused a 404)</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Component
|
||||
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
|
||||
|
||||
private Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(CustomAccessDeniedHandler.class);
|
||||
|
||||
@Override
|
||||
public void handle(HttpServletRequest request, HttpServletResponse response,
|
||||
public void handle(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
AccessDeniedException accessDeniedException) throws IOException, ServletException {
|
||||
boolean isAjax = ControllerTools.isAjaxRequest(request);
|
||||
System.out.println("CustomAccessDeniedHandler handle");
|
||||
if (!response.isCommitted()) {
|
||||
if (isAjax) {
|
||||
String msg = accessDeniedException.getMessage();
|
||||
log.info("accessDeniedException.message:" + msg);
|
||||
String accessDenyMsg = "{\"code\":\"403\",\"msg\":\"没有权限\"}";
|
||||
ControllerTools.print(response, accessDenyMsg);
|
||||
} else {
|
||||
request.setAttribute(WebAttributes.ACCESS_DENIED_403, accessDeniedException);
|
||||
if (response.isCommitted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.warn("Access denied for {} → {}", request.getRequestURI(), accessDeniedException.getMessage());
|
||||
response.setStatus(HttpStatus.FORBIDDEN.value());
|
||||
RequestDispatcher dispatcher = request.getRequestDispatcher("/403");
|
||||
dispatcher.forward(request, response);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
|
||||
if (isAjaxRequest(request)) {
|
||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
||||
try (PrintWriter writer = response.getWriter()) {
|
||||
writer.write("{\"code\":403,\"msg\":\"没有权限\"}");
|
||||
}
|
||||
} else {
|
||||
response.setContentType(MediaType.TEXT_HTML_VALUE);
|
||||
try (PrintWriter writer = response.getWriter()) {
|
||||
writer.write("<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>403 Forbidden</title></head>"
|
||||
+ "<body><h1>403 Forbidden</h1><p>您没有权限访问该资源。</p></body></html>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ControllerTools {
|
||||
public static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
private static boolean isAjaxRequest(HttpServletRequest request) {
|
||||
return "XMLHttpRequest".equals(request.getHeader("X-Requested-With"));
|
||||
}
|
||||
|
||||
public static void print(HttpServletResponse response, String msg) throws IOException {
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json; charset=utf-8");
|
||||
PrintWriter writer = response.getWriter();
|
||||
writer.write(msg);
|
||||
writer.flush();
|
||||
writer.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ public class PostsServiceImpl implements IPostsService {
|
||||
}
|
||||
//将回复添加到对应的位置
|
||||
List list = Node.addAllNode(nodes, thenpostCustomList);
|
||||
System.out.println();
|
||||
//打印回复链表
|
||||
Node.show(list);
|
||||
|
||||
|
||||
@@ -49,9 +49,4 @@ public class DelTagsUtil {
|
||||
return htmlStr;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
String htmlStr = "test";
|
||||
System.out.println(getTextFromHtml(htmlStr));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ spring:
|
||||
hibernate:
|
||||
ddl-auto: validate
|
||||
# H2 uses the same SQL dialect as MySQL in MySQL compatibility mode
|
||||
database-platform: org.hibernate.dialect.H2Dialect
|
||||
|
||||
# H2 console — enabled in dev ONLY.
|
||||
# Access restricted to ROLE_ADMIN via Spring Security (see WebSecurityConfig).
|
||||
@@ -46,10 +45,8 @@ spring:
|
||||
stat-view-servlet:
|
||||
enabled: true
|
||||
url-pattern: /druid/*
|
||||
# Druid's own login — change before deploying to any shared environment
|
||||
# Druid UI gated by Spring Security ROLE_ADMIN; built-in basic auth disabled
|
||||
reset-enable: false
|
||||
login-username: admin
|
||||
login-password: changeme_dev
|
||||
allow: 127.0.0.1
|
||||
|
||||
# Flyway: run V1 + V2 migrations on H2 in-memory DB on startup
|
||||
@@ -59,4 +56,6 @@ spring:
|
||||
user: ${spring.datasource.username}
|
||||
password: ${spring.datasource.password}
|
||||
|
||||
# Override default port for dev profile
|
||||
server:
|
||||
port: 80
|
||||
@@ -9,7 +9,6 @@ spring:
|
||||
hibernate:
|
||||
# Flyway owns all DDL; Hibernate only validates
|
||||
ddl-auto: validate
|
||||
database-platform: org.hibernate.dialect.MySQLDialect
|
||||
|
||||
# H2 console is OFF in production
|
||||
h2:
|
||||
@@ -18,7 +17,7 @@ spring:
|
||||
|
||||
datasource:
|
||||
# Updated URL for MySQL 8: removed deprecated useSSL=false; use requireSSL for prod
|
||||
url: jdbc:mysql://127.0.0.1:3306/jiscuss?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&serverTimezone=Asia/Shanghai
|
||||
url: jdbc:mysql://127.0.0.1:3306/jiscuss?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: changeme_production
|
||||
# Updated driver class name for MySQL Connector/J 8+
|
||||
@@ -43,9 +42,7 @@ spring:
|
||||
enabled: true
|
||||
url-pattern: /druid/*
|
||||
reset-enable: false
|
||||
# Change these credentials — Druid UI is also restricted to ROLE_ADMIN in Spring Security
|
||||
login-username: admin
|
||||
login-password: changeme_production
|
||||
# Druid UI is gated by Spring Security ROLE_ADMIN; built-in basic auth disabled
|
||||
allow: 127.0.0.1
|
||||
|
||||
# Flyway for MySQL
|
||||
@@ -55,22 +52,6 @@ spring:
|
||||
user: ${spring.datasource.username}
|
||||
password: ${spring.datasource.password}
|
||||
|
||||
freemarker:
|
||||
# 设置模板后缀名
|
||||
suffix: .ftl
|
||||
# 设置文档类型
|
||||
content-type: text/html
|
||||
# 设置页面编码格式
|
||||
charset: UTF-8
|
||||
# 设置页面缓存
|
||||
cache: false
|
||||
# 设置ftl文件路径
|
||||
template-loader-path:
|
||||
- classpath:/templates
|
||||
settings:
|
||||
classic_compatible: true
|
||||
# 设置静态文件路径,js,css等
|
||||
mvc:
|
||||
static-path-pattern: /static/**
|
||||
server:
|
||||
port: 80
|
||||
forward-headers-strategy: FRAMEWORK
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
insert into user
|
||||
values (1, 'admin', '管理员', 'as_2583698@sina.com', '123', '2019-09-09 00:00:00', 31, '', '男', '13804250293', 0, 0,
|
||||
'2019-09-09 00:00:00', 1, 1);
|
||||
|
||||
insert into user
|
||||
values (2, 'test', '测试用户1', 'as_2583698@sina.com', '123', '2019-09-09 00:00:00', 31, '', '男', '13804250293', 0, 0,
|
||||
'2019-09-09 00:00:00', 1, 0);
|
||||
|
||||
|
||||
insert into discussion
|
||||
values (1, '测试主题1', '测试内容1', null, null, null, '2020-09-19 00:00:00', 1, null, '2020-09-29 00:00:00', 2, null, null,
|
||||
null, null, null, 1, '2020-09-09 00:00:00');
|
||||
|
||||
|
||||
insert into discussion
|
||||
values (2, '测试主题2', '测试内容2', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (3, '测试主题3', '测试内容3', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
|
||||
insert into discussion
|
||||
values (4, '测试主题4', '测试内容4', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
|
||||
insert into discussion
|
||||
values (5, '测试主题5', '测试内容2测试内容2测试内容2测试内容2测试内容2测试内容2', null, null, null, null, null, null, null, 1, null, null, null,
|
||||
null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (6, '测试主题6', '测试内容3测试测试测试测试测试测试测试测试测试测试测试测试测试', null, null, null, null, null, null, null, 1, null, null, null,
|
||||
null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (7, '测试主题7', '测试内容7', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
|
||||
insert into discussion
|
||||
values (8, '测试主题8', '测试内容8', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (9, '测试主题9', '测试内容9', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (10, '测试主题10', '测试内容113', null, null, null, null, null, null, null, 1, null, null, null, null, null, null, null);
|
||||
|
||||
insert into discussion
|
||||
values (11, '测试主题11', '测试内容3测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试测试', null, null, null, null, null, null, null, 1,
|
||||
null, null, null, null, null, null, null);
|
||||
|
||||
insert into tag
|
||||
values (1, '测试标签1', null, null, 'edit', null, null, null, null, null, null, null);
|
||||
|
||||
|
||||
insert into post
|
||||
values (1, 1, 1, '2020-02-09 00:00:00', 1, null, '评论内容222', null, null, null, null, null, null, 1,
|
||||
'2020-08-09 00:00:00');
|
||||
|
||||
insert into post
|
||||
values (2, 1, 2, '2020-01-09 00:00:00', 2, null, '评论内容333', null, null, 1, null, null, null, 2, '2020-07-09 00:00:00');
|
||||
|
||||
insert into post
|
||||
values (7, 1, 7, '2020-01-09 00:00:00', 1, null, '评论内容3331111', null, null, 1, null, null, null, 1,
|
||||
'2020-03-09 00:00:00');
|
||||
|
||||
|
||||
insert into post
|
||||
values (3, 1, 3, '2020-01-09 00:00:00', 1, 1, '评论内容444', null, null, 1, null, null, null, 1, '2020-02-09 00:00:00');
|
||||
|
||||
insert into post
|
||||
values (4, 1, 4, '2020-01-09 00:00:00', 2, 1, '评论内容555', null, null, null, null, null, null, 2, '2020-03-09 00:00:00');
|
||||
|
||||
insert into post
|
||||
values (5, 1, 5, '2020-01-09 00:00:00', 2, null, '评论内容666', null, null, 1, null, null, null, 2, '2020-09-09 00:00:00');
|
||||
|
||||
insert into post
|
||||
values (6, 1, 6, '2020-01-09 00:00:00', 1, null, '评论内容777', null, null, 5, null, null, null, 1, '2020-09-09 00:00:00');
|
||||
@@ -126,24 +126,19 @@ INSERT INTO user (id, username, realname, email, password, join_time, age, avata
|
||||
discussions_count, comments_count, last_seen_time, flag, level)
|
||||
VALUES (1, 'admin', '管理员', 'admin@jiscuss.local',
|
||||
'$2a$10$7EqJtq98hPqEX7fNZaFWoOziYXMEIUBm..wTkPE3bsUJn4Lm7oHVC',
|
||||
'2019-09-09 00:00:00', 31, '', '男', '13800000001', 0, 0, '2019-09-09 00:00:00', 1, 1)
|
||||
ON DUPLICATE KEY UPDATE id = id;
|
||||
'2019-09-09 00:00:00', 31, '', '男', '13800000001', 0, 0, '2019-09-09 00:00:00', 1, 1);
|
||||
|
||||
INSERT INTO user (id, username, realname, email, password, join_time, age, avatar, gender, phone,
|
||||
discussions_count, comments_count, last_seen_time, flag, level)
|
||||
VALUES (2, 'test', '测试用户', 'test@jiscuss.local',
|
||||
'$2a$10$7EqJtq98hPqEX7fNZaFWoOziYXMEIUBm..wTkPE3bsUJn4Lm7oHVC',
|
||||
'2019-09-09 00:00:00', 31, '', '男', '13800000002', 0, 0, '2019-09-09 00:00:00', 1, 0)
|
||||
ON DUPLICATE KEY UPDATE id = id;
|
||||
'2019-09-09 00:00:00', 31, '', '男', '13800000002', 0, 0, '2019-09-09 00:00:00', 1, 0);
|
||||
|
||||
INSERT INTO discussion (id, title, content, start_time, start_user_id, last_time, last_user_id, create_id, create_time)
|
||||
VALUES (1, '测试主题1', '测试内容1', '2020-09-19 00:00:00', 1, '2020-09-29 00:00:00', 2, 1, '2020-09-09 00:00:00')
|
||||
ON DUPLICATE KEY UPDATE id = id;
|
||||
VALUES (1, '测试主题1', '测试内容1', '2020-09-19 00:00:00', 1, '2020-09-29 00:00:00', 2, 1, '2020-09-09 00:00:00');
|
||||
|
||||
INSERT INTO tag (id, name, icon, position)
|
||||
VALUES (1, '测试标签1', 'edit', 1)
|
||||
ON DUPLICATE KEY UPDATE id = id;
|
||||
VALUES (1, '测试标签1', 'edit', 1);
|
||||
|
||||
INSERT INTO post (id, discussion_id, number, time, user_id, content, create_id, create_time)
|
||||
VALUES (1, 1, 1, '2020-02-09 00:00:00', 1, '评论内容222', 1, '2020-08-09 00:00:00')
|
||||
ON DUPLICATE KEY UPDATE id = id;
|
||||
VALUES (1, 1, 1, '2020-02-09 00:00:00', 1, '评论内容222', 1, '2020-08-09 00:00:00');
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
-- 用户表1
|
||||
drop table if exists user;
|
||||
CREATE TABLE user
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
username varchar(20),
|
||||
realname varchar(20),
|
||||
email varchar(20),
|
||||
password varchar(20),
|
||||
join_time datetime,
|
||||
age INTEGER,
|
||||
avatar text,
|
||||
gender char(20),
|
||||
phone char(20),
|
||||
discussions_count INTEGER,
|
||||
comments_count INTEGER,
|
||||
last_seen_time datetime,
|
||||
flag INTEGER,
|
||||
level INTEGER
|
||||
);
|
||||
-- 主题表2
|
||||
drop table if exists discussion;
|
||||
CREATE TABLE discussion
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
title varchar(200),
|
||||
content text,
|
||||
comments_count INTEGER,
|
||||
participants_count INTEGER,
|
||||
number_index INTEGER,
|
||||
start_time datetime,
|
||||
start_user_id INTEGER,
|
||||
start_post_id INTEGER,
|
||||
last_time datetime,
|
||||
last_user_id INTEGER,
|
||||
last_post_id INTEGER,
|
||||
last_post_number INTEGER,
|
||||
is_approved INTEGER,
|
||||
like_count INTEGER,
|
||||
ip_address varchar(200),
|
||||
create_id INTEGER,
|
||||
create_time datetime
|
||||
);
|
||||
-- 主题标签关联表3
|
||||
drop table if exists discussiontag;
|
||||
CREATE TABLE discussiontag
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
discussion_id INTEGER not null,
|
||||
tag_id INTEGER
|
||||
);
|
||||
-- 评论表4
|
||||
drop table if exists post;
|
||||
CREATE TABLE post
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
discussion_id INTEGER,
|
||||
number INTEGER,
|
||||
time datetime,
|
||||
user_id INTEGER,
|
||||
type varchar(20),
|
||||
content text,
|
||||
edit_time datetime,
|
||||
edit_user_id INTEGER,
|
||||
parent_id INTEGER,
|
||||
ip_address varchar(200),
|
||||
copyright varchar(200),
|
||||
is_approved INTEGER,
|
||||
create_id INTEGER,
|
||||
create_time datetime
|
||||
);
|
||||
-- 设置表5
|
||||
drop table if exists setting;
|
||||
CREATE TABLE setting
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
setting_key varchar(20),
|
||||
setting_value text
|
||||
);
|
||||
-- 标签表6
|
||||
drop table if exists tag;
|
||||
CREATE TABLE tag
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
name varchar(200),
|
||||
description varchar(200),
|
||||
color varchar(200),
|
||||
icon varchar(200),
|
||||
position INTEGER,
|
||||
parent_id INTEGER,
|
||||
discussions_count text,
|
||||
last_time datetime,
|
||||
last_discussion_id INTEGER,
|
||||
create_id INTEGER,
|
||||
create_time datetime
|
||||
);
|
||||
-- 喜欢收藏表7
|
||||
drop table if exists likecollect;
|
||||
CREATE TABLE likecollect
|
||||
(
|
||||
id INTEGER not null primary key auto_increment,
|
||||
discussion_id INTEGER,
|
||||
discussion_name varchar(200),
|
||||
tag_id INTEGER,
|
||||
post_id INTEGER,
|
||||
post_content text,
|
||||
user_id INTEGER,
|
||||
user_name varchar(200),
|
||||
type varchar(20),
|
||||
like_type varchar(20),
|
||||
collect_type varchar(20),
|
||||
create_id INTEGER,
|
||||
create_time datetime
|
||||
);
|
||||
|
||||
@@ -21,7 +21,8 @@ $("#newdiscussions").click(function () {
|
||||
url: "/newdiscussions",
|
||||
data: JSON.stringify({
|
||||
title: title,
|
||||
content: content
|
||||
content: content,
|
||||
tag: Array.isArray(tag) ? tag.join(",") : tag
|
||||
}),
|
||||
contentType: 'application/json',
|
||||
beforeSend: function (request) {
|
||||
|
||||
Reference in New Issue
Block a user