同学们有没有觉得之前的代码都是面向数据库的?仍然没有什么“业务逻辑”?
我们以注册为例,说一说什么是业务逻辑:
这些逻辑,都应该放到User.Register()方法中。
引入相应字段:
private int bCredit; private String inviteCode; @ManyToOne private User invitedBy;邀请人(invitedBy),在对象生成后立即设置。比如在UserService.Register()中:
public int Register(RegisterModel model) { User user = userConvert.toUser(model); User invitedBy = userRepository.GetByName(model.getInvitedByName()).get(0); user.setInvitedBy(invitedBy);
其他在注册时直接赋值:
public void Register() { inviteCode = String.valueOf(Math.round((1+Math.random())*10000-10000)); bCredit += 10; }
邀请码还可以设置成只读,因为之后就不会再变化。
#体会#:当Register()中有了业务逻辑之后,就更能体现出dbFactory的作用
public class UserFactory { static User fg, atai; public static void create(EntityManager em) { fg = register("fg", null, em); atai = register("atai", fg, em); }
private static User register(String username, User invitedBy, EntityManager em) {
相对于帮帮点,帮帮币(BMoney)价值更高一些(详见功能索引),所以我们应记录其每一次的变动(明细,包括发生时间、金额、事由……),以备查验。
所以我们需要一个新的entity:
public class BMoney extends Entity{@想一想@:它应该有哪些属性,和User直接是什么关联关系?
@javax.persistence.Entity public class BMoney extends Entity{ private int amount; private String comment; @ManyToOne private User owner;
所有这些字段都可以封装成只读属性!
说明:稍微简化一下,将帮帮币的掉落改成直接奖励。
我们可以在Register()方法里面生成一个帮帮币的明细对象,但怎么将这个对象持久化呢?这就需要用到双向引用和casacade(复习)了:
@OneToMany(mappedBy = "owner", cascade = CascadeType.PERSIST) private List<BMoney> bMoneys;
bMoneys = new ArrayList<>(); bMoneys.add(new BMoney(5, "注册奖励", this));
很多时候,我们都要知道某用户现有多少帮帮币,怎么办呢,这样吗?(方案一)
public int GetBMoney() { return bMoneys.stream() .collect(Collectors.summingInt(b -> b.getAmount())) .intValue(); }
但每次都这样统计的话,如果用户的帮帮币明细数量较大,是不是就会有性能问题?
为了提高性能(用空间换时间),我们有意识的使用了冗余:(方案二)
private int bMoneyBalance;在User中直接记录用户现有的结余。
但这样的话,又会带来另外一个问题:该用户任何一个地方(比如发布文章/设置求助悬赏/交易……)的帮帮币变动,都要更新结余,
private void refreshBMoney() { bMoneyBalance = bMoneys.stream()
bMoneys.add(new BMoney(5, "注册奖励", this)); refreshBMoney();这不仅麻烦,而且容易疏漏!
所以最后的解决方案是在每一个帮帮币明细项中记录其当前结余(同时满足UI的呈现需求):
public class BMoney extends Entity{ private int balance;
但这个balance的值从哪里来?还是不能靠repository查询,只能是关联entity!所以在User中添加一个:
@OneToOne private BMoney latestBMoney;这样,在BMoney的构造函数中,通过owner的最新BMoney明细记录计算出当下的结余:
//省略null值判断 this.balance = this.owner.getLatestBMoney().balance + this.amount;不要忘了更新owner的最新BMoney明细记录:
this.owner.setLatestBMoney(this);然后要获取用户当前帮帮币数量,只需要找到最近的一次帮帮币明细,查看其中的结余即可:
System.out.println(UserFactory.fg.getLatestBMoney().getAmount());
发布文章,就会消耗一个帮帮币,怎么实现?很简单,Article的Publish()方法中:
author.addBMoney(new BMoney(-1, "文章发布", author));addBMoney是因为User中没有暴露bMoneys集合(避免滥用),声明的方法:
public void addBMoney(BMoney money) { bMoneys.add(money); }
最后,不要忘了在ArticleService中调用article.Publish()方法:
articleRepository.Save(article); article.Publish();
用户现有帮帮币。
首先,LoginStatusModel中添加balance字段/属性,便于页面呈现:
public class LoginStatusModel { private int balance;
<span>源栈欢迎你, <a href="/User/${loginStatus.id}">${loginStatus.username}</a> (${loginStatus.balance}) </span>因为在LoginStatusModel是和entity User进行映射,而User没有balance属性(只有latestBMoney),我也不喜欢字符串格式的express映射:
@Mapping(target = "balance", expression = "java(...)") LoginStatusModel toLoginStatusModel(User entity);所以就在UserService中用代码赋值:
model.setBalance(currentUser.getLatestBMoney().getBalance());
当新用户注册时,要发送一个消息给邀请人……怎么发?
演示:我的消息
@想一想@:邀请人怎么就能知道谁谁谁用他/她做邀请人?
所谓“动态”生成,就是每次都利用现有数据(比如博客评论)生成消息,生成的消息不予以保存。
但这有两个问题:
所以我们宁愿有一定的冗余:在消息事件发生(比如注册)时,就生成的消息(message)并予以存储,以后消息接收人(receiver)只需简单查询就能得到ta的消息。
具体实现:
@javax.persistence.Entity public class Message extends Entity { private String body; @ManyToOne private User receiver; private String kind; private boolean hasRead;
@OneToMany(mappedBy = "receiver", cascade = CascadeType.PERSIST) private Set<Message> messages = new HashSet<>();
if (invitedBy != null) { invitedBy.messages.add( new Message(this.getUsername() + "使用你的邀请码注册成功", invitedBy, "邀请") ); }//else nothing
用户可以根据种类订阅/退订消息。
首先,如何记录用户的订阅?
在Java语言层面,
public class MessageKind { public final static String RegisterInvitedBy = "注册邀请"; public final static String BeCommentd = "被评论";
private String[] descriptions;
关键是在数据库层面怎么办?
//注意不是数组了! private String descriptions = "";
所以就需要一个“转换”:
private static final String splitor = ",";
public String[] getDescriptions() { return descriptions.split(splitor); } public void setDescriptions(String[] descriptions) { this.descriptions = String.join(splitor, descriptions); }
最后,利用上述工具:
if (Arrays.asList(invitedBy.getDescriptions()).contains(MessageKind.RegisterInvitedBy)) {
应该有一个方法,类似于SendMessage()啥的,但这个方法放在哪里呢?User中么?user.Send(messge)……
不对,更好的实现应该是:message.Send()(复习:类的职责)
public class Message extends Entity { public void Send() { if (Arrays.asList(receiver.getDescriptions()) .contains(MessageKind.RegisterInvitedBy)) { receiver.getMessages().add(this); } // else nothing }
判断收件人是否为null值,这里又有两种选择,写在send()方法
public void Send() { if (receiver != null && Arrays.asList(receiver.getDescriptions())
public void Register() { if (invitedBy != null) { new Message().Send();
@想一想@:哪一个更好?为什么?
外面更好,而且Send()方法内部,还有做检查(复习:防御式编程):
public void Send() { if (receiver == null ) { throw new IllegalArgumentException("收件人不能为null……"); }
帮帮币明细的备注、用户消息的内容(比如“谁谁谁”使用了你的邀请码……中的“谁谁谁”)在entity中生成,但其格式(样式/链接等)应由UI决定,如何解决这个问题呢?
UI把消息内容制作成模板(template),放到entity和ui都能够访问的glb中,
package glb; public class HtmlTemplate { public static String UserRegister(int userId, String username) { return String.format("<a href='/User/%s'>%s</a>使用你的邀请码注册成功!", userId, username);entity调用填充:
new Message(HtmlTemplate.UserRegister(id, username), invitedBy, MessageKind.RegisterInvitedBy)注意:这就是为什么之前强调的,entity总是要先save()再调用其方法的原因,没有save()就没有id!
userRepository.Save(user); user.Register();
首先,在数据库中为每个email保存一个校验码(随机数)
然后,将这个校验码发送到该email邮箱中。
这样,就只有email的所有人能得到这个校验码,用于激活邮箱(即让系统确认该email可用,且属于该用户)
为了方便,一般是直接直接发送一个链接,url参数中带着校验码,比如:
点击 <a href='http://localhost:8080/17bang/email/validate?id=1&token=4936'>激活</a>你的email……
实现以上功能,至少需要记录email地址、校验码,以及是否激活三个信息;为了演示效果,我们将其封装成一个component:
@Embeddable public class EmailStatus { private String address; private String token; private Boolean validated;
public class User extends Entity { @Embedded private EmailStatus email;为了有效封装token可用设置成只读,并在构造函数中赋值:
public EmailStatus(String address) { super(); this.token = StringHelper.GetRandom();PS:GetRandom()是我们自己封装的,实现同邀请码……
在User.Register中就可以调用:
EmailHelper.Send(email.getAddress(), "激活email", HtmlTemplate.EmailValid(getId(), email.getToken()));
public static String EmailValid(int userId, String token) { return String.format( "点击 <a href='http://localhost:8080/17bang/email/validate?id=%s&token=%s'>激活</a>你的email", userId, token); }
最后,再添加对应的页面:
@RequestMapping(URLMapping.Email.Base) public class EmailController extends BaseController { @RequestMapping(URLMapping.Email.Validate) public String validate(Model model, int userId, String token) { model.addAttribute("result", userService.valid(userId, token)); return URLMapping.Email.Base + URLMapping.Email.Validate;在UserService(也可以单独声明一个EmailService)中注意
public boolean valid(int userId, String token) { User user = userRepository.Get(User.class, userId); //调用user方法,而不是直接改user值 return user.ValidEmail(token);在User中:
public boolean ValidEmail(String token) { boolean passed = getEmail().getToken().equals(token); if (passed) { getEmail().setValidated(true); }//else nothing return passed;
log4j
<context-param> <param-name>log4jConfiguration</param-name> <param-value>/WEB-INF/log4j2.xml</param-value> </context-param>
我们自己也可以使用log4j记录各种信息:
import org.jboss.logging.Logger; private Logger logger = Logger.getLogger(RegisterControler.class); logger.info("This is an info log entry"); logger.error("This is an error log entry");
多快好省!前端后端,线上线下,名师精讲
更多了解 加: