Security decisions: *
Intentionally excludes the {@code password} field from all output DTOs. - * Replace with a generated MapStruct mapper if mapping complexity grows. + *
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; } } diff --git a/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java b/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java index 5dd7e62..1c6689c 100644 --- a/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java +++ b/src/main/java/com/yaoyuan/jiscuss/handler/CustomAccessDeniedHandler.java @@ -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 + *
Behavior: + *
您没有权限访问该资源。
"); } } } - public static class ControllerTools { - public 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(); - } + private static boolean isAjaxRequest(HttpServletRequest request) { + return "XMLHttpRequest".equals(request.getHeader("X-Requested-With")); } - } + diff --git a/src/main/java/com/yaoyuan/jiscuss/service/impl/PostsServiceImpl.java b/src/main/java/com/yaoyuan/jiscuss/service/impl/PostsServiceImpl.java index ec246c7..d975c6c 100644 --- a/src/main/java/com/yaoyuan/jiscuss/service/impl/PostsServiceImpl.java +++ b/src/main/java/com/yaoyuan/jiscuss/service/impl/PostsServiceImpl.java @@ -69,7 +69,6 @@ public class PostsServiceImpl implements IPostsService { } //将回复添加到对应的位置 List list = Node.addAllNode(nodes, thenpostCustomList); - System.out.println(); //打印回复链表 Node.show(list); diff --git a/src/main/java/com/yaoyuan/jiscuss/util/DelTagsUtil.java b/src/main/java/com/yaoyuan/jiscuss/util/DelTagsUtil.java index 78bc33f..2bb8f38 100644 --- a/src/main/java/com/yaoyuan/jiscuss/util/DelTagsUtil.java +++ b/src/main/java/com/yaoyuan/jiscuss/util/DelTagsUtil.java @@ -49,9 +49,4 @@ public class DelTagsUtil { return htmlStr; } - public static void main(String[] args) { - String htmlStr = "test"; - System.out.println(getTextFromHtml(htmlStr)); - } - } diff --git a/src/main/resources/application-h2.yml b/src/main/resources/application-h2.yml index 03243f0..96c1efc 100644 --- a/src/main/resources/application-h2.yml +++ b/src/main/resources/application-h2.yml @@ -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} - port: 80 \ No newline at end of file +# Override default port for dev profile +server: + port: 80 diff --git a/src/main/resources/application-mysql.yml b/src/main/resources/application-mysql.yml index 3aa84d4..ca23243 100644 --- a/src/main/resources/application-mysql.yml +++ b/src/main/resources/application-mysql.yml @@ -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 \ No newline at end of file + port: 80 + forward-headers-strategy: FRAMEWORK diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql deleted file mode 100644 index cc1e06b..0000000 --- a/src/main/resources/data.sql +++ /dev/null @@ -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'); diff --git a/src/main/resources/db/migration/V1__init_schema.sql b/src/main/resources/db/migration/V1__init_schema.sql index 569de4f..d0608fa 100644 --- a/src/main/resources/db/migration/V1__init_schema.sql +++ b/src/main/resources/db/migration/V1__init_schema.sql @@ -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'); diff --git a/src/main/resources/schema.sql b/src/main/resources/schema.sql deleted file mode 100644 index 2232f76..0000000 --- a/src/main/resources/schema.sql +++ /dev/null @@ -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 -); - diff --git a/src/main/resources/static/js/user/newdiscussiontag.js b/src/main/resources/static/js/user/newdiscussiontag.js index 562a1d7..7e431cd 100644 --- a/src/main/resources/static/js/user/newdiscussiontag.js +++ b/src/main/resources/static/js/user/newdiscussiontag.js @@ -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) {