NutzBook - 用户头像上传及显示

NutzBook - 用户头像上传及显示

Tags: Nutz

摘要

NutzBook - 用户头像上传及显示

建立UserProfile类

package net.javablog.bean;

import org.nutz.dao.entity.annotation.Column;
import org.nutz.dao.entity.annotation.Id;
import org.nutz.dao.entity.annotation.Table;
import org.nutz.json.JsonField;

@Table("t_user_profile")
public class UserProfile extends BasePojo {

    /**
     * 关联的用户id
     */
    @Id(auto = false)
    @Column("uid")
    protected int userId;
    /**
     * 用户昵称
     */
    @Column
    protected String nickname;
    /**
     * 用户邮箱
     */
    @Column
    protected String email;
    /**
     * 邮箱是否已经验证过
     */
    @Column("email_checked")
    protected boolean emailChecked;
    /**
     * 头像的byte数据
     */
    @Column
    @JsonField(ignore = true)
    protected byte[] avatar;
    /**
     * 性别
     */
    @Column
    protected String gender;
    /**
     * 自我介绍
     */
    @Column("dt")
    protected String description;
    @Column("loc")
    protected String location;

    public int getUserId() {
        return userId;
    }

    public void setUserId(int userId) {
        this.userId = userId;
    }

    public String getNickname() {
        return nickname;
    }

