You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Thymeleaf结合Spring MVC:如何让按钮执行Post类的Java方法?

解决点赞/点踩按钮的后端交互问题

嘿,我来帮你搞定这个事儿!你现在的写法其实踩了Thymeleaf的一个常见坑:Thymeleaf是服务器端模板引擎,当页面渲染完成发送到浏览器后,后端的Post对象就不存在了——你现在写的th:onclick="${Post.upvote()}"只会在服务器生成页面的时候执行一次,根本不会在用户点击按钮时触发。

要实现点击按钮调用Post类的方法,我们得通过前端发起HTTP请求到后端接口,让后端去调用对应的方法,再把结果返回给前端更新页面。下面给你两种实用的实现方案:

1. 先搞定后端接口

首先在你的Spring控制器(假设是Spring Boot项目)里加两个接口,专门处理点赞和点踩请求,接收帖子的ID作为参数:

@Controller
@RequestMapping("/posts")
public class PostController {

    // 假设你用PostRepository来操作帖子数据
    @Autowired
    private PostRepository postRepository;

    // 处理点赞请求
    @PostMapping("/{id}/upvote")
    public String upvotePost(@PathVariable Long id) {
        // 根据ID找到对应的帖子
        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("无效的帖子ID"));
        post.upvote(); // 调用你Post类里的点赞方法
        postRepository.save(post); // 保存更新后的点赞数
        // 重定向回帖子列表页面,刷新后就能看到新的点赞数
        return "redirect:/posts"; // 这里替换成你的帖子列表页面路径
    }

    // 处理点踩请求
    @PostMapping("/{id}/downvote")
    public String downvotePost(@PathVariable Long id) {
        Post post = postRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("无效的帖子ID"));
        post.downvote(); // 调用点踩方法
        postRepository.save(post);
        return "redirect:/posts";
    }
}

2. 修改前端Thymeleaf代码

方案一:表单提交(简单直接,适合快速实现)

把每个按钮放在独立的表单里,提交到对应的后端接口,自动带上帖子ID:

<ul>
    <!-- 注意这里th:each要放在li上,你原来的th:block嵌套有点问题,调整一下 -->
    <li class="post" th:each="post : ${posts}">
        <p th:text="${post.postText}"></p>
        
        <!-- 点赞表单 -->
        <form th:action="@{/posts/{id}/upvote(id=${post.id})}" method="post">
            <button type="submit">Upvote</button>
        </form>
        
        <!-- 点踩表单 -->
        <form th:action="@{/posts/{id}/downvote(id=${post.id})}" method="post">
            <button type="submit">Downvote</button>
        </form>
        
        <p th:text="${post.postVotes}"></p>
    </li>
</ul>

这里要注意:

  • 我把你原来的th:block去掉了,直接把th:each放在li上更规范
  • th:action动态生成带帖子ID的请求地址
  • 点击按钮会提交表单,后端处理后重定向回列表页,刷新后就能看到更新的点赞数

方案二:AJAX无刷新更新(用户体验更好)

如果不想每次点击都刷新整个页面,可以用AJAX发送请求,直接更新页面上的点赞数:

首先调整前端代码,给点赞数元素加唯一ID,给按钮加数据属性存帖子ID:

<ul>
    <li class="post" th:each="post : ${posts}">
        <p th:text="${post.postText}"></p>
        
        <button class="upvote-btn" th:data-post-id="${post.id}">Upvote</button>
        <button class="downvote-btn" th:data-post-id="${post.id}">Downvote</button>
        
        <!-- 给点赞数元素加唯一ID,方便JS定位 -->
        <p class="vote-count" th:text="${post.postVotes}" th:id="'vote-count-' + ${post.id}"></p>
    </li>
</ul>

<!-- 原生JS实现AJAX -->
<script>
    // 绑定点赞按钮的点击事件
    document.querySelectorAll('.upvote-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const postId = this.dataset.postId;
            // 发送POST请求到后端接口
            fetch(`/posts/${postId}/upvote`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    // 记得加CSRF令牌,Spring Boot默认开启CSRF保护
                    'X-CSRF-TOKEN': document.querySelector('meta[name="_csrf"]').content
                }
            })
            .then(response => response.text())
            .then(newVoteCount => {
                // 更新页面上的点赞数
                document.getElementById(`vote-count-${postId}`).textContent = newVoteCount;
            })
            .catch(error => console.error('点赞出错:', error));
        });
    });

    // 点踩按钮同理
    document.querySelectorAll('.downvote-btn').forEach(btn => {
        btn.addEventListener('click', function() {
            const postId = this.dataset.postId;
            fetch(`/posts/${postId}/downvote`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRF-TOKEN': document.querySelector('meta[name="_csrf"]').content
                }
            })
            .then(response => response.text())
            .then(newVoteCount => {
                document.getElementById(`vote-count-${postId}`).textContent = newVoteCount;
            })
            .catch(error => console.error('点踩出错:', error));
        });
    });
</script>

然后修改后端接口,让它直接返回更新后的点赞数,而不是重定向:

@PostMapping("/{id}/upvote")
@ResponseBody // 告诉Spring直接返回数据,不是视图
public Integer upvotePost(@PathVariable Long id) {
    Post post = postRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("无效的帖子ID"));
    post.upvote();
    postRepository.save(post);
    return post.getPostVotes(); // 返回更新后的点赞数
}

@PostMapping("/{id}/downvote")
@ResponseBody
public Integer downvotePost(@PathVariable Long id) {
    Post post = postRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("无效的帖子ID"));
    post.downvote();
    postRepository.save(post);
    return post.getPostVotes();
}

另外,别忘了在HTML头部添加CSRF元标签,否则AJAX请求会被Spring拦截:

<meta name="_csrf" th:content="${_csrf.token}"/>
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

最后补个小提醒

你的Post类里的upvote()downvote()方法要确保逻辑正确,比如:

public class Post {
    private int postVotes;

    public void upvote() {
        this.postVotes++;
    }

    public void downvote() {
        this.postVotes--;
    }

    // 别忘了getter和setter
    public int getPostVotes() {
        return postVotes;
    }

    public void setPostVotes(int postVotes) {
        this.postVotes = postVotes;
    }
}

内容的提问来源于stack exchange,提问作者ShadowWesley77

火山引擎 最新活动