    public void setNickname(String nickname) {
        this.nickname = nickname;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public boolean isEmailChecked() {
        return emailChecked;
    }

    public void setEmailChecked(boolean emailChecked) {
        this.emailChecked = emailChecked;
    }

    public byte[] getAvatar() {
        return avatar;
    }

    public void setAvatar(byte[] avatar) {
        this.avatar = avatar;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

建立关联关系

打开User类,加入2行,记得setter,getter

 @One(target=UserProfile.class, field="id", key="userId")
    protected UserProfile profile;

target: 当前类(User)跟哪个目标类(UserProfile)一对一关联
field:当前类中的哪个属性(类的属性,不是表的字段)跟目标类中的属性关联。
key:目标类中被关联的属性(类的属性,不是表的字段)

UserProfile改查方法

新建一个单独的模块

package net.javablog.module;

import net.javablog.bean.UserProfile;
import org.nutz.dao.FieldFilter;
import org.nutz.dao.util.Daos;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.Scope;
import org.nutz.mvc.adaptor.JsonAdaptor;
import org.nutz.mvc.annotation.*;
import org.nutz.mvc.filter.CheckSession;

import java.util.Date;

@IocBean
@At("/user/profile")
@Filters(@By(type = CheckSession.class, args = {"me", "/"})) // 检查当前Session是否带me这个属性
public class UserProfileModule extends BaseModule {

    @At
    public UserProfile get(@Attr(scope = Scope.SESSION, value = "me") int userId) {
        UserProfile profile = Daos.ext(dao, FieldFilter.locked(UserProfile.class, "avatar")).fetch(UserProfile.class, userId);
        if (profile == null) {
            profile = new UserProfile();
            profile.setUserId(userId);
            profile.setCreateTime(new Date());
            profile.setUpdateTime(new Date());
            dao.insert(profile);
        }
        return profile;
    }

    @At
    @AdaptBy(type = JsonAdaptor.class)
    @Ok("void")
    public void update(@Param("..") UserProfile profile, @Attr(scope = Scope.SESSION, value = "me") int userId) {
        if (profile == null)
            return;
        profile.setUserId(userId);//修正userId,防止恶意修改其他用户的信息
        profile.setUpdateTime(new Date());
        profile.setAvatar(null); // 不准通过这个方法更新
        UserProfile old = get(userId);
        // 检查email相关的更新
        if (old.getEmail() == null) {
            // 老的邮箱为null,所以新的肯定是未check的状态
            profile.setEmailChecked(false);
        } else {
            if (profile.getEmail() == null) {
                profile.setEmail(old.getEmail());
                profile.setEmailChecked(old.isEmailChecked());
            } else if (!profile.getEmail().equals(old.getEmail())) {
                // 设置新邮箱,果断设置为未检查状态
                profile.setEmailChecked(false);
            } else {
                profile.setEmailChecked(old.isEmailChecked());
            }
        }
        Daos.ext(dao, FieldFilter.create(UserProfile.class, null, "avatar", true)).update(profile);
    }
}

User被删除的时候,UserProfile要同时被删除。所以要修改UserModule类的删除User方法。
注意是在同一个事务中。

   @At
    @Aop(TransAop.READ_COMMITTED)
    public Object delete(@Param("id") int id, @Attr("me") int me) {
        if (me == id) {
            return new NutMap().setv("ok", false).setv("msg", "不能删除当前用户!!");
        }
        dao.delete(User.class, id); // 再严谨一些的话,需要判断是否为>0
        dao.clear(UserProfile.class, Cnd.where("userId", "=", me));
        return new NutMap().setv("ok", true);
    }

上传头像入口方法

@AdaptBy(type = UploadAdaptor.class, args = {"${app.root}/WEB-INF/tmp/user_avatar", "8192", "utf-8", "20000", "102400"})
    @POST
    @Ok(">>:/user/profile")
    @At("/avatar")
    public void uploadAvatar(@Param("file") TempFile tf,
                             @Attr(scope = Scope.SESSION, value = "me") int userId,
                             AdaptorErrorContext err) {
        String msg = null;
        if (err != null && err.getAdaptorErr() != null) {
            msg = "文件大小不符合规定";
        } else if (tf == null) {
            msg = "空文件";
        } else {
            UserProfile profile = get(userId);
            try {
                BufferedImage image = Images.read(tf.getFile());
                image = Images.zoomScale(image, 128, 128, Color.WHITE);
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                Images.writeJpeg(image, out, 0.8f);
                profile.setAvatar(out.toByteArray());
                dao.update(profile, "^avatar$");
            } catch (DaoException e) {
                log.info("System Error", e);
                msg = "系统错误";
            } catch (Throwable e) {
                msg = "图片格式错误";
            }
        }

        if (msg != null)
            Mvcs.getHttpSession().setAttribute("upload-error-msg", msg);
    }

Upload适配器的几个参数: 临时文件夹路径,缓存环的大小,编码,临时文件夹的文件数,上传文件的最大大小

图片读取方法

 @Ok("raw:jpg")
    @At("/avatar")
    @GET
    public Object readAvatar(@Attr(scope = Scope.SESSION, value = "me") int userId, HttpServletRequest req) throws SQLException {
        UserProfile profile = Daos.ext(dao, FieldFilter.create(UserProfile.class, "^avatar$")).fetch(UserProfile.class, userId);
        if (profile == null || profile.getAvatar() == null) {
            return new File(req.getSession().getServletContext().getRealPath("/rs/user_avatar/none.jpg"));
        }
        return profile.getAvatar();
    }

如果用户没有头像信息,就读取一个默认图片显示出来。

用户详情页

准备一个默认头像,放在webapp/rs/user_avatar/none.jpg的位置

打开UserProfileModule类,加入一个方法, 用于内部跳转到profile.jsp

    @At("/")
    @GET
    @Ok("jsp:jsp.user.profile")
    public UserProfile index(@Attr(scope=Scope.SESSION, value="me")int userId) {
        return get(userId);
    }

新建一个jsp文件 webapp/WEB-INF/jsp/user/profile.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" trimDirectiveWhitespaces="true"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>用户详情页</title>
  <script type="text/javascript"
          src="http://cdn.staticfile.org/jquery/1.8.3/jquery.min.js"></script>
  <script type="text/javascript">
    var base = '${base}';
    $.fn.serializeObject = function() {
      var o = {};
      var a = this.serializeArray();
      $.each(a, function() {
        if (o[this.name] !== undefined) {
          if (!o[this.name].push) {
            o[this.name] = [ o[this.name] ];
          }
          o[this.name].push(this.value || '');
        } else {
          o[this.name] = this.value || '';
        }
      });
      return o;
    };
    $(function() {
      $("#user_profile_btn").click(function() {
        //alert(JSON.stringify($("#user_profile").serializeObject()));
        $.ajax({
          url : base + "/user/profile/update",
          type : "POST",
          data : JSON.stringify($("#user_profile").serializeObject()),
          success : function() {
            location.reload();
          }
        });
      });
    });
  </script>
</head>
<body>
<div>
  <div>
    头像 <img alt="用户头像" src="${base}/user/profile/avatar">
    <p />
    <form action="${base}/user/profile/avatar" method="post"
          enctype="multipart/form-data">
      头像文件 <input type="file" name="file">
      <button type="submit">更新头像</button>
    </form>
            <span class="color:#f00"> <%
              if (session.getAttribute("upload-error-msg") != null) {
                String msg = session.getAttribute("upload-error-msg")
                        .toString();
                out.print(msg);
                session.removeAttribute("upload-error-msg");
              }
            %>
            </span><p />
  </div>
</div>
<div>
  <form action="#" id="user_profile" method="post">
    <div>
      id:<c:out value="${obj.userId}"></c:out><p />
    </div>
    <div>
      昵称:<input name="nickname" value="${obj.nickname}"><p />
    </div>
    <div>
      邮箱:<input name="email" value="${obj.email}">
      <p />
    </div>
    <div>
      邮箱验证状态:<c:out value="${obj.emailChecked}"></c:out><p />
    </div>
    <div>
      性别:<input name="gender" value="${obj.gender}"><p />
    </div>
    <div>
      自我介绍:<input name="description" value="${obj.description}"><p />
    </div>
    <div>
      地理位置:<input name="location" value="${obj.location}"><p />
    </div>
  </form>
  <button type="button" id="user_profile_btn">更新</button>
</div>
</body>
</html>
`

测试页面

先访问 http://127.0.0.1:8080/ 登陆
再访问 http://127.0.0.1:8080/user/profile

上传的图片需要是jpg的类型